From d322c495c2abc7e01311311dd9ad39a41a445b50 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 30 Nov 2021 14:25:50 -0800 Subject: [PATCH] mixin/polyfill.php - Import. Update comments. --- mixin/polyfill.md | 46 +++++++++++++++++++++ mixin/polyfill.php | 101 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 mixin/polyfill.md create mode 100644 mixin/polyfill.php diff --git a/mixin/polyfill.md b/mixin/polyfill.md new file mode 100644 index 0000000000..d8059fc284 --- /dev/null +++ b/mixin/polyfill.md @@ -0,0 +1,46 @@ +# Mixin Polyfill + +Mixins will have built-in support in a future version of CiviCRM (vTBD). However, +for an extension to use a mixin on an older version of CiviCRM, it should +include the polyfill ([mixin/polyfill.php](../mixin/polyfill.php)). + +## Usage + +The polyfill will be enabled in `civix` (vTBD). To activate the polyfill in +a bespoke extension (`myext`, `org.example.myextension`), copy `mixin/polyfill.php`. +Load this file during a few key moments: + +```php +function _myext_mixin_polyfill() { + if (!class_exists('CRM_Extension_MixInfo')) { + $polyfill = __DIR__ . '/mixin/polyfill.php'; + (require $polyfill)('org.example.myextension', 'myext', __DIR__); + } +} + +function myext_civicrm_config() { + _myext_mixin_polyfill(); +} + +function myext_civicrm_install() { + _myext_mixin_polyfill(); +} + +function myext_civicrm_enable() { + _myext_mixin_polyfill(); +} +``` + +## Limitations / Comparison + +The polyfill loader is not as sophisticated as the core loader. Here's a comparison to highlight some of the limitations: + +| Feature | Core Loader | Polyfill Loader | +| -- | -- | -- | +| Load mixins from files (`mixin/*.mixin.php`) | Yes | Yes | +| Load mixins from subdirectories (`mixin/*/mixin.php`) | Yes | No | +| Read annotations from mixin files (eg `@mixinVersion`) | Yes | No | +| Activation - How does it decide to activate a mixin? | Read `info.xml` | Read `mixin/*.mixin.php` | +| Boot cache - How does it store boot-cache? | All boot-caches combined into one file | Each extension has separate boot-cache file | +| Deduplication - If two extensions include the same mixin, then only load one copy. | Yes | Partial - for exact version matches. | +| Upgrade - If two extensions include different-but-compatible versions, always load the newer version. | Yes | No | diff --git a/mixin/polyfill.php b/mixin/polyfill.php new file mode 100644 index 0000000000..f57c5ebbf8 --- /dev/null +++ b/mixin/polyfill.php @@ -0,0 +1,101 @@ +')) { + $mixinVers[$name] = $ver; + } + } + $mixins = []; + foreach ($mixinVers as $name => $ver) { + $mixins[] = "$name@$ver"; + } + + // Imitate CRM_Extension_MixInfo. + $mixInfo = new class() { + + /** + * @var string + */ + public $longName; + + /** + * @var string + */ + public $shortName; + + public $_basePath; + + public function getPath($file = NULL) { + return $this->_basePath . ($file === NULL ? '' : (DIRECTORY_SEPARATOR . $file)); + } + + public function isActive() { + return \CRM_Extension_System::singleton()->getMapper()->isActiveModule($this->shortName); + } + + }; + $mixInfo->longName = $longName; + $mixInfo->shortName = $shortName; + $mixInfo->_basePath = $basePath; + + // Imitate CRM_Extension_BootCache. + $bootCache = new class() { + + public function define($name, $callback) { + $envId = \CRM_Core_Config_Runtime::getId(); + $oldExtCachePath = \Civi::paths()->getPath("[civicrm.compile]/CachedExtLoader.{$envId}.php"); + $stat = stat($oldExtCachePath); + $file = Civi::paths()->getPath('[civicrm.compile]/CachedMixin.' . md5($name . ($stat['mtime'] ?? 0)) . '.php'); + if (file_exists($file)) { + return include $file; + } + else { + $data = $callback(); + file_put_contents($file, '<' . "?php\nreturn " . var_export($data, 1) . ';'); + return $data; + } + } + + }; + + // Imitate CRM_Extension_MixinLoader::run() + // Parse all live mixins before trying to scan any classes. + global $_CIVIX_MIXIN_POLYFILL; + foreach ($mixins as $mixin) { + // If the exact same mixin is defined by multiple exts, just use the first one. + if (!isset($_CIVIX_MIXIN_POLYFILL[$mixin])) { + $_CIVIX_MIXIN_POLYFILL[$mixin] = include_once $basePath . '/mixin/' . $mixin . '.mixin.php'; + } + } + foreach ($mixins as $mixin) { + // If there's trickery about installs/uninstalls/resets, then we may need to register a second time. + if (!isset(\Civi::$statics[__FUNCTION__][$mixin])) { + \Civi::$statics[__FUNCTION__][$mixin] = 1; + $func = $_CIVIX_MIXIN_POLYFILL[$mixin]; + $func($mixInfo, $bootCache); + } + } +}; -- 2.25.1