PHP's "Strict" Types That Aren’t So Strict: The Curious Case of Int-to-Float Coercion
When PHP 7 introduced strict types, many developers breathed a sigh of relief. Finally, a way to enforce strict type checking in function arguments and return types! But if you’ve ever played around with strict_types=1
, you may have noticed something peculiar: passing an integer to a function expecting a float doesn’t throw an error. Instead, it just works.
Let’s unpack this.
What is strict_types=1
?
When you write:
declare(strict_types=1);
PHP enables strict type checking for scalar types (int, float, string, bool) in function arguments and return values. Without strict_types
, PHP tries to be “helpful” and performs loose type coercion:
- Passing a string
'123'
to a function expecting anint
will just work. - Passing
'3.14'
to afloat
-typed function? Sure, PHP will quietly convert it.
With strict_types=1
, PHP stops this silent coercion. If you pass a string where an int is expected, it throws a TypeError.
But Then, Why Does int
to float
Still Work?
Here’s the kicker: strict_types=1
doesn’t prevent int-to-float coercion. Even in strict mode, passing 1
(an int) to a function expecting a float
does not throw an error. PHP accepts it and converts the int to float under the hood.
Why?
- PHP’s type system sees int and float as numeric types that are compatible.
- Converting an integer like
1
to1.0
(float) is safe and lossless. - The PHP designers made a deliberate decision: int and float are close enough for strict types to allow automatic conversion.
The Philosophy Behind It
PHP’s type system isn’t designed to be ultra-strict like some other languages (e.g., Rust or TypeScript with strict
mode). Instead, it prioritizes pragmatism and developer convenience.
- Numeric types (int and float) are often interchangeable in mathematical contexts.
- Forcing developers to manually convert
1
to1.0
would add unnecessary friction, with little benefit.
Where strict_types
Does Make a Difference
Let’s look at what strict_types
does enforce strictly:
- Passing a string to a float-typed function? Throws a
TypeError
. - Passing an array to an int-typed function? Also a
TypeError
. - Return values of functions are strictly checked. If your function says it returns a
float
, returning anint
is allowed, but returning astring
will trigger aTypeError
.
What About the Return Types?
You might think, “if a function declares : float
, shouldn’t it require an actual float
return?” PHP allows returning an int
when the function signature specifies float
.
For example:
declare(strict_types=1);
function getFloat(): float {
return 1; // No error
}
This works because of the same int-to-float compatibility. The integer 1
is silently converted to 1.0
.
How to Enforce True Strictness (Even Int vs Float)?
If you want to reject int
values where float
is expected, PHP won’t do it for you automatically. You’ll need to:
- Use static analysis tools (like PHPStan or Psalm) with strict configuration rules to catch these cases during development.
Manually check types at runtime:
function f($f) {
if (!is_float($f)) {
throw new TypeError("Expected float, got " . gettype($f));
}
var_dump($f);
}
f(1); // Will throw TypeError
What Other Languages Do
In stricter languages like:
- Rust:
i32
andf32
are completely separate; explicit casting is mandatory. - Go: You can’t pass an
int
to afloat64
parameter; you must explicitly convert it. - TypeScript: Numeric literals are all
number
, but if you want stricter checks, you must use custom types.
PHP’s design leans towards developer flexibility over strict enforcement, which can be both a blessing and a curse.
Considerations You Might Be Missing
- Silent Errors in Production: If you rely on
strict_types
to prevent unintended type coercion, remember thatint
tofloat
(and vice versa) will still happen silently. This can lead to subtle bugs, especially in financial or precision-sensitive calculations. - Performance: Manually adding runtime checks for stricter typing could impact performance if overused. It’s a trade-off between safety and speed.
- Legacy Code Compatibility: Many PHP projects have legacy code where type declarations were not enforced. Be cautious when introducing
strict_types=1
to an older codebase, as it may not catch all issues.
Finally
PHP’s strict_types=1
is a powerful tool for improving code quality, but it’s not as rigid as you might expect. The int-to-float loophole isn’t a bug – it’s a deliberate design choice to prioritize flexibility.
If you need true, unforgiving strictness, PHP isn’t the language for it. But for many use cases, this compromise strikes a balance between developer convenience and type safety.
Comments ()