In PHP, when a strict_types file declares a function and a file without strict_types calls it, which checking mode applies?
answer
- the calling file decides
- arguments: caller's mode
- return values: declaring file's mode
- callbacks run by built-ins are coercive
- include does not inherit the flag
basics
~20 sCoercive mode applies: for arguments, PHP uses the mode of the file that makes the call, not the file that declares the function. Return values are checked in the declaring file's mode, and callbacks invoked by built-in functions are always coercive.
solid answer
~50 s`strict_types` is a property of the **calling code**. When `report.php` (no declaration) calls `dailyFine("4")` defined in a strict `Fines.php`, the argument is coerced to `4`, because the call is made from a coercive file. Move the same call into a strict file and it throws `TypeError`. **Return values** follow the other file: the `return` statement lives in the declaring file, so its mode decides whether a returned float is accepted for `: int`. A consequence people miss is that a callback run by a built-in such as `array_map()` or `usort()` is called by internal code, which is never strict, so its arguments are coerced even when the closure was written in a strict file. So a library cannot force strict checking on its callers; it only controls how its own code calls others and what it returns.
code
php · 8 lines<?php
// Fines.php
declare(strict_types=1);
function dailyFine(int $days): int
{
return $days * 25;
}go deeper
Remember the short rule: the file that makes the call decides. A strict function called from a non-strict file still coerces its arguments.
Explain the split: arguments follow the caller's file, return values follow the declaring file, and callbacks invoked by built-ins are coerced because internal code is never strict.
Use the rule to plan a migration: converting callers surfaces call-site bugs, converting libraries surfaces return and built-in call bugs, and neither breaks the other side.
Judge where the guarantee must live: a library cannot impose strict callers, so decide how much to lean on declarations, explicit validation and static analysis in consumers' CI.
## The rule in one line `declare(strict_types=1)` does not describe the functions a file defines. It describes the **calls the file makes**. The PHP manual puts it directly: strict typing applies to function calls made from within the file with strict typing enabled, not to the functions declared within that file. This surprises people who expect it to work like an access modifier on the function, and it is why interviewers ask "whose file does the flag apply to?". ## Arguments: the caller's file decides Take a library-fines module that was converted to strict mode, and a legacy report script that was not: ```php <?php // Fines.php declare(strict_types=1); function dailyFine(int $days): int { return $days * 25; } ``` ```php <?php // report.php — no declaration, so coercive require __DIR__ . '/Fines.php'; var_dump(dailyFine("4")); // int(100) ``` The call in `report.php` succeeds: the argument check happens in coercive mode because the calling file is coercive. If a strict file makes the same call, it throws `TypeError: dailyFine(): Argument #1 ($days) must be of type int, string given`. The engine implements this literally. Each compiled function carries a flag recording whether its file was strict, and when a parameter is checked the engine looks at the flag on the **calling frame**. ## Return values: the declaring file decides A return value is checked when the `return` statement runs, and that statement is inside the function, in the declaring file. So return checks use the **declaring file's** mode: | What is checked | Whose mode applies | Example | |---|---|---| | Argument to a user function | the file that makes the call | coercive `report.php` passes `"4"` to strict `dailyFine()`: accepted | | Argument to a built-in function | the file that makes the call | `strlen(123)` in a strict file: `TypeError` | | Return value | the file that declares the function | strict `: int` function returning `round(...)`'s float: `TypeError` | | Argument to a callback run by a built-in | the built-in, which is never strict | `array_map(fn(int $d) => ..., ['1'])` in a strict file: accepted | ## Callbacks run by built-in functions The last row is the trap. When `array_map()`, `usort()`, `array_filter()` or `call_user_func()` invokes a callback, the caller of that callback is internal code, and internal code has no `strict_types` flag. The manual warns about it: function calls from within internal functions are not affected by `strict_types`. ```php <?php declare(strict_types=1); $fines = array_map(fn(int $d): int => $d * 25, ['1', '2']); var_dump($fines); // [25, 50] — '1' and '2' were coerced ``` The closure's own `return` is still checked strictly, because the closure is declared in this strict file. Only its arguments are coerced. ## Includes do not spread the flag `strict_types` is per file in both directions: - A strict file that `require`s a file without the declaration does not make the included file strict. Calls written inside the included file stay coercive. - A coercive file that includes a strict file does not make its own calls strict. There is no ini directive to set a project-wide mode. Every file either carries the declaration or is coercive. ## Why PHP chose the caller The design lets a library adopt strict mode without breaking its users. A library author who adds `declare(strict_types=1)` changes how the library calls PHP and other code, and how its own return values are checked; callers who still pass `"4"` keep working until they opt in themselves. It also means: 1. A library **cannot force** strict checking on the people who call it. If the library needs a guarantee, it declares the parameter type and relies on it being enforced in at least coercive mode, which already rejects values that cannot be converted, such as an array or a non-numeric string for an `int`. 2. Converting a **caller** is what surfaces bugs. When a controller file gains the declaration, every call it makes with a request string where an `int` is declared starts to throw. 3. Converting a **library** mostly surfaces bugs in its own calls to built-ins (`str_repeat('-', $width)` with a string width) and in its return values. ## Common misreadings - "The function is strict, so it rejects strings." Not for coercive callers. - "Strict mode is inherited by included files." It is not. - "A closure written in a strict file is always called strictly." Not when a built-in calls it. - "Return values follow the caller too." They follow the declaring file.
- In PHP, if a strict_types file declares function total(): int and the function returns a float, does a coercive caller change the outcome?No. Return values are checked in the mode of the file that declares the function, because the `return` statement executes there. A strict declaring file rejects a float for `: int` with `TypeError`, whatever mode the caller uses. In a coercive declaring file, a float with no fractional part would be converted to int instead.
- In PHP, why do arguments passed by usort() to a closure written in a strict_types file still get coerced?Because the closure's caller is `usort()`, an internal function, and calls made from internal code are never strict. Argument checks look at the calling frame, and a built-in has no `strict_types` flag. The closure's return value is still checked strictly, since the closure is declared in the strict file.
- In PHP, can a library author force callers to pass exact scalar types to the library's functions?Not through `strict_types`; the caller's file decides the mode for arguments. The declaration still guarantees the type inside the function, because coercive callers either get a converted value or a `TypeError` for non-coercible input. For stricter contracts a library relies on static analysis in its users' CI, or validates and rejects input explicitly.
saying these in an interview costs you the question
- Says a function declared in a strict file rejects numeric strings from any caller.
- Thinks return values are checked in the caller's mode, like arguments.
- Believes a strict file makes every file it includes strict as well.
- Assumes a closure written in a strict file is strict even when array_map calls it.
- Claims a library can enforce strict mode on its callers by declaring strict_types.