Reputation: 1083
PHP 8.1 has deprecated passing null
as a parameter to a lot of core functions. My main problem is with functions like htmlspecialchars
and trim
, where null
is no longer silently converted to the empty string.
To fix this issue without going through a huge amount of code I was trying to rename the original built-in functions and replace them with wrappers that cast input from null
to (empty) string.
My main problem with this approach is, that the function rename_function
(from PECL apd) no longer works; last update on this is from 2004 1.
I need some sort of override of built-in functions, to avoid writing null checks each time a function is called making all my code two times larger.
The only other solution I can think of is to use only my custom functions, but this still require going through all my code and third party libraries I have.
Upvotes: 98
Views: 236350
Reputation: 61
Question : Deprecated: Assert\that(): Implicitly marking parameter $defaultPropertyPath as nullable is deprecated, the explicit nullable type must be used instead in
And For Solving this issue, follow below steps.
Open php.ini file and
Add below line error_reporting = E_ALL & ~E_DEPRECATED
Restart the apache server on windows.
This will remove all the Deprecated: warning issue with phpmyadmin
Upvotes: -1
Reputation: 2149
Rector has the rule NullToStrictStringFuncCallArgRector
to fix this:
- mb_strtolower($value);
+ mb_strtolower((string) $value);
Upvotes: 10
Reputation: 10002
While ??
is cool I think doing is_string($val)
check might be more appropriate.
You can use regexp replacement to change all occurrences (or at least all typical occurrences).
Find and replace:
htmlspecialchars\((\$\w+)\)
(!is_string($1) ? '' : htmlspecialchars($1))
This would replace htmlspecialchars($val);
with (!is_string($val) ? '' : htmlspecialchars($val))
.
Upvotes: 0
Reputation: 664
After searching a lot and trying too many solutions, here is the final solution for this issue until they release a newer compatible version of Smarty templates... Add to the sitewide function.php
file.
function custom_trim(?string $value) {
return empty($value) ? '' : trim($value);
}
Upvotes: 0
Reputation: 2085
What a painful experience this is.
Here's my quick solution which is compatible with older versions of php:
function custom_trim( $value)
{
if($value!="")
{
$value = trim($value);
}
return $value;
}
function custom_stripslashes( $value)
{
if($value!="")
{
$value = stripslashes($value);
}
return $value;
}
Upvotes: 2
Reputation: 11
We recently updated from php 7.4 to 8.1 (on Magento 2.4.4). We also experienced lots of exceptions thrown for deprecation warnings within vendor modules and our extensive in-house custom modules.
We remediated everything we could find, however to be safe, we implemented the following patch, which modifies magento's error handler to log these deprecation warnings, rather than throwing them as exceptions.
On go-live in production, this kept the application running (by simply logging these errrors), and gave us breathing room to address them as we saw them occuring in the logs.
Keep in mind that magento cli does not use this error handler, but rather the symphony framework one, which luckly handles deprecation warnings by outputing them to the console (so watch for those as well).
Upgrade went smooth, so maybe this will helps others...
Index: vendor/magento/framework/App/Bootstrap.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/vendor/magento/framework/App/Bootstrap.php b/vendor/magento/framework/App/Bootstrap.php
--- a/vendor/magento/framework/App/Bootstrap.php
+++ b/vendor/magento/framework/App/Bootstrap.php (date 1679064575518)
@@ -384,7 +384,7 @@
*/
private function initErrorHandler()
{
- $handler = new ErrorHandler();
+ $handler = new ErrorHandler($this->objectManager->get(LoggerInterface::class));
set_error_handler([$handler, 'handler']);
}
Index: vendor/magento/framework/App/ErrorHandler.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/vendor/magento/framework/App/ErrorHandler.php b/vendor/magento/framework/App/ErrorHandler.php
--- a/vendor/magento/framework/App/ErrorHandler.php
+++ b/vendor/magento/framework/App/ErrorHandler.php (date 1679073448870)
@@ -34,6 +34,15 @@
E_USER_DEPRECATED => 'User Deprecated Functionality',
];
+ private $logger;
+
+ public function __construct(
+ $logger = null
+ )
+ {
+ $this->logger = $logger;
+ }
+
/**
* Custom error handler
*
@@ -50,6 +59,12 @@
// there's no way to distinguish between caught system exceptions and warnings
return false;
}
+
+ if (E_DEPRECATED == $errorNo) {
+ $msg = "Logging - PHP Deprecation Warning: {$errorStr} in {$errorFile} on line {$errorLine}";
+ if ($this->logger) $this->logger->warning($msg);
+ return false;
+ }
$errorNo = $errorNo & error_reporting();
if ($errorNo == 0) {
Upvotes: 1
Reputation: 937
A solution for existing projects with a lot of pages, that you want to migrate to PHP8+:
In my case, most problems came with "trim" function that receives null values. In that case, you can create a custom "trim" function and then replace in your existing code the "trim" for "custom_trim" function:
public function custom_trim(?string $value)
{
return trim($value ?? '') ;
}
or just cast the parameter like this
trim((string) $value);
Upvotes: 8
Reputation: 611
WPCS is incompatible with PHP 8.1. Adding this to your phpcs config file may fix it for you.
<ini name="error_reporting" value="E_ALL & ~E_DEPRECATED" />
(Note that the &
is an escaped &
(ampersand); the &
character must be escaped in XML documents.)
Reference - https://github.com/WordPress/WordPress-Coding-Standards/issues/2035#issuecomment-1325532520
Upvotes: 1
Reputation: 197533
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)
andtrim(php)
, wherenull
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:
trim()
is not necessary any more - than it can be removed (one of my favorite fixes: removing code!) - or -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.
@
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.Upvotes: 8
Reputation: 1
Another option is to create a phpFunctionWrapper class that you can inject through the constructor of your class. The wrapper functions should take care of the null coalescent operator rather than introduce this dependency in the code.
For example:
<?php
namespace Vendor\Core\Helper;
class PhpFunctionWrapper
{
public function numberFormat($number, $decimals): string|false {
return number_format($number ?? 0.0, $decimals);
}
public function strPos($haystack, $needle, int $offset = 0): int|false {
return strpos( $haystack ?? "", $needle ?? "", $offset);
}
public function pregSplit($pattern, $subject, $limit = -1, $flags = 0): array|bool
{
return preg_split($pattern ?? '', $subject ?? '', $limit, $flags);
}
public function explode($separator, $string, $limit = PHP_INT_MAX): array
{
return explode($separator, $string, $limit);
}
}
Then, you inject the wrapper class in your class through the constructor:
<?php
namespace Vendor\Catalog\Block\Product;
use Vendor\Core\Helper\PhpFunctionWrapper;
use Magento\Catalog\Block\Product\Context;
use Magento\Catalog\Api\ProductRepositoryInterface;
class View extends \Magento\Catalog\Block\Product\View
{
private PhpFunctionWrapper $phpFunctionWrapper;
public function __construct(Context $context,
\Magento\Framework\Url\EncoderInterface $urlEncoder,
\Magento\Framework\Json\EncoderInterface $jsonEncoder,
\Magento\Framework\Stdlib\StringUtils $string,
\Magento\Catalog\Helper\Product $productHelper,
\Magento\Catalog\Model\ProductTypes\ConfigInterface $productTypeConfig,
\Magento\Framework\Locale\FormatInterface $localeFormat,
\Magento\Customer\Model\Session $customerSession,
ProductRepositoryInterface $productRepository,
\Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
PhpFunctionWrapper $phpFunctionWrapper,
array $data = [])
{
parent::__construct($context,
$urlEncoder,
$jsonEncoder,
$string,
$productHelper,
$productTypeConfig,
$localeFormat,
$customerSession,
$productRepository,
$priceCurrency,
$data);
$this->phpFunctionWrapper = $phpFunctionWrapper;
}
}
Finally, in for example a template file that uses the View
block, you change the code from:
<div data-role="add-to-links" class="actions-secondary"<?= strpos($pos, $viewMode . '-secondary') ? $position : '' ?>>
to:
<div data-role="add-to-links" class="actions-secondary"<?= $block->phpFunctionWrapper->strPos($pos, $viewMode . '-secondary') ? $position : '' ?>>
Of course, you need to find all occurrences, but you need to go through them anyway. At least in the future, if you need to change something about these functions, you only need to change the wrapper.
I have created a core helper module where I keep these types of solutions that I can inject where needed. It keeps my code clean, and free from dependencies.
Upvotes: 0
Reputation: 449
The problem was occured
at vendor/laravel/framework/src/Illuminate/Routing/RouteGroup.php:65
You can fix this problem by casting the variable to a string using (string)
Like before it was
trim($old, '/')
After casting
trim((string)$old, '/')
protected static function formatPrefix($new, $old, $prependExistingPrefix = true)
{
$old = $old['prefix'] ?? null;
if ($prependExistingPrefix) {
return isset($new['prefix']) ? trim((string)$old, '/').'/'.trim((string)$new['prefix'], '/') : $old;
} else {
return isset($new['prefix']) ? trim((string)$new['prefix'], '/').'/'.trim((string)$old, '/') : $old;
}
}
Upvotes: 0
Reputation: 302
The OP's issue is that refactoring a large code base is hard. Adding ??'' to every strlen() call is a major time sink when your dealing with many MB of legacy source code.
Type conversion works on null values such that
strlen((string)null); // returns 0
A search and replace of strlen( with strlen((string) can work but you'd still need to step through them one at a time to look for edge cases.
Upvotes: 3
Reputation: 2819
while waiting to correct the problems (there may be many) it is possible to define a custom error handling function to ignore them.
For exemple :
error_reporting(E_ALL) ;
set_error_handler(
function($severity, $message, $file, $line) {
if ( !$severity || error_reporting()!=E_ALL ) return ; // to treat @ before functions
$erreurs_autorisees = array(
E_NOTICE => "Notice",
E_USER_NOTICE => "User Notice",
E_DEPRECATED => "Deprecated",
E_USER_DEPRECATED => "User Deprecated",
E_WARNING => "Warning",
E_USER_WARNING => "User Warning",
) ;
if ( isset($erreurs_autorisees[$severity]) ) {
$warning_autorises = [
"addslashes(): Passing null to parameter #1 (\$string) of type string is deprecated",
"base64_decode(): Passing null to parameter #1 (\$string) of type string is deprecated",
"htmlspecialchars(): Passing null to parameter #1 (\$string) of type string is deprecated",
"mb_decode_mimeheader(): Passing null to parameter #1 (\$string) of type string is deprecated",
"mysqli_real_escape_string(): Passing null to parameter #2 (\$string) of type string is deprecated",
"preg_replace(): Passing null to parameter #3 (\$subject) of type array|string is deprecated",
"preg_split(): Passing null to parameter #3 (\$limit) of type int is deprecated",
"rawurlencode(): Passing null to parameter #1 (\$string) of type string is deprecated",
"setcookie(): Passing null to parameter #2 (\$value) of type string is deprecated",
"str_starts_with(): Passing null to parameter #1 (\$haystack) of type string is deprecated",
"strcmp(): Passing null to parameter #1 (\$string1) of type string is deprecated",
"strlen(): Passing null to parameter #1 (\$string) of type string is deprecated",
"strtr(): Passing null to parameter #1 (\$string) of type string is deprecated",
"strpos(): Passing null to parameter #1 (\$haystack) of type string is deprecated",
"substr(): Passing null to parameter #1 (\$string) of type string is deprecated",
"trim(): Passing null to parameter #1 (\$string) of type string is deprecated",
"strncasecmp(): Passing null to parameter #1 (\$string1) of type string is deprecated",
] ;
if ( in_array($message, $warning_autorises) ) return true ;
// On ne converti pas les warning en Exception, on se contente de les logger / les afficher
$msg = $erreurs_autorisees[$severity].": $message in $file on line $line" ;
if ( ini_get('display_errors') ) echo $msg ;
// @error_log($msg) ; // if you want to log
}
else throw new ErrorException($message, 0, $severity, $file, $line) ;
return true;
}
);
Upvotes: 0
Reputation: 97628
Firstly, two things to bear in mind:
htmlspecialchars($something)
can be replaced with htmlspecialchars($something ?? '')
Next, some options:
?? ''
or fixing a logic bug where you weren't expecting a null anyway.nullable_htmlspecialchars
and do a straight-forward find and replace in your code.nullableoverride\htmlspecialchars
; then in any file where you add use function nullableoverride\htmlspecialchars;
that function will be used instead of the built-in one. This has to be added in each file, though, so you may need a tool to automate adding it.?? ''
to appropriate function calls, so you don't have to edit them all by hand. Unfortunately, there doesn't seem to be a built-in rule for this (yet), so you'd have to learn to write your own.?? ''
to simple cases.Upvotes: 107