Extension cannot found its own classes during install when opcache is enabled
I start with the following configuration:
- PHP 8.1.7 Using the docker image FROM php:8.1.7-apache-buster
- OPCache enabled standard configuration
- Buildkit CiviCRM install for Drupal 9 see below:
civibuild create patch \
--type drupal9-demo \
--civi-ver master \
--url http://patch.kainuk \
--cms-ver 9.5
Now, when I enable the Action Provider extension (just from the extension screen), the following error is thrown.
Error: Class "Civi\ActionProvider\Symfony\Component\DependencyInjection\DefinitionAdapter" not found in action_provider_civicrm_container() (line 15 of /buildkit/build/patch/web/sites/default/files/civicrm/ext/action-provider/action_provider.php)
cv flush
lets the problem disappear, and for the Action Provider that is no problem because only code is installed (it does not change the database). However, other extensions can have more problems because the installation is interrupted, and possible incomplete.
The problem is, however, reproducible. When I disable the extension, and uninstall it, the error reappears after installing.
I did some duty time in the debugger and found the following code in the extension class loader (CRM_Extension_ClassLoader)
$file = $this->getCacheFile();
if (file_exists($file)) {
$this->loader = require $file;
}
else {
$this->loader = $this->buildClassLoader();
$ser = serialize($this->loader);
file_put_contents($file,
sprintf("<?php\nreturn unserialize(%s);", var_export($ser, 1))
);
}
$this->buildClassLoader()
is an expensive procedure, so after calculation the result is saved for reuse. When a new extension is installed, the saved file is removed because it does not contain the new extension configuration. A new classloader with all the extension stuff is calculated and an updated file is written.
For loading the class, the PHP construct require
is used. This construct was designed for loading PHP code. The assumption is that PHP code does not change much, and OPCache used this assumption, to store the file in memory. The risk is here is that not the newly written configuration is used, but an old one from memory. On my configuration, I observed with the PHP debugger, that when the hook_civicrm_container
the old classloader is loaded with require. Just put a breakpoint before the $this->loader = require $file
line.
When the OPCache is disabled, the problem does not appear.