I'd like (as an addition, existing answers have my upvotes) to paint a different picture on how to see and tackle with such "problems". It does not make the outlined approaches less right or wrong and is merely an additional view which hopefully is of mutual benefit. And every project is different.
Given the premise:
My main problem is with functions like htmlspecialchars(php)
and trim(php)
, where null
no longer is silently converted to the empty string.
then this looks (first of all) as a reporting problem to me. The code can be made silent by not reporting E_DEPRECATED
.
Doing so ships (not only your code) with the benefit, that it is now known that your code is with deprecation notices. Reporting did work.
On the other hand, silencing deprecation notices may move them out of sight. And if you loose the information that the code-base is with deprecation notices, it may still be technically easy to recover from that loss of information (report deprecation notices again), however if the time from change has enlarged, there might now be an overwhelming noise (E_TOO_MUCH_NOISE).
So is the code not being silent actually a bad thing? Or can it be turned into a benefit? I'd prefer to go with the later. We're working with the information already anyway.
So in this case I had the idea to not generally suppress the deprecation notices but to "silence" the function calls. It is easy, and there is stupidity both in the good but also in the worse sense with it:
trim($mixed); #1 -> @trim($mixed); #2
This is certainly an operation that can be applied on a code-base with standard text tooling. It would also show you where use of the @
suppression operator already was made in the past:
@trim($mixed); #3 -> @@trim($mixed); #4
If you're a PHP developer looking at such code within your editor (for cases #2-#4), they would immediately scream to you and for all four cases raise your eyebrows at least ($mixed
).
So thanks for not being silent, we made the places screaming, just not at runtime1.
Different to the first approach to silence by not reporting E_DEPRECATED
which is prone to loosing information, the information is preserved by having all the @
-signs.
Does it help with the noise problem? If we stop doing the work here, not at all. Now we would have plastered the code with @
-signs, decided to take no further action so we could have already done it with the first solution (do not report deprecation message) without touching the code.
So what is the benefit of it? Well, despite the code now running silent, PHP still is providing the diagnostic messages. That is, it is now possible to register a PHP error-handler as a listener (when executing the code).
And just on code-level, it is easy to review the places as the @
-signs are (typically) easy to spot in the code as well.
The second part is important, as albeit multiple places may be affected by a deprecation, there must not be one fix to catch them all (me prefers to stay away from "one size fits it all 'solutions'" if possible), but especially with this PHP 8.1 change in context of the question, I can imagine there are different needs depending on place of use.
For example in templating code (output) the concrete type is less an issue, and therefore a cast to string is very likely the preferred fix:
@trim($mixed); -> trim((string)$mixed)
@@trim($mixed); -> @trim((string)$mixed)
The templating (output) remains stable.
But for actual input processing, the deprecation notice may uncover actual underlying flaws that are worth to fix, like missing defaults (over complicating things), unclear handling of values (empty vs. null vs. string vs. bool vs. number vs. array vs. object in PHP) or a general $mixed
confusion.
Such a trim($mixed)
could be a years old forgotten safe-guard that has never undergone the upgrade (there are better safeguards available). For such code I'm pretty sure I already want and demand that $mixed
actually is $string
before I make use of trim()
. The reason is simple, at least two things are coming to mind directly:
- a) Either
trim()
is not necessary any more - than it can be removed (one of my favorite fixes: removing code!) - or -
- b) It is doing string-work then I have a problem as I don't want anything not a string to be there. Problem in the sense, that it is often not applicable with a shotgun approach (Gießkanne anyone?).
It is totally valid to patch with $mixed ?? ''
if the original use was string or null
only.
@trim($mixed); -> trim($mixed ?? '')
@@trim($mixed); -> @trim($mixed ?? '')
But otherwise, e.g. a number like 42, instead of a deprecation message, a TypeError
is being thrown. This can make the difference between running and not running code.
So there is a bit more to maintain here, like reviewing the places, further clustering if possible and then applying more dedicated fixes. It could reveal missing tests or assertions, need a bit of a time to stabilize in the overall application flow etc..
Under such cases to complete the migration of the code, do the clustering, handle w/ null-coalescing operator and do the proper paper-work for the real fixes. Once the non-obvious error suppression with the null-coalescing operator has been done and the @
suppression operator removed, you'll likely loose the information if the fix planning has not captured them.
When looking more educated at such places, I'm not surprised when I find myself scratching head or rubbing my eyes. Then I remind myself that those errors are not because of the PHP 8.1 version, the version change has just brought them up (again) and I get sometimes even complete bug clusters as a by-catch only by maintaining the PHP version.
Cheat-Sheet
(string)$mixed
- previous behaviour
$mixed ?? ''
- error suppression for TypeError
on null
only
@
- full error suppression. you should document in/for your code-base where it is applicable to use.
@@
- if this comes up, it is likely an interesting spot to look into.
empty($mixed) ? '' : xxx($mixed)
- carry the trash out, typical empty paralysis / mixed confusion, look for clusters, there is a chance the code-base can be greatly simplified. migrate to scalar types (PHP 7), pull in strict type handling from most-inner in outwards direction, use both PHP "classic" and "strict" type handling where applicable. PHP 7.0 assertions and PHP 8.1 deprecation messages can support here well.
Error Handler
There is no magic with the error handling, it is standard as documented on PHP.net (compare with Example #1), it works as an observer on the error events, distinction between suppressed and non-suppressed errors can be done via error_reporting(php)
/ error_reporting(php-ini)
at least to the level normally necessary if the distinction is needed (in a production setting E_DEPRECATED
is normally not part of the reporting). This exemplary handler throws on all reported errors, so would for deprecation events as well for E_ALL
and would therefore require the @
suppression operator to not throw:
set_error_handler(static function ($type, $message, $file, $line) use (&$deprecations) {
if (!(error_reporting() & $type)) {
// This error code is not included in error_reporting, so let it fall
// through to the standard PHP error handler
// capture E_DEPRECATED
if ($type === E_DEPRECATED) {
$deprecations[] =
['deprecations' => count($deprecations ?: [])]
+ get_defined_vars();
}
return false;
}
// throwing error handler, stand-in for own error reporter
// which may also be `return false;`
throw new ErrorException($message, $type, error_reporting(), $file, $line);
});
An error handler similar to it can be found in an extended example on 3v4l.org including deprecated code to report on.
E_USER_DEPRECATED
Technically, the error suppression operator can be combined with E_USER_DEPRECATED
the same as outlined with E_DEPRECATED
above.
However there is less control about it and it may already be in use by third-party code a project may already have in its dependencies. It is not uncommon to find code similar to:
@trigger_error('this. a message.', E_USER_DEPRECATED);
which does exactly the same: emit deprecation events but exclude them from PHPs' reporting. Subscribing on those may put you into the noise. With E_DEPRECATED
you always get the "good, original ones" from PHP directly.
- When considered the approach with the
@
error suppression operator and commented on it, IMSoP immediately raised a red/black flag (rightfully!) that it is easy to throw the baby out with the bathwater with the @
suppression operator. In context of my answer it is intended to only suppress the deprecation notice but the consequence of use is that it suppresses all diagnostic messages and errors, in some PHP versions even fatal ones, so PHP exits 255 w/o any further diagnostics - not only take but handle with care. This operator is powerful. Track its usage in your code-base and review it constantly that it matches your baseline/expectations. For legit cases consider to make use of a silencer. For porting / maintaining the code use it for flagging first of all. When done with mass-editing, remove it again.
??''
. wth? what's the benefits?! php make jit! was fast with opcache and memcache as Java, but without mem leakage.. so said.. – Alexandra