Skip to content

Mixins: Create a new mixin

About this document

This page presents how to develop a mixin. It assumes you have read about the general concepts.

Tutorial

Let's create a mixin called hello-world with version 1.0.0. It will announce "Hello world".

First, enable hello-world in info.xml:

 <extension type="module">
   <mixins>
     <mixin>menu-xml@1.0.0</mixin>
     <mixin>mgd-php@1.0.0</mixin>
+    <mixin>hello-world@1.0.0</mixin>
   </mixins>
 </extension>

Next, create the mixin PHP file:

mkdir 'mixin/hello-world@1/'
nano 'mixin/hello-world@1/mixin.php'

This file will need some annotations:

<?php
/**
 * @mixinName hello-world
 * @mixinVersion 1.0.0
 */

Finally, we define a function. This function receives information about your extension.

It can use that information to do something pleasant, like say "Hello".

return function (CRM_Extension_MixInfo $mixInfo, CRM_Extension_BootCache $bootCache) {
  printf("Hello world. I am extension \"%s\"!\n", $mixInfo->longName);
};

The mixin function runs very early -- and very frequently. Calling printf() here would be too much.

Instead, we should be more selective -- we should use CiviCRM hooks to find the opportune moment. For example, hook_civicrm_alterContent allows you to append content to the HTML page-output.

return function (CRM_Extension_MixInfo $mixInfo, CRM_Extension_BootCache $bootCache) {

  Civi::dispatcher()->addListener('hook_civicrm_alterContent', function ($event) use ($mixInfo) {
    if (!$mixInfo->isActive()) {
      return;
    }
    $event->content .= sprintf("<div>Hello world. I am extension \"%s\"!\n", $mixInfo->longName);
  });

};

Finally, to activate the new mixin, you will need to clear system caches.

cv flush

FAQ

What is $mixInfo?

A mixin like hello-world or menu-xml can be enabled on many different extensions. The $mixInfo tells you about the name, location, and status of the extension.

For more information, see CRM_Extension_MixInfo.

Note: The class CRM_Extension_MixInfo was added in CiviCRM 5.45. If you are using the polyfill to run older versions of CiviCRM, then it will provide a similar object - but the class-name may differ. Use relaxed type-hints.

What is $bootCache?

Many mixins perform a scan over the extension source-code. Depending on the specific scan, this may be fast or slow. The boot-cache is optimized for storing the scan-results during boot.

Of course, like any cache, there is a trade-off between performance and complexity. I would only suggest it if (1) the scan-process is expensive and (2) the scan-data needs to be used frequently.

For more information, see CRM_Extension_BootCache.

Note: The class CRM_Extension_BootCache was added in CiviCRM 5.45. If you are using the polyfill to run older versions of CiviCRM, then it will provide a similar object - but the class-name may differ. Use relaxed type-hints.

Why does it return an anonymous function?

Mixins are designed to be multi-version safe. This notation allows the system to load multiple versions of the same mixin -- without any conflicts over function-names or class-names.

Can I define named classes or named functions?

Sort of.

Remember that mixins are multi-version safe. Specifically, the system must be allowed to load major versions in parallel. As long as the major versions don't conflict, it should work.

For example, to prevent a conflict between hello-world@1.0.0 and hello-world@2.0.0, you could use versioned-namespaces:

// FILE: mixin/hello-world@1.0.0.php
namespace HelloWorldV1; // <== Notice "V1"

class Greeter {
}

return function($mixInfo, $bootCache) {
  $g = new Greeter();
};
// FILE: mixin/hello-world@2.0.0.php
namespace HelloWorldV2; // <== Notice "V2"

class Greeter {
}

return function($mixInfo, $bootCache) {
  $g = new Greeter();
};

Another approach is to refrain from ever issuing a major update. This gist outlines such a scenario.

However, this has only been tested lightly. At time of writing, standard mixins do not use named-classes or named-functions.