Strict Types
What Is Strict Typing?
In simple terms: By default, PHP is forgiving: if a function expects an integer and you pass the string '5', PHP quietly converts it to 5. That is convenient, but it can also hide bugs. Strict typing tells PHP to stop converting: values must already have the declared type, or PHP throws a TypeError.
Technically: PHP has two modes for checking scalar type declarations (int, float, string, bool):
| Mode | Behavior | How to Enable |
|---|---|---|
| Coercive (default) | Compatible values are converted to the declared type | Nothing to do |
| Strict | Values must match the declared type exactly (one exception: int is accepted for float) | declare(strict_types=1); as the first statement of a file |
The mode affects function and method calls (arguments), return values, and typed property assignments. Class and interface types are always checked strictly in both modes.
Coercive Mode (Default)
<?php
function add(int $a, int $b): int
{
return $a + $b;
}
var_dump(add('5', '10'));
var_dump(add(5.0, 2));
try {
add('five', 1);
} catch (TypeError $e) {
echo strstr($e->getMessage(), ', called', true) . "\n";
}
Output
int(15)
int(7)
add(): Argument #1 ($a) must be of type int, string given
- The numeric strings
'5'and'10'are converted to integers. - The float
5.0has no fractional part, so it is converted to5. (Passing a float with a fractional part, such as5.5, to anintparameter is deprecated since PHP 8.1.) - Even in coercive mode, values that cannot be converted meaningfully — like
'five'— throw aTypeError.
(The strstr() call only removes the "called in ... on line ..." part of the message, which contains file paths.)
Strict Mode
<?php
declare(strict_types=1);
function add(int $a, int $b): int
{
return $a + $b;
}
function half(float $value): float
{
return $value / 2;
}
var_dump(add(5, 10));
var_dump(half(5)); // int → float is allowed
$calls = [
fn() => add('5', '10'),
fn() => add(5.0, 2),
fn() => strlen(123),
];
foreach ($calls as $call) {
try {
$call();
} catch (TypeError $e) {
echo strstr($e->getMessage() . ', called', ', called', true) . "\n";
}
}
Output
int(15)
float(2.5)
add(): Argument #1 ($a) must be of type int, string given
add(): Argument #1 ($a) must be of type int, float given
strlen(): Argument #1 ($string) must be of type string, int given
How It Works
declare(strict_types=1);must be the very first statement in the file (only the opening<?phptag may come before it).- Calls made from this file are checked strictly:
'5'is a string, not an int, soadd('5', '10')throws. 5.0is a float and is rejected for anintparameter.- Built-in functions are checked strictly too:
strlen(123)throws instead of converting123to'123'. - The only conversion still allowed is int to float (
half(5)), because it never loses information.
Return Types and Properties
In a strict file, return values and property assignments are checked strictly as well:
<?php
declare(strict_types=1);
function price(): int
{
return '20';
}
class Product
{
public int $stock = 0;
}
try {
price();
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
try {
$product = new Product();
$product->stock = '15';
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
Output
price(): Return value must be of type int, string returned
Cannot assign string to property Product::$stock of type int
In coercive mode, both operations would succeed by converting '20' and '15' to integers.
The Mode Belongs to the Calling File
The most important (and most surprising) rule: for arguments, strict typing depends on the file where the call is written, not the file where the function is defined.
<?php
// lib.php — no strict_types declaration
function double(int $n): int
{
return $n * 2;
}
<?php
// app.php
declare(strict_types=1);
require __DIR__ . '/lib.php';
double('4'); // TypeError: the call is in a strict file
<?php
// legacy.php — no declaration
require __DIR__ . '/lib.php';
double('4'); // int(8): the call is in a coercive file
| Check | Mode Determined By |
|---|---|
| Arguments passed to a function | The file containing the call |
| Return values | The file containing the function definition |
| Property assignments | The file containing the assignment |
This design lets strict and non-strict code coexist: a library can be used from strict application code and from older code without changes. In practice, modern projects simply declare strict types in every file.
What Strict Types Do Not Change
strict_types affects only type declarations. Other operations still convert values:
<?php
declare(strict_types=1);
var_dump('5' + 10); // arithmetic still converts numeric strings
var_dump('10' == 10); // loose comparison still converts
var_dump((bool) 'false'); // casting rules are unchanged
if ('0') {
echo "never printed\n"; // truthiness rules are unchanged
} else {
echo "'0' is falsy\n";
}
Output
int(15)
bool(true)
bool(true)
'0' is falsy
Use strict comparisons (===), explicit casts, and validation in addition to strict types.
Why Use Strict Types?
| Benefit | Explanation |
|---|---|
| Bugs surface early | A string ID passed where an int is expected fails immediately, at the call site |
| No silent data changes | '12abc' or true are never quietly turned into numbers |
| Clear contracts | Function signatures mean exactly what they say |
| Better tooling | Static analysis tools (PHPStan, Psalm) and IDEs give more precise feedback |
Working With External Data
Request data ($_GET, $_POST), database results, and CSV files usually arrive as strings. In strict mode, convert and validate them once, at the boundary of your application:
<?php
declare(strict_types=1);
function loadProduct(int $id): string
{
return "Product #$id";
}
$input = ['id' => '42']; // like $_GET
$id = filter_var($input['id'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
if ($id === false) {
exit("Invalid product ID\n");
}
echo loadProduct($id) . "\n";
Output
Product #42
After validation, $id is a real int, and the rest of the code works with correct types.
Common Mistakes
Placing declare After Other Code
Effect: Fatal error: "strict_types declaration must be the very first statement in the script".
Fix: Put declare(strict_types=1); directly after <?php.
Passing Raw Request Strings to Typed Functions
Effect: TypeError in strict mode.
Fix: Validate and convert input at the application boundary.
Assuming Strict Types Affect Comparisons and Arithmetic
Effect: Loose comparisons (==) still convert values.
Fix: Use === and explicit casts.
Expecting the Defining File's Mode to Apply
Effect: A library with strict_types still accepts coerced arguments from non-strict callers.
Fix: Remember that the calling file decides how arguments are checked.
Best Practices
- Add
declare(strict_types=1);to every PHP file in new projects. - Convert and validate external input once, at the edges of the application.
- Declare parameter, return, and property types everywhere.
- Combine strict types with strict comparisons and static analysis.
- Introduce strict types to legacy code file by file, starting with well-tested parts.
Official reference: https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict