PHP's "Strict" Types That Aren’t So Strict: The Curious Case of Int-to-Float Coercion

PHP's "Strict" Types That Aren’t So Strict: The Curious Case of Int-to-Float Coercion
Photo by Diego Rastelli / Unsplash

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 an int will just work.
  • Passing '3.14' to a float-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 to 1.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 to 1.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 an int is allowed, but returning a string will trigger a TypeError.

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:

  1. 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 and f32 are completely separate; explicit casting is mandatory.
  • Go: You can’t pass an int to a float64 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 that int to float (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.

Support Us