Understanding PHP’s strict_types, Type Checking, and Float Coercion
When working with PHP, especially in modern versions like 8.2.x through 8.4.x, many developers stumble upon unexpected behaviors around type-checking. This is especially true when dealing with strict_types
, is_float
, and type coercion. Let’s dive deep into this with an example, breaking down what’s happening behind the scenes and what it means for your code.
The Code: A Simple Example
<?php
declare(strict_types=1);
var_dump(is_float(1));
var_dump(gettype(1));
var_dump(1);
echo "\n";
function f(float $f){
var_dump(is_float($f));
var_dump(gettype($f));
var_dump($f);
}
f(1);
Breaking It Down
1️⃣ Strict Types (declare(strict_types=1)
)
By declaring strict_types=1
, PHP is instructed to enforce strict type-checking when calling functions and methods. This means that type coercion (implicit conversion) is disabled for scalar types (like int
, float
, string
, bool
). If you pass an int
where a float
is expected, PHP should ideally throw a TypeError
. But here’s the twist.
2️⃣ Type Checking with is_float
and gettype
is_float(1)
checks whether the literal1
is a float. Since1
is an integer, it returns false.gettype(1)
returns the type of1
, which is "integer".- When we pass
1
tof(float $f)
, we’d expect a TypeError because we’re passing an integer into a float-typed function withstrict_types=1
. But instead, it silently converts1
to1.0
(a float).
3️⃣ The Reality of PHP’s Float Coercion
Here’s the surprising part: PHP, even with strict_types
, allows implicit conversion of integers to floats. This means:
- Passing
1
tof(float $f)
converts1
(integer) to1.0
(float). is_float($f)
insidef()
returns true.gettype($f)
returns "double" (which is PHP’s internal name forfloat
).- The value of
$f
is float(1).
Why does this happen? PHP considers integers and floats “compatible” in many contexts, even under strict type-checking. This is because floating-point numbers naturally include all integers—an int
like 1
can be precisely represented as a float
(1.0
).
The Output
For PHP versions 8.2.x, 8.3.x, and 8.4.x, the output is:
bool(false)
string(7) "integer"
int(1)
bool(true)
string(6) "double"
float(1)
Why No TypeError?
This behavior highlights an exception to strict_types
. Scalar values that can be safely and precisely converted, such as int
to float
, are allowed. However, passing a float
to a function(int $i)
will throw a TypeError
, because floating-point numbers can’t always be converted to integers without potential loss of precision.
Key Takeaways
✅ strict_types=1
applies to function/method parameters, but not to internal functions like is_float
and gettype
.
✅ PHP treats integers and floats as safely interchangeable when passing to a float
parameter, even with strict types.
✅ gettype
returns "double" for floats, which can be confusing, but it’s PHP’s convention.
✅ Be cautious with implicit type conversions, especially when designing APIs or critical systems where precision matters.
Other Considerations You Might Miss
- Rounding Issues: When converting floats to ints (e.g.,
1.9
toint
), PHP truncates rather than rounds. This is not reversible, unlikeint
tofloat
. - Database Implications: If you’re storing numeric values, always be explicit about types. Databases like MySQL have clear distinctions between
INT
andFLOAT/DOUBLE
, and mismatches can cause subtle bugs. - Interfacing with External APIs: When sending data as JSON or to APIs, be explicit in your serialization.
1
might be sent as aninteger
but expected as afloat
on the other side. - PHP Version Differences: Although your code works the same in 8.2.x to 8.4.x, future versions of PHP might tighten rules or introduce deprecation warnings for implicit coercion.
Finally
This example teaches us that PHP’s type system isn’t always as strict as we think, even with strict_types=1
. Understanding these subtle behaviors is crucial for writing robust, type-safe code. Always consider how your types interact, especially when crossing boundaries like databases, APIs, or strict business logic.
Comments ()