Merge pull request #20561 from WeMoveEU/fix/double-autosave
[civicrm-core.git] / CRM / Extension / MixinLoader.php
index 391eddf141ac8702ba52e8f4861d118dab6ef27d..edb67a0814d1a946f8051ba59fba34c1d88081a3 100644 (file)
  */
 
 /**
- * The MixinLoader tracks a list of extensions and mixins.
+ * The MixinLoader gets a list of extensions and mixins - then loads them.
  */
 class CRM_Extension_MixinLoader {
 
   /**
-   * Load all extensions and call their respective function-files.
+   * List extension-mixins that have been loaded already.
    *
-   * @throws \CRM_Core_Exception
+   * @var array
    */
-  public function run(CRM_Extension_BootCache $bootCache, array $liveFuncFiles, array $mixInfos): void {
-    // == WIP ==
-    //
-    //Do mixins run strictly once (during boot)? Or could they run twice? Or incrementally? Some edge-cases:
-    // - Mixins should make changes via dispatcher() and container(). If there's a Civi::reset(), then these things go away. We'll need to
-    //   re-register. (Example scenario: unit-testing)
-    // - Mixins register for every active module. If a new module is enabled, then we haven't had a chance to run on the new extension.
-    // - Mixins register for every active module. If an old module is disabled, then there may be old listeners/services lingering.
-    if (!isset(\Civi::$statics[__CLASS__]['done'])) {
-      \Civi::$statics[__CLASS__]['done'] = [];
+  protected $done = [];
+
+  public function run($force = FALSE) {
+    $system = CRM_Extension_System::singleton();
+    $cache = $system->getCache();
+
+    $cachedScan = $force ? NULL : $cache->get('mixinScan');
+    $cachedBootData = $force ? NULL : $cache->get('mixinBoot');
+
+    [$funcFiles, $mixInfos] = $cachedScan ?: (new CRM_Extension_MixinScanner($system->getMapper(), $system->getManager(), TRUE))->build();
+    $bootData = $cachedBootData ?: new CRM_Extension_BootCache();
+
+    $this->loadMixins($bootData, $funcFiles, $mixInfos);
+
+    if ($cachedScan === NULL) {
+      $cache->set('mixinScan', [$funcFiles, $mixInfos], 24 * 60 * 60);
     }
-    $done = &\Civi::$statics[__CLASS__]['done'];
+    if ($cachedBootData === NULL) {
+      $bootData->lock();
+      $cache->set('mixinBoot', $bootData, 24 * 60 * 60);
+    }
+  }
 
+  /**
+   * Load all extensions and call their respective function-files.
+   *
+   * @throws \CRM_Core_Exception
+   */
+  protected function loadMixins(CRM_Extension_BootCache $bootCache, array $liveFuncFiles, array $mixInfos): void {
     // Read each live func-file once, even if there's some kind of Civi::reset(). This avoids hard-crash where the func-file registers a PHP class/function/interface.
     // Granted, PHP symbols require care to avoid conflicts between `mymixin@1.0` and `mymixin@2.0` -- but you can deal with that. For minor-versions, you're
     // safe because we deduplicate.
@@ -52,12 +68,12 @@ class CRM_Extension_MixinLoader {
       /** @var \CRM_Extension_MixInfo $ext */
       foreach ($ext->mixins as $verExpr) {
         $doneId = $ext->longName . '::' . $verExpr;
-        if (isset($done[$doneId])) {
+        if (isset($this->done[$doneId])) {
           continue;
         }
         if (isset($funcsByFile[$liveFuncFiles[$verExpr]])) {
           call_user_func($funcsByFile[$liveFuncFiles[$verExpr]], $ext, $bootCache);
-          $done[$doneId] = 1;
+          $this->done[$doneId] = 1;
         }
         else {
           error_log(sprintf('MixinLoader: Failed to load "%s" for extension "%s"', $verExpr, $ext->longName));