# RFC: Deprecate Fuzzy Type Casts and Allow Stringable in Strict Mode

- Status: Under discussion
- Authors: Alexandre Daubois alexandredaubois@php.net, Nicolas Grekas nicolasgrekas@php.net
- Date: 2026-01-23
- Version: 1.0
- Target: PHP 8.6 (deprecation and Stringable support), PHP 9.0 (TypeError)
- Implementation: https://github.com/alexandre-daubois/php-src/pull/16
- Original: https://wiki.php.net/rfc/deprecate-fuzzy-casts
- Page: https://externals.io/rfc/deprecate-fuzzy-casts
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Votes

### Deprecate fuzzy casts and allow Stringable in strict mode in PHP 8.6? (closed)

Yes: 0 · No: 0 · Abstain: 0

## Discussions on the mailing list

- [\[RFC\] Deprecate Fuzzy Type Casts and Allow Stringable in Strict Mode](https://externals.io/message/129884.md) — started on 2026-01-23, 23 messages
- [\[RFC\] Deprecate Fuzzy Type Casts and Allow Stringable in Strict Mode](https://externals.io/message/132277.md) — started on 2026-08-20, 1 messages
- [\[RFC\] Deprecate Fuzzy Type Casts and Allow Stringable in Strict Mode](https://externals.io/message/132310.md) — started on 2026-08-23, 1 messages

---

## 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)`), `string` type declarations reject `Stringable` values even though they have a well-defined string representation.

This RFC changes both behaviors:

- **PHP 8.6**: emit `E_DEPRECATED` for fuzzy casts; accept `Stringable` for `string` parameters in strict mode.
- **PHP 9.0**: throw `TypeError` for 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
<?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
<?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 as `19` or `19.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|Stringable` and 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](https://github.com/Girgias/unify-typing-modes-rfc) gist).

## Proposal

### Fuzzy Scalar Casts

#### Phase 1: PHP 8.6 - Deprecation

Fuzzy casts emit `E_DEPRECATED`:

```php
<?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
<?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
<?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](https://externals.io/rfc/implicit-float-int-deprecate).

## Backward Compatibility Impact

### Fuzzy Casts

Affected patterns:

```php
<?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
<?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_DEPRECATED` for fuzzy casts; accept `Stringable` for string parameters in strict mode
- **PHP 9.0**: Throw `TypeError` for 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.

**Vote: Deprecate fuzzy casts and allow Stringable in strict mode in PHP 8.6?** — Yes: 0 · No: 0 · Abstain: 0 (closed)

Voting opens \[TBD\] and closes \[TBD\].

## References

- RFC: Null Coercion Consistency: [https://wiki.php.net/rfc/null\_coercion\_consistency](https://externals.io/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://externals.io/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>
