This RFC seeks to address two differences in scalar conversion behavior:
declare(strict_types=1)), string type declarations reject Stringable values even though they have a well-defined string representation.This RFC changes both behaviors:
E_DEPRECATED for fuzzy casts; accept Stringable for string parameters in strict mode.TypeError for fuzzy casts.PHP's type conversion system contains differences between explicit casts and scalar type checks. This RFC addresses two issues:
Issue 1: Fuzzy Casts
PHP's explicit cast operators can discard part of the input when it cannot be fully represented in the target type:
<?php (int) "123abc"; // 123 (discards "abc") (float) "12.34foo"; // 12.34 (discards "foo") (object) 42; // stdClass { scalar: 42 } ?>
Issue 2: Stringable Objects in Strict Mode
Strict mode rejects Stringable objects for string parameters, despite Stringable being an explicit contract for safe string conversion:
<?php declare(strict_types=1); function process(string $value): void { } $obj = new class implements Stringable { public function __toString(): string { return "hello"; } }; process($obj); // TypeError (works in non-strict mode) ?>
For this RFC:
"123", " 42 ", and "1e3".intval()) that succeeds today by discarding information or by performing a conversion that is rejected by the engine's scalar type checks.
Since PHP 7.0, scalar type declarations in the default typing mode reject partial string conversions (process("123abc") throws TypeError), yet explicit casts can truncate ((int) "123abc" returns 123). This difference has several effects:
"19.99corrupted" can be accepted as 19 or 19.99Additionally, strict typing mode is widely enabled by modern coding styles. In practice this can lead to code using explicit casts simply to satisfy type declarations. When those casts are fuzzier than the engine's type checks, “too strict” code can become “too lax”.
Making explicit casts follow the same acceptance rules as scalar type checks reduces the incentive to “cast to make it pass” and encourages either valid coercion or explicit parsing. This reduces the number of conversion rules that need to be remembered and reduces reliance on explicit casts to satisfy scalar type declarations.
In strict typing mode, passing a Stringable object to a string parameter throws TypeError. In the default typing mode, the same call is accepted.
This results in patterns such as:
string|Stringable and casting manually, or(string) $value.
This RFC proposes accepting Stringable in strict mode for string parameters, as part of reducing special cases in conversion behavior.
This also matches the longer-term direction of reducing the gap between typing modes (see the Unify PHP's typing modes gist).
Fuzzy casts emit E_DEPRECATED:
<?php // Valid conversions (unchanged) (int) "123"; // 123 (int) " 42 "; // 42 (whitespace acceptable) (float) "1e3"; // 1000.0 (scientific notation acceptable) // Fuzzy casts (deprecated) (int) "123abc"; // E_DEPRECATED, returns 123 (float) "12.5foo"; // E_DEPRECATED, returns 12.5 (object) 42; // E_DEPRECATED, returns stdClass { scalar: 42 } ?>
Fuzzy casts throw TypeError:
<?php (int) "123abc"; // TypeError: Cannot convert "123abc" to int (object) 42; // TypeError: Cannot convert int to object ?>
This aligns explicit casts with scalar type coercion rules used by the engine in the default (coercive) typing mode.
Strict mode will accept Stringable objects for string parameters, converting them via __toString():
<?php declare(strict_types=1); function greet(string $name): string { return "Hello, $name!"; } $user = new class implements Stringable { public function __toString(): string { return "John"; } }; greet($user); // PHP 8.5: TypeError, PHP 8.6: "Hello, John!" ?>
No deprecation in PHP 8.6; this is a relaxation of strict mode restrictions.
The proposed fuzzy cast rules align with existing validation used by scalar type declarations in the default (coercive) typing mode since PHP 7.0.
| Input | Cast | Current | PHP 8.6 | PHP 9.0 |
|---|---|---|---|---|
"123" | (int) | 123 | 123 | 123 |
" 42 " | (int) | 42 | 42 | 42 |
"123abc" | (int) | 123 | E_DEPRECATED | TypeError |
"12.5foo" | (float) | 12.5 | E_DEPRECATED | TypeError |
"abc" | (int) | 0 | E_DEPRECATED | TypeError |
42 | (object) | stdClass{scalar:42} | E_DEPRECATED | TypeError |
['a'=>1] | (object) | stdClass{a:1} | Unchanged | Unchanged |
"false" | (bool) | true | Unchanged | Unchanged |
| Stringable (non-strict mode) | String parameter | Converts | Converts | — |
| Stringable (strict mode) | String parameter | TypeError | Converts | — |
Note: Boolean casts use truthiness rules, not numeric parsing, they are unaffected. Float-to-int precision loss ((int) 12.5) is covered by a separate RFC.
Affected patterns:
<?php $quantity = (int) $_POST['quantity']; // Deprecated in 8.6 if not a valid numeric string $timeout = (int) $config['timeout']; // Deprecated in 8.6 if not a valid numeric string $wrapped = (object) $someScalar; // Deprecated in 8.6 (use array cast instead) ?>
In practice, these patterns will start emitting E_DEPRECATED in PHP 8.6 and will throw TypeError in PHP 9.0.
Migration options:
<?php // Validate before casting if (!is_numeric($value)) { throw new ValueError("Invalid numeric value: $value"); } $result = (int) $value; // Or use filter functions $result = filter_var($value, FILTER_VALIDATE_INT); // If you intentionally accept formats like "10 items" or "30s", parse them explicitly if (preg_match('/^\s*([+-]?\d+)/', $value, $m)) { $result = (int) $m[1]; } // For scalar-to-object, use array cast $obj = (object) ['value' => 42]; // Instead of (object) 42 ?>
Deprecation notices in PHP 8.6 will identify affected code. Static analysis tools (PHPStan, Psalm) can also detect these patterns.
Code that relies on strict mode rejecting Stringable objects will need adjustment. This is expected to be rare.
settype() will follow the same rules as explicit casts: fuzzy conversions will emit deprecation in PHP 8.6 and throw TypeError in PHP 9.0.
intval() and floatval() are thin wrappers around casts and will behave identically.
E_DEPRECATED for fuzzy casts; accept Stringable for string parameters in strict modeTypeError for fuzzy casts
Testing against the Symfony codebase raised only a few dozen deprecations across components, bundles, and bridges. Symfony doesn't use strict types, so no impact from the Stringable change.
Single yes/no vote requiring 2/3 majority.
Voting opens [TBD] and closes [TBD].