When starting a client's project, we had to decide which PHPStan level we would use. We went for the highest level, which means the most strict checks. We reasoned that it would be better to start strict and lower it when needed.
PHPStan defines level 9, the highest level, as follows:
Enabling this rule makes sense when you use strict type hinting on function parameters. A function that returns a mixed variable might return an integer, and passing it to a function that expects a string would cause a crash during execution. So it would be better to know this upfront rather than when the code is running in production and you're looking at the Sentry issue.
The first attempt to fix this would be casting the mixed value to a string:
someFunction((string) $someMixedValue)
But alas, PHPStan now reports that a mixed value cannot be cast to a string.
The solution is to use PHP's strval()
function. This function is documented as follows:
function strval(mixed $value): string {}
We pass it a mixed value and get a string back. This makes PHPStan happy; it ensures type safety, so everyone should be satisfied.
But then came this PR on the PHPStan repository. The documentation on the strval()
function is wrong. When you pass it as stdClass
, it gives a fatal error. The change in PHPStan is because it only applies to the highest level. You probably want to know about these potential issues if you enable this.
So, I had to figure out potential options to update to the latest version and fix those newly reported issues. It would be possible to add a /** @phpstan-ignore-next-line */
above the offending line, but that kind of defeats the purpose of using PHPStan and could suppress other issues.
Another option would be to add a docblock that declares the variable:
/** @var string $iAmAStringPinkySwear */
This makes PHPStan happy, but it does not protect against potential issues. Adding docblocks everywhere to type hint a variable also makes the code bulkier than it needs to be.
A last option is to have some function or service that mimics the behavior of strval
without lying.
String::fromMixed(mixed $mixed): string
Internally, this function could use strval
but ensure it does not get unsupported types. This would fix everything. However, a decision needs to be made on the string representation of a callable. Thinking about that decision explains why the core php function does not make a decision. It could also throw an exception, but then it would just be a useless wrapper around the strval
function.
I ended up lowering the strictness level of PHPStan from 9 to 8. Writing the service that perfectly casts a mixed variable to a string feels like writing a patch for a PHP issue and over-engineering the solution. Moreover, it would also need to be done on intval
and similar functions because those have similar problems.
So what do you think? Should PHPStan lower its principles or have a separate strictness level for total mixed-type safety? Are there other solutions that I missed? I think PHP should just support casting callables to a string in the strval
function. Let's just say I ended up with mixed
feelings.
Member discussion