skip to content

In PHP, what does declare(strict_types=1) change about how scalar parameter and return type declarations are checked?

level: juniorimportance: must knowfreq 74%

answer

  1. coercive mode is the default
  2. only int, float, string, bool affected
  3. mismatch throws TypeError
  4. one exception: int widens to float
  5. must be the file's first statement

basics

~20 s

By default PHP coerces a wrong-typed scalar into the declared type when it can, so "12" passed to an int parameter becomes 12. With declare(strict_types=1) the value must already match, or a TypeError is thrown; only int-to-float widening survives.

solid answer

~40 s

PHP checks scalar declarations (`int`, `float`, `string`, `bool`) in one of two modes. The default, **coercive mode**, converts a value that can be converted: `"12"` becomes `12`, `true` becomes `1`, `5` becomes `"5"`; a value that cannot be converted, such as `"abc"` for `int` or an array for `string`, still throws `TypeError`. `declare(strict_types=1)` switches the file to **strict mode**: the value's type must match the declaration exactly, otherwise `TypeError`, and the single allowed conversion is an `int` passed where a `float` is declared. The declaration must be the first statement in the file and applies to that file only. Class, interface and `array` declarations were never coerced, so the flag changes nothing for them.

code

php · 16 lines
php
<?php
// no strict_types declaration: coercive mode

function fineCents(int $days): int
{
    return $days * 25;
}

var_dump(fineCents("4"));  // int(100): "4" coerced to 4
var_dump(fineCents(true)); // int(25): true coerced to 1

try {
    fineCents("four");
} catch (TypeError $e) {
    echo $e->getMessage(), PHP_EOL; // not coercible even here
}

go deeper

for a junior

Recall the two modes: coercive converts "12" to 12, strict throws TypeError. Know that int still passes a float declaration and that the declare line goes first in the file.

for a middle

Explain which values coercive mode rejects anyway, why class types are unaffected, and that the flag covers built-in function calls and return values, not operators.

for a senior

Show how strict mode moves type failures to the call site and forces explicit conversion at input boundaries, and why that pairs with static analysis in CI.

for a principal

Weigh the cost of enforcing strict_types on every file in a large codebase — boundary conversions, test churn, tooling — against the silent-coercion bugs it removes.

