Hello everyone,
Before going further with our already under discussion RFC about
nullable casts, Nicolas Grekas and I would like to present this new
RFC about deprecating fuzzy casts, and adding support for Stringable
in string parameters when using strict mode.
RFC: https://wiki.php.net/rfc/deprecate-fuzzy-casts
Thanks,
— Alexandre Daubois
On Fri, Jan 23, 2026 at 11:51 AM Alexandre Daubois <
alex.daubois+php@gmail.com> wrote:
Hello everyone,
Before going further with our already under discussion RFC about
nullable casts, Nicolas Grekas and I would like to present this new
RFC about deprecating fuzzy casts, and adding support for Stringable
in string parameters when using strict mode.RFC: https://wiki.php.net/rfc/deprecate-fuzzy-casts
Thanks,
— Alexandre Daubois
What's the expected behavior of (int) '001' and (int) 1.? I would
expect both to still be valid integers, but I can't tell from this RFC if
those scenarios would be affected. The first scenario is common with
"ZEROFILL" database columns, and the latter is a scenario I recently found
being used to get a valid int from user input if considered number-ish and
switch between whole number vs decimal behavior.
I'm afraid that deprecating and eventually erroring these casts will break
a lot of hard to trace legacy code, where a cast "solved" most problems, or
at least did not crash things. I'm talking about values that come from
automated import systems, go through dozens of layers of code that haven't
been touched since php 5.2 or earlier, and eventually end up in a part
where we do a cast. It was also not unthinkable that these casts were used
because it truncated the invalid parts, and while we could replace this
with a preg_match to extract the number, I don't particularly feel like
going through nearly 5k int casts in this codebase and figure out if it
should change or not.
Does this behavior als affect parameters being passed in a non-strict
fashion? Because then I don't see a realistic migration path within the
next decade for this application.
On the subject of strings, I'm very afraid this will cause hidden issues.
The Stringable value isn't always the value I want from an object, and with
this change my IDE will no longer give an error that it's the wrong type. I
foresee people throwing stringable objects into string parameters and
silently causing the wrong values to be used. Especially when the
__toString implementation is not within our control this will be a
dangerous thing to miss. This will also silently break values being passed
if the __toString implementation changes, which is going to be extremely
hard to trace back like with (string) $object. This change also makes
reviewing code more difficult as I have to know the parameter type,
variable type, and what the __toString function does in order to find out
if it's correct, or another value had to be passed.
I feel like these 2 changes will cause big problems, way more than it's
worth having them be correct. Perhaps my worries are unjustified?
Hi Lynn, thank you for sharing your thoughts.
Le ven. 23 janv. 2026 à 12:44, Lynn kjarli@gmail.com a écrit :
What's the expected behavior of
(int) '001'and(int) 1.? I would expect both to still be valid integers, but I can't tell from this RFC if those scenarios would be affected. The first scenario is common with "ZEROFILL" database columns, and the latter is a scenario I recently found being used to get a valid int from user input if considered number-ish and switch between whole number vs decimal behavior.
They will be indeed valid integers. There is no lossy cast here: "001"
and "1." are valid integers and expected.
I'm afraid that deprecating and eventually erroring these casts will break a lot of hard to trace legacy code, where a cast "solved" most problems, or at least did not crash things. I'm talking about values that come from automated import systems, go through dozens of layers of code that haven't been touched since php 5.2 or earlier, and eventually end up in a part where we do a cast. It was also not unthinkable that these casts were used because it truncated the invalid parts, and while we could replace this with a preg_match to extract the number, I don't particularly feel like going through nearly 5k int casts in this codebase and figure out if it should change or not.
I understand the concern about legacy code. However, if code relies on
(int) "123abc" silently returning 123, this represents a data
integrity issue that would benefit from being made explicit. The
deprecation period exists specifically to help identify and address
these cases.
Does this behavior als affect parameters being passed in a non-strict fashion? Because then I don't see a realistic migration path within the next decade for this application.
This behavior already exists and forbids such casts, see
https://3v4l.org/WQJPv#vnull.
On the subject of strings, I'm very afraid this will cause hidden issues. The Stringable value isn't always the value I want from an object, and with this change my IDE will no longer give an error that it's the wrong type.
This is exactly the intended behavior: Stringable is an explicit
contract informing that an object can be converted to string.
Non-strict mode is aligned with that, whereas strict mode misleadingly
forbids that. Default mode has better casting rules that strict mode
doesn't offer. The whole "non-strict community" uses this rule, making
their code simpler by not having to deal with a union type every time.
I foresee people throwing stringable objects into string parameters and silently causing the wrong values to be used.
Again, Stringable is an explicit contract to define the appropriate
string representation of an object. If this is a problem, the issue
lies in the __toString() implementation.
This will also silently break values being passed if the __toString implementation changes, which is going to be extremely hard to trace back like with
(string) $object.
If any __toString() implementation changes in your object, it will
already break everywhere the object is casted to string or used in a
string context. This is an API breaking issue, which, to me, feels
unrelated to the RFC.
This change also makes reviewing code more difficult as I have to know the parameter type, variable type, and what the __toString function does in order to find out if it's correct, or another value had to be passed.
With string|Stringable, you have to check two types (even if
Stringable is meant to be an explicit contract). With just string
that accepts Stringable, it's simpler and more consistent. The code
will also be less error-prone.
— Alexandre Daubois
I understand the concern about legacy code. However, if code relies on
(int) "123abc"silently returning123, this represents a data
integrity issue that would benefit from being made explicit. The
deprecation period exists specifically to help identify and address
these cases.
Hi, Alexandre
While I agree that this code likely indicates some form of broken logic,
this code still needs a clear migration path, so introducing something
like fuzzy_int() function with the old behavior I'd say is expected.
Second, there should be a time frame where an old and a new way can
coexist so deprecating it in 8.x feels a bit rushed. To me a perfect
migration would be something like
8.6: introduce fuzzy_int or even a family of functions (fuzzy_float?)
9.1: deprecate
10.0: TypeError
--
Anton
Hi Anton,
Le ven. 23 janv. 2026 à 15:59, Anton Smirnov sandfox@sandfox.me a écrit :
While I agree that this code likely indicates some form of broken logic,
this code still needs a clear migration path, so introducing something
like fuzzy_int() function with the old behavior I'd say is expected.
I have concerns about introducing fuzzy_int() as a migration path:
- It would essentially codify the current problematic behavior into a
new API, making it "official" rather than fixing the underlying issue - It adds yet another conversion mechanism to an already complex
landscape (casts, type coercion, filter_var, etc.) - Developers who truly need to extract integers from strings like
"123abc" can use explicit parsing. This makes the intent explicit:
"I'm extracting a number from potentially dirty input", rather than
relying on cast semantics that differ from type validation.
Second, there should be a time frame where an old and a new way can
coexist so deprecating it in 8.x feels a bit rushed. To me a perfect
migration would be something like
I understand the desire for a longer migration period. However, I'd argue that:
- The current timeline (8.6 deprecation then 9.0 TypeError) provides
2-4 years depending on update cycles, which aligns with how we've
handled other significant deprecations - Deprecation warnings in 8.6 give clear signals about what needs fixing
- Static analysis tools (PHPStan, Psalm or any other tool) can detect
these patterns immediately, even before upgrading to 8.6
— Alexandre Daubois
- Developers who truly need to extract integers from strings like
"123abc" can use explicit parsing. This makes the intent explicit:
"I'm extracting a number from potentially dirty input", rather than
relying on cast semantics that differ from type validation.
The existing functions are explicit. They have always had the semantics of "try to extract an integer from this value". They are not "problematic", they are a perfectly reasonable feature for the language to include.
The new functionality needed is "validate that this value is int-like, and only give me an integer if it is".
I don't think we should repurpose widely-used syntax for a different use case. If we want new functionality, with new semantics, we should add new syntax for that.
Rowan Tommins
[IMSoP]
Hello Rowan,
Le ven. 23 janv. 2026 à 18:46, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk a écrit :
I don't think we should repurpose widely-used syntax for a different use case. If we want new functionality, with new semantics, we should add new syntax for that.
The rules/semantics proposed in this RFC are not new. These are
validation rules applied to function arguments (see
https://3v4l.org/WQJPv#vnull). This is nothing new, and it doesn't use
exotic syntax, but rather a standard type declaration. For this
reason, and given the complexity that it would add (even more) to the
language, adding new syntax does not seem to me to be the right
approach: these behaviors already exist with existing syntax that is
well known to everyone.
— Alexandre Daubois
Le ven. 23 janv. 2026 à 18:46, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk a écrit :I don't think we should repurpose widely-used syntax for a different use case. If we want new functionality, with new semantics, we should add new syntax for that.
The rules/semantics proposed in this RFC are not new.
Hi Alexandre,
In my mind, cast / type conversion behaviour can be described in two parts:
The first part is the rules of which input values count as "valid".
In the case of string to integer casts: can it include a leading '+'?
leading space? trailing space? leading zeroes? trailing zero decimal
part? trailing non-zero decimal part? other trailing characters?
PHP has a bunch of different versions of these rules used in different
contexts, and it would certainly be nice to make them all more consistent.
The second part is the behaviour of what happens for a value which is
not valid.
In the case of parameter types and return types, this is to throw a
TypeError. But that's not the only reasonable option - you might just a
boolean (true=valid, false=invalid); or you might want to substitute a
default value.
In the case of the "(int)" syntax, I would argue that the established
behaviour is that an invalid value returns a default value - for an
integer, zero.
In other words, I don't think (int)'hello' is a "fuzzy cast", it is an
operator with clear and useful behaviour: reject 'hello' as input for
integer, and substitute default zero.
The "(int)" syntax has had that behaviour in PHP for over 20 years, and
a huge amount of code has been written explicitly making use of that
fact. For instance, I see this pattern a lot:
$foo = (int)$_GET['foo'];
if ( $foo !== 0 ) { ... }
Changing the behaviour of (int) so that it throws a TypeError for
invalid cases would be a hugely disruptive change, and would replace one
useful feature with a different useful feature, rather than having good
syntax for both.
Regards,
--
Rowan Tommins
[IMSoP]
Hello Rowan, Alexandre, and all :)
On Tue, Jan 27, 2026 at 1:45 AM Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
Le ven. 23 janv. 2026 à 18:46, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk a écrit :I don't think we should repurpose widely-used syntax for a different use case. If we want new functionality, with new semantics, we should add new syntax for that.
The rules/semantics proposed in this RFC are not new.
Hi Alexandre,
In my mind, cast / type conversion behaviour can be described in two parts:
The first part is the rules of which input values count as "valid".
In the case of string to integer casts: can it include a leading '+'? leading space? trailing space? leading zeroes? trailing zero decimal part? trailing non-zero decimal part? other trailing characters?
PHP has a bunch of different versions of these rules used in different contexts, and it would certainly be nice to make them all more consistent.
I would even say it is almost a must at this stage. The introduction
of declare strict_type somehow helps a bit in the context where it is
used, but overall, the cognitive weight of dealing with the
casts/conversions happens is beyond any level that could make sense,
no matter how I look at it. And to anyone working in the engine,
runtime parts can triple that cognitive weight to carry.
In the case of parameter types and return types, this is to throw a TypeError. But that's not the only reasonable option - you might just a boolean (true=valid, false=invalid); or you might want to substitute a default value.
In the case of the "(int)" syntax, I would argue that the established behaviour is that an invalid value returns a default value - for an integer, zero.
The implicit cast is the only one I would not change, if one chooses
it, we can assume the use case is clear and the person knows what's
happening. The classic example you gave with a _GET expected to be an
int is one of them. And if not, well, we can't prevent anyone from
shooting themselves in the knee(s).
It has been mentioned in various recent RFCs that one or another
operations, conversions, casts, syntax is related to another similar
but should be separated RFCs. I think for a few cases, it is the case.
But operations like the ones discussed here, or anything related to
automatic casts/conversions, may end in a way better state if they are
handled once and together.
We have different paradigms in the language, and communities adopt
different ones. A growing part of our users, do explicitly decide to
go full typed, going beyond what php does, to avoid all these
pitfalls. I do think that the amount of projects going this way will
increase and that's something PHP should not ignore. Maybe it is a
good time to have an actual mind-friendly, I would say, sane, typing
mode across the board in an actual strong type mode for the/a next php
major version?
Best,
Pierre
On Fri, Jan 23, 2026, 15:17 Alexandre Daubois alex.daubois+php@gmail.com
wrote:
Hi Lynn, thank you for sharing your thoughts.
Le ven. 23 janv. 2026 à 12:44, Lynn kjarli@gmail.com a écrit :
What's the expected behavior of
(int) '001'and(int) 1.? I would
expect both to still be valid integers, but I can't tell from this RFC if
those scenarios would be affected. The first scenario is common with
"ZEROFILL" database columns, and the latter is a scenario I recently found
being used to get a valid int from user input if considered number-ish and
switch between whole number vs decimal behavior.They will be indeed valid integers. There is no lossy cast here: "001"
and "1." are valid integers and expected.
According to whose definition of "lossy" is that not a lossy conversion?
The only non lossy conversion is when (string) (int) $value === $value
Everything else is lossy. The number of zeroes in front is information,
so is the whitespace around the number.
Your interpretation of lossy seems completely arbitrary to me.
My opinion is that the BC break of this proposal in its current form would
be insane and unreasonable.
Hello Giovanni,
Le ven. 23 janv. 2026 à 18:33, Giovanni Giacobbi
giovanni@giacobbi.net a écrit :
Your interpretation of lossy seems completely arbitrary to me.
As stated in the RFC: "The proposed fuzzy cast rules align with
existing validation". None of the rules stated in the RFC are
arbitrary, it is an application of what already exists in the core
with the validation of function arguments. See
https://3v4l.org/WQJPv#vnull for example.
However, we agree this deserves to be more emphasized in the RFC. I'll
update in this regard.
— Alexandre Daubois
On Fri, Jan 23, 2026 at 3:17 PM Alexandre Daubois <
alex.daubois+php@gmail.com> wrote:
I understand the concern about legacy code. However, if code relies on
(int) "123abc"silently returning123, this represents a data
integrity issue that would benefit from being made explicit. The
deprecation period exists specifically to help identify and address
these cases.Does this behavior als affect parameters being passed in a non-strict
fashion? Because then I don't see a realistic migration path within the
next decade for this application.This behavior already exists and forbids such casts, see
https://3v4l.org/WQJPv#vnull.
Thanks for your reply!
I've given it a bit more thought and while I 100% agree with the reasons
given, I'm still afraid this change is too big of an unknown break. I do
not have voting power, so all I can give is my opinion.
As an alternative, what about this? It could be the best of both worlds
where the new behavior is explicit:
$var = (int) '123test'; // still works, nothing changes, becomes an `(int)
123`
int $var = '123test'; // follows the proposed rules, errors because it's
not a value that's a valid integer
int $var = '123'; // works because it's a valid integer format, becomes
(int) 123
int $var = (int) '123test'; // becomes (int) 123, existing behavior
This way nothing breaks and the casting remains what it is, but we can
still enforce correct types. I know the 3rd line might be controversial,
but it makes it an opt-in instead of opt-out feature and errors can be
thrown as of day 1.
Bonus:
int|null $var = '123test'; // could result in `null` instead of throwing,
similar to the Enum tryFrom.
// could enable
MyEnum $foo = '123test';
MyEnum|null $bar = '123test';
// translates to
$foo = MyEnum::from('123test');
$bar = MyEnum::tryFrom('123test');
from and tryFrom could be taken as base for custom cast functions, but
that's probably a whole different can of worms.
I understand the concern about legacy code. However, if code relies on
(int) "123abc"silently returning123, this represents a data
integrity issue that would benefit from being made explicit. The
deprecation period exists specifically to help identify and address
these cases.Does this behavior als affect parameters being passed in a non-strict fashion? Because then I don't see a realistic migration path within the next decade for this application.
This behavior already exists and forbids such casts, see
https://3v4l.org/WQJPv#vnull.Thanks for your reply!
I've given it a bit more thought and while I 100% agree with the reasons given, I'm still afraid this change is too big of an unknown break. I do not have voting power, so all I can give is my opinion.
As an alternative, what about this? It could be the best of both worlds where the new behavior is explicit:
$var = (int) '123test'; // still works, nothing changes, becomes an `(int) 123` int $var = '123test'; // follows the proposed rules, errors because it's not a value that's a valid integer int $var = '123'; // works because it's a valid integer format, becomes (int) 123 int $var = (int) '123test'; // becomes (int) 123, existing behaviorThis way nothing breaks and the casting remains what it is, but we can still enforce correct types. I know the 3rd line might be controversial, but it makes it an opt-in instead of opt-out feature and errors can be thrown as of day 1.
Bonus:
int|null $var = '123test'; // could result in `null` instead of throwing, similar to the Enum tryFrom. // could enable MyEnum $foo = '123test'; MyEnum|null $bar = '123test'; // translates to $foo = MyEnum::from('123test'); $bar = MyEnum::tryFrom('123test');
fromandtryFromcould be taken as base for custom cast functions, but that's probably a whole different can of worms.
I kinda like this idea, but it would be nice to follow the same rules we have with function calls, thus
int|null $var = "123test"; (https://3v4l.org/C0uaK/rfc#vgit.master)
would be a TypeError. Less special cases means less bugs and less things to remember.
— Rob
I kinda like this idea, but it would be nice to follow the same rules
we have with function calls, thusint|null $var = "123test"; (https://3v4l.org/C0uaK/rfc#vgit.master)
This comes back to my point about the two-part definition.
You are suggesting that "int|null" should only affect the input
interpretation: "allow null as a valid input, but error on invalid input".
Lynn is suggesting "int|null" should also affect the output
behaviour: "use null as the default output for any invalid input".
This is why I keep thinking about a family of cast operations, where
the input interpretation is standardised, but the output behaviour
is selectable by the user.
My current best thought is a generic-style syntax:
must_cast<int>( 'hello' ) // TypeError
try_cast<int>( 'hello', 0 ) // int(0)
can_cast<int>( 'hello' ) // bool(false)
must_cast<int>( null ) // TypeError
try_cast<int>( null, 0 ) // int(0)
can_cast<int>( null ) // bool(false)
must_cast<int|null>( 'hello' ) // TypeError
try_cast<int|null>( 'hello', null ) // null
can_cast<int|null>( 'hello' ) // bool(false)
must_cast<int|null>( null ) // null
try_cast<int|null>( null, null ) // null
can_cast<int|null>( null ) // bool(true)
--
Rowan Tommins
[IMSoP]
I'm afraid that deprecating and eventually erroring these casts will break a lot of hard to trace legacy code, where a cast "solved" most problems, or at least did not crash things. I'm talking about values that come from automated import systems, go through dozens of layers of code that haven't been touched since php 5.2 or earlier, and eventually
end up in a part where we do a cast. It was also not unthinkable that these casts were used because it truncated the invalid parts, and while we could replace this with a preg_match to extract the number, I don't particularly feel like going through nearly 5k int casts in this codebase and figure out if it should change or not.
I share this exact same concern for the same reasons, particularly regarding the int cast.
-Jeff
Hello everyone,
Before going further with our already under discussion RFC about
nullable casts, Nicolas Grekas and I would like to present this new
RFC about deprecating fuzzy casts, and adding support for Stringable
in string parameters when using strict mode.
Hi Alexandre,
I am strongly against the Stringable part of this.
It would mean users who have explicitly asked for no type coercion would get silent type coercion. It would mean that a string parameter in mode 1 would still reject an integer 42, but would accept new BcMath\Number(42). I can't see how that makes sense.
My thoughts on "fuzzy casts" are more nuanced.
I do think PHP needs some functions or syntax for "validating" casts, but I'm not convinced they should replace our existing casts.
Not only would it break the huge amount of code out there which assumes that a cast will always return a result rather than an error, I think that is a perfectly valid use case. It is often useful to say "take this string, and return an integer if it's int-like, or a default value of 0 if not". Although they're not perfect, that's roughly what our existing casts provide.
I can see an argument for making (int)"123hello" evaluate to 0, rather than 123; but even that would be a confusing BC break for a lot of applications.
As I've said elsewhere, I think the right approach to this is to design a new set of cast operations, with a more extendable syntax. They can have more clearly defined rules for "int-like string" etc, and different modes/variants for use cases like "error on failure" vs "default value on failure".
Rowan Tommins
[IMSoP]
Hi Rowan,
Le ven. 23 janv. 2026 à 15:55, Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk a écrit :
I am strongly against the Stringable part of this.
It would mean users who have explicitly asked for no type coercion would get silent type coercion. It would mean that a string parameter in mode 1 would still reject an integer 42, but would accept new BcMath\Number(42). I can't see how that makes sense.
Stringable is an explicit contract that an object can be safely
converted to a string. If an application breaks because of such a
cast, this means the implementation of the __toString() method is
buggy and does not respect the contract. Currently, non-strict and
strict modes differ on Stringable handling, and I'm not aware of this
non-strict behavior causing bugs in production applications - though
I'd genuinely welcome counterexamples if you're aware of any.
Regarding your BcMath\Number example: I see the distinction you're
making, but I think the key difference is the explicit opt-in. An
integer doesn't choose to be convertible to string - that's a built-in
coercion. A class implementing Stringable explicitly declares "I have
a canonical string representation". The parallel would be: strict mode
rejects int -> string coercion (implicit), but should accept
Stringable -> string because it's an explicit contract, much like it
accepts objects implementing ArrayAccess for array operations in
certain contexts.
As mentioned by Larry, Stringable changes would deserve another RFC.
We're considering creating a second RFC just for the Stringable part,
so I propose to continue this discussion in the coming thread if that
suits you, so we can keep things organised.
As I've said elsewhere, I think the right approach to this is to design a new set of cast operations, with a more extendable syntax. They can have more clearly defined rules for "int-like string" etc, and different modes/variants for use cases like "error on failure" vs "default value on failure".
Our goal is to make things more consistent and simple, and I'm afraid
creating a new set of casts would just overcomplexify things. The
fundamental problem would be shifted to somewhere else, not solved.
Adding new cast operators would create a third set of rules alongside
type coercion and type declarations. Aligning existing casts with the
type checking semantics that have been standard since PHP 7.0 seems
simpler and more maintainable long-term.
— Alexandre Daubois
Hello everyone,
Before going further with our already under discussion RFC about
nullable casts, Nicolas Grekas and I would like to present this new
RFC about deprecating fuzzy casts, and adding support for Stringable
in string parameters when using strict mode.RFC: https://wiki.php.net/rfc/deprecate-fuzzy-casts
Thanks,
— Alexandre Daubois
-
This really feels like two separate RFCs. I'd rather see them as two separate RFCs than one mixed RFC.
-
I tend to agree with other commenters that we should leave the existing casts alone, and instead add a new, more reasonable type conversion operators (for some definition of reasonable).
-
Unlike the other commenters, I am 100% in favor of allowing
stringto accept Stringable in strict mode. Strict mode basically broke Stringable, and that's been an annoyance for a long time. I know some feel that Stringable is always and forever a bad thing, amen, but I don't agree. It has ample valid use cases, even if it can be abused. I want my Stringable objects back! :-)
--Larry Garfield
- Unlike the other commenters, I am 100% in favor of allowing
stringto accept Stringable in strict mode. Strict mode basically broke Stringable, and that's been an annoyance for a long time. I know some feel that Stringable is always and forever a bad thing, amen, but I don't agree. It has ample valid use cases, even if it can be abused. I want my Stringable objects back! :-)
I genuinely don't understand the reasoning here. If you want type coercion, why are you running in strict_types=1 mode? Why would you want to allow objects to be coerced, but not other values with unique, well-defined, string representations, like integers?
Also, what do you mean by "strict mode broke Stringable"? Do you mean there was some code you wrote in PHP 5 that relied on implicit object-to-string conversion? It certainly wasn't called "Stringable" at the time; and it would have to have been internal functions, because users couldn't define "string" parameters.
Rowan Tommins
[IMSoP]
Hi Larry,
Le ven. 23 janv. 2026 à 17:06, Larry Garfield larry@garfieldtech.com a écrit :
- This really feels like two separate RFCs. I'd rather see them as two separate RFCs than one mixed RFC.
Indeed, we'll be working on separating the RFCs. Let's consider this
one as the fuzzy casts one, and the soon-to-be-created RFC will only
talk about Stringable.
- I tend to agree with other commenters that we should leave the existing casts alone, and instead add a new, more reasonable type conversion operators (for some definition of reasonable).
I shared my point of view about this solution in my responses to
others. Don't hesitate if something's unclear or bothers you!
- Unlike the other commenters, I am 100% in favor of allowing
stringto accept Stringable in strict mode. Strict mode basically broke Stringable, and that's been an annoyance for a long time. I know some feel that Stringable is always and forever a bad thing, amen, but I don't agree. It has ample valid use cases, even if it can be abused. I want my Stringable objects back! :-)
Let's work for that!
— Alexandre Daubois
Hello everyone,
Before going further with our already under discussion RFC about
nullable casts, Nicolas Grekas and I would like to present this new
RFC about deprecating fuzzy casts, and adding support for Stringable
in string parameters when using strict mode.RFC: https://wiki.php.net/rfc/deprecate-fuzzy-casts
Thanks,
— Alexandre Daubois
Hello Alexandre,
I know we had some preliminary offline discussions about this RFC,
but having the final document in front of me, I feel you are mixing 3 different RFCs in one.
And none of them are fully fleshed out within this single document.
I see that you've already replied to Larry that you're going to spin out the Stringable change into its own RFC, which I think is good.
However, even when just talking about the casting operators the discussion is muddled.
You are grouping the transformation between scalar types (string, int, float, bool) and how they can truncate/coerce data with a possible loss of information on one hand,
and talking about the (object) cast on the other hand, which is a cast to a "structured" type.
While I agree that the (object) cast is definitely strange on scalar types, I also don't see why keeping (object) for casting arrays makes that much more sense?
And why do you not talk about the (array) cast at all?
Both of these casts are two sides of the same coin, and IMHO should both be deprecated outright, due to their confusing semantics.
Yes, I know Symfony uses the (array) cast a lot, but the get_mangled_object_vars() function has existed long enough for it to replace the usage of (array) on objects.
And even the aspect about the fuzzy casts feels incomplete to me, you do not talk about the (bool) or (string) cast (and relevant boolval()/strval() functions) are these affected by your RFC change?
Because casting a float to a string depends on the precision INI setting, see: https://3v4l.org/5GJWh
Which feels like a fuzzy cast to me, although this is just PHP's usual float to string coercion semantics.
You also mention that the (int) 12.5 fuzzy cast is covered by my "Deprecate implicit non-integer-compatible float to int conversions" RFC [1],
but this is not the case. See: https://3v4l.org/3AZOd#vnull
As I specifically did not affect the behaviour of the casting operators in this RFC, nor in my prior "Saner numeric strings" RFC.
The reason being is that many people were concerned about the impact on legacy codebase, or codebase that utilized the long-standing truncation behaviour.
Moreover, at no point do you describe if NULL would still be accepted for type casting.
Because if you do, then it would not follow the behaviour of scalar type checks which rejects NULL.
And what would happen to these scalar casts if a different version of the "Deprecate type juggling to and from bool type within the function type juggling context" RFC [3] passes?
Overall, it feels this proposal is trying to work around the "mindless" usage of strict_types, to which my opinion and solution is to just remove it.
I very much understand the appeal of trying to fix the problem at the root, but I'm not sure if this would pass,
considering that adding various "parse_T()" functions that exposes the ZPP behaviour might just be more sensible.
Best regards,
Gina P. Banyard
[1] https://wiki.php.net/rfc/implicit-float-int-deprecate
[2] https://wiki.php.net/rfc/saner-numeric-strings
[3] https://wiki.php.net/rfc/deprecate-function-bool-type-juggling
Hello everyone,
Before going further with our already under discussion RFC about
nullable casts, Nicolas Grekas and I would like to present this new
RFC about deprecating fuzzy casts, and adding support for Stringable
in string parameters when using strict mode.RFC: https://wiki.php.net/rfc/deprecate-fuzzy-casts
Thanks,
— Alexandre Daubois
This is interesting. As a user of non-strict mode, I rarely need casts. That being said, after working on tons of strict-mode code bases, this is exactly the footgun I use to show how strict-mode is actually bad for their code base: I remove the casts and turn off strict mode, and the application actually crashes because it was silently handling bad inputs all over the place.
Here's a pretty good example I saw a few weeks ago:
$newBalance = (int)$walletBalance;
$wallet->store($newBalance);
/* ... later ... */
$log->append($walletBalance); // which takes a string
Now, if this string is something like "1000; drop table money" and there isn't proper injection protection... you're in for a bad day.
Without strict mode:
$wallet->store($walletBalance); // TypeError: cannot coerce string to int
That being said, I think the more proper solution is to simply remove strict mode instead of making casts stricter. Why? Sometimes you actually do need to use a cast to force a specific type, and you want to use the loss of information. Like I’m pretty sure casting a float to an integer is faster than calling floor(), and you end up with an integer, not a float. Secondly, to properly use strict-mode, you have to religiously cast everything, which hides errors that’d otherwise appear. But getting rid of lossy casting seems like trying to apply a tourniquet to the wrong limb.
— Rob
Hi Rob,
On Tue, Jan 27, 2026, 4:41 PM Rob
That being said, I think the more proper solution is to simply remove
strict mode instead of making casts stricter.
Why I use "strong" and not strict. no cast magic, new operators for
explicit cast as type, etc.
everything else pretty much ends as less "magic" and does not remove the
issues. Php was great in the early days, get some numerical string, do some
math's, response and everyone was happy to get things done in a few lines
of code. That time is somehow over (can still be done but not without some
sanity first :)
best,
Pierre
@pierrejoye | http://www.libgd.org