PHP RFC: Deprecate Fuzzy Type Casts and Allow Stringable in Strict Mode
- Version: 1.0
- Date: 2026-01-23
- Author: Alexandre Daubois alexandredaubois@php.net, Nicolas Grekas nicolasgrekas@php.net
- Status: Under discussion
- Target Version: PHP 8.6 (deprecation and Stringable support), PHP 9.0 (TypeError)
- Implementation: https://github.com/alexandre-daubois/php-src/pull/16
Summary
This RFC seeks to address two differences in scalar conversion behavior:
- Explicit casts can perform lossy conversions (“fuzzy casts”), silently discarding part of the input.
- In strict typing mode (
declare(strict_types=1)),stringtype declarations rejectStringablevalues even though they have a well-defined string representation.
This RFC changes both behaviors:
- PHP 8.6: emit
E_DEPRECATEDfor fuzzy casts; acceptStringableforstringparameters in strict mode. - PHP 9.0: throw
TypeErrorfor fuzzy casts.
Introduction
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) ?>
Terminology
For this RFC:
- A numeric string is a string accepted by PHP's scalar type coercion for numeric type declarations in the default (coercive) typing mode. Examples include
"123"," 42 ", and"1e3". - A fuzzy cast is an explicit cast (or cast-like function such as
intval()) that succeeds today by discarding information or by performing a conversion that is rejected by the engine's scalar type checks.
Motivation
Fuzzy Casts
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:
- Data loss: Values like
"19.99corrupted"can be accepted as19or19.99 - Different acceptance rules: Scalar type declarations and explicit casts can accept different inputs
Additionally, 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.
Stringable Objects in Strict Mode
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:
- using unions like
string|Stringableand casting manually, or - requiring callers to cast with
(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).
Proposal
Fuzzy Scalar Casts
Phase 1: PHP 8.6 - Deprecation
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 } ?>
Phase 2: PHP 9.0 - TypeError
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.
Stringable Objects in Strict Mode
PHP 8.6
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.
Scope and Affected Cases
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.
Backward Compatibility Impact
Fuzzy Casts
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.
Stringable in Strict Mode
Code that relies on strict mode rejecting Stringable objects will need adjustment. This is expected to be rare.
Related Functions
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.
Proposed PHP Version
- PHP 8.6: Emit
E_DEPRECATEDfor fuzzy casts; acceptStringablefor string parameters in strict mode - PHP 9.0: Throw
TypeErrorfor fuzzy casts
RFC Impact
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.
Vote
Single yes/no vote requiring 2/3 majority.
Voting opens [TBD] and closes [TBD].
References
- RFC: Null Coercion Consistency: https://wiki.php.net/rfc/null_coercion_consistency
- PHP Type Juggling Documentation: https://www.php.net/manual/en/language.types.type-juggling.php
- PHP Type Declarations: https://www.php.net/manual/en/language.types.declarations.php
- PHP filter_var() Documentation: https://www.php.net/manual/en/function.filter-var.php
- RFC: Deprecate implicit float to int conversions: https://wiki.php.net/rfc/implicit-float-int-deprecate
- Unify PHP's typing modes (context/motivation): https://github.com/Girgias/unify-typing-modes-rfc
- Discussion thread: https://news-web.php.net/php.internals/129884