Problem/Motivation
PHP 8.1 introduces many deprecation notices that would not appear previously. In my experience, it seems that hitting one of these notices (when thrown by a contrib module) generally causes a critical error coming from Core -- Error: Class "Drupal\Core\Utility\Error" not found in _drupal_error_handler_real() (line 63 of core/includes/errors.inc).
. In such cases, the deprecation message is included in the stack trace:
Update: this was a core PHP bug, that was fixed in the (very) recent PHP 8.1.6 release.
The website encountered an unexpected error. Please try again later.
Error: Class "Drupal\Core\Utility\Error" not found in _drupal_error_handler_real() (line 63 of core/includes/errors.inc).
_drupal_error_handler_real(8192, 'Return type of Drupal\profile\Plugin\Field\ProfileEntityFieldItemList::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice', '/var/www/html/drupal/web/core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php', 121) (Line: 346)
_drupal_error_handler(8192, 'Return type of Drupal\profile\Plugin\Field\ProfileEntityFieldItemList::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice', '/var/www/html/drupal/web/core/lib/Drupal/Core/TypedData/ComputedItemListTrait.php', 121) (Line: 571)
include('/var/www/html/drupal/web/core/includes/bootstrap.inc') (Line: 571)
Composer\Autoload\includeFile('/var/www/html/drupal/vendor/composer/../../web/core/lib/Drupal/Core/Utility/Error.php') (Line: 428)
Composer\Autoload\ClassLoader->loadClass('Drupal\Core\Utility\Error') (Line: 44)
Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber->on403(Object) (Line: 97)
Drupal\Core\EventSubscriber\ExceptionLoggingSubscriber->onException(Object, 'kernel.exception', Object)
call_user_func(Array, Object, 'kernel.exception', Object) (Line: 142)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object, 'kernel.exception') (Line: 219)
Symfony\Component\HttpKernel\HttpKernel->handleThrowable(Object, Object, 1) (Line: 91)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 67)
Drupal\simple_oauth\HttpMiddleware\BasicAuthSwap->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 49)
Asm89\Stack\Cors->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 708)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
Fixing/patching the deprecation seems to get rid of the notice, as well as this error handler error itself. The error handler error is problematic because it turns what should be a simple notice into a critical failure. I think that what must be happening is that the deprecation is somehow short-circuiting the autoloader or something.
This is an intermittent bug that seems to appear at a frequency of every few days. So far it has only been reported due to PHP core deprecation notices (e.g. for new return types for classes like ArrayAccess). As such, the problem may be with recursive classloader calls, i.e.:
- Classloader loads class A that extends a PHP core class - PHP triggers a deprecation notices for a return type.
- Drupal error handler is invoked
- The error handler triggers autoloading of \Drupal\Core\Utility\Error
- autoloading of \Drupal\Core\Utility\Error fails (why?) -> fatal
Steps to reproduce
There are no obvious steps to reproduce, you need a module installed which triggers a PHP deprecation notice, possibly php-fpm, and it may take several days for this error to appear.
Proposed resolution
Add a hook_requirements() to all supported branches of Drupal that checks for PHP >= 8.1.0 and < 8.1.6, and suggests updating to PHP 8.1.6 due to intermittent PHP autoloading issues.
PHP 8.1.6 is so recent that a hard requirement would potentially be a barrier to running Drupal for several months, and this only happens with deprecation warnings triggered via classloading, which isn't a problem for every install.
If we detect PHP 8.1.0 through 8.1.6, we should make sure the status report warns about it. How exactly we do that varies depending on Drupal version.
In Drupal 10, PHP 8.1.0 is the minimum bootable PHP version, and 8.1.6 is now the minimum recommended version. So, if we detect a broken PHP version is running, we should change the PHP section of the status report to a warning, with this (proposed) wording:
PHP $RUNNING_VERSION has an OPcache bug that can cause fatal errors with class autoloading. This can be fixed by upgrading to PHP 8.1.6 or later.
See #110, #111, and #112 for screenshots in 9.4.x, 9.5.x, and 10.0.x, respectively.
User interface changes
Release note snippet
Needed.