PHP's type conversion system contains inconsistencies that make behavior unpredictable across different mechanisms. This RFC addresses two related issues:
Issue 1: Fuzzy Casts
PHP's explicit type casting operators ((int), (float), (bool), (string), (array), (object)) have historically been maximally permissive, performing lossy conversions when the input value cannot be fully represented in the target type. Fuzzy casts are conversions where information is lost or transformed in unexpected ways:
<?php // Partial string conversions - only a portion of the string is used (int) "123abc"; // Returns 123 (discards "abc") (float) "12.34foo"; // Returns 12.34 (discards "foo") // Scalar to object casts - creates arbitrary structure (object) 42; // Returns stdClass Object ( [scalar] => 42 ) (object) "hello"; // Returns stdClass Object ( [scalar] => "hello" ) ?>
Issue 2: Stringable Objects in Strict Mode
Another inconsistency exists with Stringable objects and string parameters in strict mode:
<?php // Non-strict mode function process(string $value): void { } $obj = new class implements Stringable { public function __toString(): string { return "hello"; } }; process($obj); // Works // Strict mode declare(strict_types=1); process($obj); // TypeError: Argument must be of type string, object given ?>
The Stringable interface was introduced in PHP 8.0 to explicitly declare that an object can be safely converted to a string. Strict mode rejecting this explicit contract is inconsistent with its purpose.
Proposed Changes
This RFC proposes:
1. Deprecate fuzzy casts in PHP 8.6 and make them throw TypeError in PHP 9.0, aligning explicit cast behavior with the validation rules already used for typed function parameters in non-strict mode
2. Allow Stringable objects for string parameters in strict mode starting in PHP 9.0, respecting the explicit conversion contract
Important: For fuzzy casts, this RFC specifically targets partial string conversions and scalar-to-object casts. Well-formed numeric strings (e.g., "123", "12.5") will continue to work as expected.
Since PHP 7.0, the language has supported type declarations and TypeError exceptions, yet explicit cast operators have retained their legacy permissive behavior from PHP 3/4. This creates a significant inconsistency:
<?php // Function parameter - rejects fuzzy conversions function process(int $value): void { } process("123abc"); // TypeError // Explicit cast - silently truncates $result = (int) "123abc"; // 123 (no error) ?>
This inconsistency is problematic because:
"19.99corrupted" are silently accepted, masking bugs
The Stringable interface provides an explicit contract for safe string conversion. However, strict mode rejects these explicitly declared safe conversions (see Introduction for example).
This is inconsistent because:
This RFC proposes two changes to improve type conversion consistency:
In PHP 8.6, fuzzy casts will emit an E_DEPRECATED notice when a string value can only be partially converted to the target scalar type, or when a scalar is cast to (object):
<?php // PHP 8.6 behavior // Valid conversions - no change (int) "123"; // 123 (no notice) (int) " 42 "; // 42 (whitespace is acceptable) (float) "12.5"; // 12.5 (no notice) (float) "1e3"; // 1000.0 (scientific notation is acceptable) // Fuzzy casts - emit E_DEPRECATED (int) "123abc"; // Deprecated: Partial string conversion from "123abc" to int // Returns: 123 (float) "12.5foo"; // Deprecated: Partial string conversion from "12.5foo" to float // Returns: 12.5 // Scalar to object casts - emit E_DEPRECATED (fuzzy cast) (object) 42; // Deprecated: Conversion from int to object is deprecated // Returns: stdClass Object ( [scalar] => 42 ) (object) "hello"; // Deprecated: Conversion from string to object is deprecated // Returns: stdClass Object ( [scalar] => "hello" ) ?>
The deprecation notice will clearly indicate the original value, the target type, and the nature of the problematic conversion, and that these conversions will become errors in PHP 9.0.
In PHP 9.0, fuzzy casts will throw a TypeError instead of performing conversion:
<?php // PHP 9.0 behavior // Valid conversions - no change (int) "123"; // 123 (float) "12.5"; // 12.5 // Fuzzy casts - throw TypeError (int) "123abc"; // TypeError: Cannot convert string "123abc" to int: trailing data (float) "12.5foo"; // TypeError: Cannot convert string "12.5foo" to float: trailing data (object) 42; // TypeError: Cannot convert int to object (object) "hello"; // TypeError: Cannot convert string to object ?>
This aligns explicit cast behavior with the validation rules already used for typed function parameters in non-strict mode.
No changes to Stringable behavior. Strict mode continues to throw TypeError when Stringable objects are passed to string parameters.
Strict mode will accept Stringable objects for string parameters, converting them via __toString() just like in non-strict mode:
<?php declare(strict_types=1); function greet(string $name): string { return "Hello, $name!"; } $user = new class implements Stringable { public function __toString(): string { return "John"; } }; // PHP 8.x: TypeError // PHP 9.0: Works - returns "Hello, John!" greet($user); ?>
Important: Apart from Stringable, the proposed behavior aligns with the existing validation rules used by typed function parameters in non-strict mode. This RFC does not introduce new rules—it applies the same well-established rules that have been used for function parameters since PHP 7.0.
A fuzzy cast occurs when:
int, float, string, or bool) is cast to (object), creating a stdClass with an arbitrary scalar property| Input Type | Cast | Current Behavior | PHP 8.6 | PHP 9.0 |
|---|---|---|---|---|
| String (fully numeric) | (int) "123" | 123 | 123 | 123 |
| String (whitespace + numeric) | (int) " 42 " | 42 | 42 | 42 |
| String (fuzzy) | (int) "123abc" | 123 | E_DEPRECATED + 123 | TypeError |
| String (fuzzy) | (float) "12.5foo" | 12.5 | E_DEPRECATED + 12.5 | TypeError |
| String (non-numeric) | (int) "abc" | 0 | E_DEPRECATED + 0 | TypeError |
| Scalar (fuzzy) | (object) 42 | stdClass with scalar property | E_DEPRECATED + stdClass | TypeError |
| Scalar (fuzzy) | (object) "hello" | stdClass with scalar property | E_DEPRECATED + stdClass | TypeError |
| Array | (object) ['a' => 1] | stdClass with properties | stdClass (not affected) | stdClass (not affected) |
| Non-string | (int) 12.5 | 12 | 12 (E_DEPRECATED for precision loss) | 12 (may throw in future) |
| Boolean string | (bool) "false" | true | true (not affected) | true (not affected) |
| Stringable (non-strict) | function(string $s) with Stringable | Accepts (converts via __toString) | Accepts (converts) | Accepts (converts) |
| Stringable (strict) | function(string $s) with Stringable in strict mode | TypeError | TypeError | Accepts (converts via __toString) |
Note on boolean casts: Boolean casts are not affected by this RFC. They use truthiness rules rather than numeric parsing, so they evaluate the entire value rather than performing partial conversion.
Developers are encouraged to use explicit comparisons for clarity:
<?php // Instead of this: $enabled = (bool) $value; // Prefer explicit comparison: $enabled = $value !== "" && $value !== "0" && $value !== false && $value !== null; // Or for boolean strings: $enabled = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); ?>
This RFC introduces backward incompatible changes that will affect existing code relying on fuzzy cast behavior and implicit use of Stringable for string parameters in strict mode.
Code that currently relies on fuzzy casts will need to be updated. Common patterns that will be affected include:
<?php // Database/API responses with trailing whitespace or corruption $age = (int) $row['age']; // If $row['age'] is "25\0" or "25 years" // User input with mixed content $quantity = (int) $_POST['quantity']; // If input is "10 items" // Configuration values with units $timeout = (int) $config['timeout']; // If config is "30s" or "30 seconds" // Scalar to object wrapping $wrapped = (object) $someScalar; // Creates stdClass with 'scalar' property ?>
Developers have several options to migrate away from fuzzy casts:
Option 1: Validate before casting
<?php // Explicit validation using is_numeric() if (!is_numeric($value)) { throw new ValueError("Invalid numeric value: $value"); } $result = (int) $value; ?>
Option 2: Use filter functions
<?php // Validate and convert $result = filter_var($value, FILTER_VALIDATE_INT); if ($result === false) { throw new ValueError("Invalid integer: $value"); } // Sanitize before converting (explicit intent) $sanitized = filter_var($value, FILTER_SANITIZE_NUMBER_INT); $result = (int) $sanitized; ?>
Option 3: For scalar to object casts
Create a proper object structure or use an array:
<?php // Instead of: $obj = (object) 42; // Option A: Create proper object structure $obj = new stdClass(); $obj->value = 42; // Option B: Or create a proper class class ValueWrapper { public function __construct(public mixed $value) {} } $obj = new ValueWrapper(42); // Option C: Use an array if you need a key-value structure $data = ['value' => 42]; // Or if object is required: $obj = (object) ['value' => 42]; // Option D: Question whether object wrapping is necessary // Often, the scalar value can be used directly processValue(42); // Instead of processObject((object) 42) ?>
To help developers identify fuzzy casts in their codebase:
(int), (float), (object) applied to variables that may contain malformed data
When using strict mode, code relying on the fact that providing a Stringable to a string parameter without the (string) explicit cast will throw won't be working as expected.
Both changes in this RFC address the same fundamental issue: PHP's type conversion behavior is inconsistent across different mechanisms.
Fuzzy casts are more permissive than function parameter validation, silently accepting malformed data that would be rejected as function arguments. This violates the principle of least surprise and can mask bugs.
Stringable in strict mode demonstrates the opposite problem: strict mode rejects explicitly declared safe conversions. The Stringable interface exists specifically to declare that an object can be safely converted to a string, yet strict mode ignores this contract.
Together, these changes move PHP toward consistent, predictable type conversion.
This RFC uses TypeError for consistency with function parameter type checking, which already uses TypeError for invalid conversions.
E_DEPRECATED for fuzzy casts (Stringable behavior unchanged)TypeError for fuzzy casts, accept Stringable objects in strict mode for string parametersA first patch has been created to php-src to test the new rules against the Symfony codebase. Only a few dozens of deprecations were raised across the whole codebase of components, built-in bundles and bridges.
Symfony doesn't use strict types in its codebase, so no impact is expected from the Stringable change in this project.
This is a simple yes/no vote requiring a 2/3 majority.
Vote will start on [TBD] and end on [TBD].