Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Overview -------- (NOTE: For this description, I reference the term "API" in the general sense of a programmatic interface -- such as a hook or file-naming convention. It is not specifically about CRUD/DB APIs.) The `civix` code-generator provides support for additional coding-conventions -- ones which are more amenable to code-generation. For example, it autoloads files from `xml/Menu/*.xml` and `**/*.mgd.php`. The technique for implementing this traditionally relies on generating a lot of boilerplate. This patch introduces a new construct ("mixin") which allows boilerplate to be maintained more easily. A mixin inspects an extension programmatically, registering new hooks as needed. A mixin may start out as a file in `civix` (or even as a bespoke file in some module) - and then be migrated into `civicrm-core`. Each mixin has a name and version, which means that (at runtime) it will only load the mixin once (ie the best-available version). See: totten/civix#175 Before ------ The civix templates generate a few files, such as `mymod.php` and `mymod.civix.php`. A typical example looks like this: ```php // mymod.php - Implement hook_civicrm_xmlMenu require_once 'mymod.civix.php'; function mymod_civicrm_xmlMenu(&$all, $the, $params) { _mymod_civix_civicrm_xmlMenu($all, $the, $params); } ``` and ```php // mymod.civix.php - Implement hook_civicrm_xmlMenu function _mymod_civix_civicrm_xmlMenu(&$all, $the, $params) { foreach (_mosaico_civix_glob(__DIR__ . '/xml/Menu/*.xml') as $file) { $files[] = $file; } } ``` These two files are managed differently: `mymod.php` is owned by the developer, and they may add/remove/manage the hooks in this file. `mymod.civix.php` is owned by `civix` and must be autogenerated. This structure allows `civix` (and any `civix`-based extension) to take advantage of new coding-convention immediately. However, it comes with a few pain-points: * If you want to write a patch for `_mymod_civix_civicrm_xmlMenu`, the dev-test-loop requires several steps. * If `civix` needs to add a new `hook_civicrm_foo`, then the author must manually create the stub function in `mymod.php`. `civix` has documentation (`UPGRADE.md`) which keeps a long list of stubs that must be manually added. * If `civix` has an update for `_mymod_civix_civicrm_xmlMenu`, then the author must regenerate `mymod.civix.php`. * If `mymod_civix_xmlMenu` needs a change, then the author must apply it manually. * If `civix`'s spin on `hook_civicrm_xmlMenu` becomes widespread, then the `xmlMenu` boilerplate is duplicated across many extensions. After ----- An extension may enable a mixin in `info.xml`, eg: ```xml <mixins> <mixin>civix-register-files@2.0</mixin> </mixins> ``` Civi will look for a file `mixin/civicrm-register-files@2.0.0.mixin.php` (either in the extension or core). The file follows this pattern: ```php return function(\CRM_Extension_MixInfo $mixInfo, \CRM_Extension_BootCache $bootCache) { // echo "This is " . $mixInfo->longName . "!\n"; \Civi::dispatcher()->addListener("hook_civicrm_xmlMenu", function($e) use ($mixInfo) { ... }); } ``` The mixin file is a plain PHP file that can be debugged/copied/edited verbatim, and it can register for hooks on its own. The code is no longer a "template", and it doesn't need to be interwoven between `mymod.php` and `mymod.civix.php`. It is expected that a system may have multiple copies of a mixin. It will choose the newest compatible copy. Hypothetically, if there were a security update or internal API change, core might ship a newer version to supplant the old copy in any extensions. Technical Details ----------------- Mixins may define internal classes/interfaces/functions. However, each major-version must have a distinct prefix (e.g. `\V2\Mymixin\FooInterface`). Minor-versions may be provide incremental revisions over the same symbol (but it's imperative for newer increments to provide the backward-compatibility).
- Loading branch information