(REF) Mixins - Promote `MixinLoader` to tracked service-object
[civicrm-core.git] / CRM / Extension / MixinLoader.php
CommitLineData
92ee7b19
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12/**
1521e0f8 13 * The MixinLoader gets a list of extensions and mixins - then loads them.
92ee7b19
TO
14 */
15class CRM_Extension_MixinLoader {
16
1521e0f8
TO
17 public function run($force = FALSE) {
18 $system = CRM_Extension_System::singleton();
19 $cache = $system->getCache();
20
21 $cachedScan = $force ? NULL : $cache->get('mixinScan');
22 $cachedBootData = $force ? NULL : $cache->get('mixinBoot');
23
24 [$funcFiles, $mixInfos] = $cachedScan ?: (new CRM_Extension_MixinScanner($system->getMapper(), $system->getManager(), TRUE))->build();
25 $bootData = $cachedBootData ?: new CRM_Extension_BootCache();
26
27 $this->loadMixins($bootData, $funcFiles, $mixInfos);
28
29 if ($cachedScan === NULL) {
30 $cache->set('mixinScan', [$funcFiles, $mixInfos], 24 * 60 * 60);
31 }
32 if ($cachedBootData === NULL) {
33 $bootData->lock();
34 $cache->set('mixinBoot', $bootData, 24 * 60 * 60);
35 }
36 }
37
92ee7b19
TO
38 /**
39 * Load all extensions and call their respective function-files.
40 *
92ee7b19
TO
41 * @throws \CRM_Core_Exception
42 */
1521e0f8 43 protected function loadMixins(CRM_Extension_BootCache $bootCache, array $liveFuncFiles, array $mixInfos): void {
92ee7b19
TO
44 // == WIP ==
45 //
46 //Do mixins run strictly once (during boot)? Or could they run twice? Or incrementally? Some edge-cases:
47 // - Mixins should make changes via dispatcher() and container(). If there's a Civi::reset(), then these things go away. We'll need to
48 // re-register. (Example scenario: unit-testing)
49 // - 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.
50 // - Mixins register for every active module. If an old module is disabled, then there may be old listeners/services lingering.
51 if (!isset(\Civi::$statics[__CLASS__]['done'])) {
52 \Civi::$statics[__CLASS__]['done'] = [];
53 }
54 $done = &\Civi::$statics[__CLASS__]['done'];
55
56 // 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.
57 // 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
58 // safe because we deduplicate.
59 static $funcsByFile = [];
1795aaf5 60 foreach ($liveFuncFiles as $verExpr => $file) {
92ee7b19
TO
61 if (!isset($funcsByFile[$file])) {
62 $func = include_once $file;
63 if (is_callable($func)) {
64 $funcsByFile[$file] = $func;
65 }
66 else {
67 error_log(sprintf('MixinLoader: Received invalid callback from \"%s\"', $file));
68 }
69 }
70 }
71
1795aaf5 72 foreach ($mixInfos as $ext) {
92ee7b19
TO
73 /** @var \CRM_Extension_MixInfo $ext */
74 foreach ($ext->mixins as $verExpr) {
75 $doneId = $ext->longName . '::' . $verExpr;
76 if (isset($done[$doneId])) {
77 continue;
78 }
1795aaf5
TO
79 if (isset($funcsByFile[$liveFuncFiles[$verExpr]])) {
80 call_user_func($funcsByFile[$liveFuncFiles[$verExpr]], $ext, $bootCache);
92ee7b19
TO
81 $done[$doneId] = 1;
82 }
83 else {
84 error_log(sprintf('MixinLoader: Failed to load "%s" for extension "%s"', $verExpr, $ext->longName));
85 }
86 }
87 }
92ee7b19
TO
88 }
89
90}