## Two ways PHP can check a scalar declaration A **type declaration** is the type written in front of a parameter or after a function's parameter list: `function fineCents(int $days): int`. PHP checks it at runtime every time the function is called and every time it returns. For **scalar types** — `int`, `float`, `string` and `bool` — the check runs in one of two modes: - **Coercive mode** (the default, sometimes called weak mode): if the value has a different scalar type but can be converted, PHP converts it and the function receives the converted value. - **Strict mode** (`declare(strict_types=1)`): the value must already have the declared type. A mismatch throws `TypeError`. `TypeError` is an `Error`, not an `Exception`, so a `catch (Exception $e)` block does not see it. ## What coercive mode accepts Coercion is not "anything goes". It follows fixed rules, and some values are rejected in both modes: | Value passed | Declared `int` | Declared `float` | Declared `string` | Declared `bool` | |---|---|---|---|---| | `"12"` | `12` | `12.0` | `"12"` | `true` | | `"abc"` | `TypeError` | `TypeError` | `"abc"` | `true` | | `7.0` | `7` | `7.0` | `"7"` | `true` | | `7.5` | `7`, plus a deprecation | `7.5` | `"7.5"` | `true` | | `true` | `1` | `1.0` | `"1"` | `true` | | `[1, 2]` | `TypeError` | `TypeError` | `TypeError` | `TypeError` | Two details from recent releases matter here: 1. Since **PHP 8.1**, passing a float with a fractional part to an `int` declaration in coercive mode emits `Deprecated: Implicit conversion from float 7.5 to int loses precision` and truncates. 2. `null` is never coerced for a user-defined function: a non-nullable `int` parameter given `null` throws `TypeError` in both modes. (Built-in functions have their own, older rule for `null`.) ## What strict mode changes In strict mode the conversion column collapses. A value either already has the declared type, or the call fails: ```php <?php declare(strict_types=1); function fineCents(int $days): int { return $days * 25; } fineCents(4); // int(100) fineCents("4"); // TypeError: Argument #1 ($days) must be of type int, string given ``` The one conversion strict mode keeps is **int-to-float widening**: an `int` passed to a `float` parameter, or returned from a function declared `: float`, is accepted and becomes a float. The reverse is not: `7.0` passed to `int` is a `TypeError` in strict mode even though it has no fractional part. Strict mode applies to the same places coercive mode does: - **arguments** passed to user-defined functions and methods; - **arguments** passed to built-in functions such as `strlen()` — `strlen(123)` throws `TypeError` in a strict file and returns `3` in a coercive one; - **return values** checked against a function's return declaration. Which file's mode governs a given call is its own rule: for arguments it is the file that makes the call, for return values the file that declares the function. ## Writing the declaration correctly The engine is strict about the declaration itself: - It must be the **very first statement** in the file. Only the opening `<?php` tag, whitespace, comments and other `declare` statements may come before it; a `namespace` line comes after it. Anything else is a compile error: `strict_types declaration must be the very first statement in the script`. - It cannot use block mode — `declare(strict_types=1) { ... }` is a compile error. - Its value must be `0` or `1`. - It is **per file**. An included or required file does not inherit it from the file that includes it, and there is no ini directive that turns it on globally. ## What the flag does not touch Strict typing is only defined for scalar declarations. Class and interface types, `array`, `callable`, `iterable`, `object` and `mixed` have no coercion in either mode — a string never becomes an object — so for them the flag changes nothing. It also does not change how operators behave: `"5" + 3` is still `8` in a strict file, and `==` still juggles types. Strict types govern declarations, not expressions. ## Why most modern code turns it on Coercion hides mistakes at the point where they are cheapest to catch. A controller that forgets to validate and convert a request value still "works" in coercive mode because `"4"` quietly becomes `4`; so do `true` becoming `1`, `"1e3"` becoming `1000`, and a fractional float truncated to `int` with only a deprecation notice. Strict mode moves that failure to the call site and makes the declared type a promise about the value, which is also what static analysers assume. The price is that every boundary — request input, which always arrives as strings, CSV fields, some database result sets — needs an explicit, validated conversion before the value is passed on.

  • In PHP, does declare(strict_types=1) make the + operator or == comparisons strict as well?
    No. `strict_types` governs only argument and return values checked against scalar type declarations. Arithmetic still converts numeric strings, so `"5" + 3` is `8`, and `==` still juggles operand types exactly as in a coercive file. To avoid juggling in comparisons you use `===`; to avoid it in arithmetic you convert and validate input before computing.
  • In PHP, can declare(strict_types=1) be placed after a namespace statement or wrapped in a block?
    Neither. It must be the very first statement of the file, before `namespace`; only the opening tag, whitespace, comments and other `declare` statements may precede it, otherwise compilation fails with `strict_types declaration must be the very first statement in the script`. Block mode, `declare(strict_types=1) { ... }`, is also a compile error, and the value must be `0` or `1`.
  • In PHP strict mode, is the float 7.0 accepted by an int parameter?
    No. Strict mode allows only one conversion, `int` to `float`. A float is rejected by an `int` declaration even when it has no fractional part, so `7.0` throws `TypeError`. In coercive mode `7.0` becomes `7` silently, and `7.5` becomes `7` with a PHP 8.1+ deprecation notice about losing precision.

Coercive mode is a cashier who accepts a banknote in the wrong currency and converts it at the till; strict mode is a cashier who takes only the currency on the sign and refuses everything else, with one standing exception: whole coins are accepted where the price has decimals, just as an int is accepted for a float.

saying these in an interview costs you the question

  • Says strict_types is a php.ini setting that can be enabled for the whole application.
  • Thinks coercive mode accepts anything, including "abc" for an int parameter.
  • Believes strict mode also rejects an int passed to a float parameter.
  • Claims strict_types makes == behave like === or stops arithmetic on numeric strings.
  • Expects an included file to inherit strict mode from the file that includes it.
  • Catches the mismatch with catch (Exception $e), not realising TypeError is an Error.