From ef935da5f239af1b820ad5238505442b776185da Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 28 Jun 2022 18:27:01 -0700 Subject: [PATCH] (dev/core#3660) CRM_Extension_ClassLoader - Defend against redundant refreshes Overview -------- This is a follow-up to #23824 (c24dd7db7e1e91120fd7daeb7e151f856d6b78c3) which addresses a regressive edge-case. Steps to Reproduce ------------------ * Write an extension like `wmf-civicrm` which (a) calls `System.flush` (`rebuildMenuAndCaches()`) during `hook_install` -- and then (b) loads some class from the same extension. ```php function foo_civicrm_install() { civicrm_api3('System', 'flush', []); CRM_Foo_Helper::doStuff(); } ``` * Try to install the extension. Before ------ Crashes on loading the class `CRM_Foo_Helper` After ----- Loads the class `CRM_Foo_Helper`. Comments -------- (1) To see what's happening, consider `CRM_Extension_Manager_Module::onPreInstall()`. This registers the new classloader and then fires `hook_install` which eventually fires `rebuildMenuAndCaches()`. With c24dd7db, this resets the classloader again. But the extension isn't fully installed yet - so it forgets about the new extension. (2) Is it safe to have some (temporarily) sticky items? Ish. You might say: "Ah, but what if we need to remove an extension? Won't this static variable retain stale things?" Doesn't matter. In PHP, classloading is a one-way-street. (You cannot unload.) So you'll still have old classes in memory, regardless of whether the `ClassLoader` has some old metadata about where to find classes. (3) I'm on the fence about whether this patch is a good idea. Calling `System.flush` explicitly in this context seems like an invitation to trouble. OTOH, it worked before #23824, so it can be called a regression. --- CRM/Extension/ClassLoader.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CRM/Extension/ClassLoader.php b/CRM/Extension/ClassLoader.php index a46580651d..bdd9ce6c68 100644 --- a/CRM/Extension/ClassLoader.php +++ b/CRM/Extension/ClassLoader.php @@ -21,6 +21,14 @@ class CRM_Extension_ClassLoader { */ const FEATURES = ',psr0,psr4,'; + /** + * A list of recently-activated extensions. This list is retained + * even if some ill-advised part of the installer does a `ClassLoader::refresh()`. + * + * @var array + */ + private static $newExtensions = []; + /** * @var CRM_Extension_Mapper */ @@ -97,6 +105,9 @@ class CRM_Extension_ClassLoader { } self::loadExtension($loader, $this->mapper->keyToInfo($key), $this->mapper->keyToBasePath($key)); } + foreach (static::$newExtensions as $record) { + static::loadExtension($loader, $record[0], $record[1]); + } return $loader; } @@ -130,6 +141,7 @@ class CRM_Extension_ClassLoader { if (file_exists($file)) { unlink($file); } + static::$newExtensions[] = [$info, $path]; if ($this->loader) { self::loadExtension($this->loader, $info, $path); } -- 2.25.1