4

I want to give others the chance to do something when certain events happen in my extension.

So to be clear: I'm not wanting to use existing hooks in my extension, I'm wanting to create a new hook that my extension will invoke and other code may choose to react to.

I couldn't find anything in the developers' guide about this. Looking at the source makes me think that maybe I should be doing something with CRM_Utils_Hook or perhaps \Civi::dispatcher()->dispatch('hook_my_new_hook', $some_event_object)?

Is there a standardised way to do this? Are there any good examples?

5

Here is an example of a hook we created for CiviRules:

https://lab.civicrm.org/extensions/civirules/blob/master/CRM/Civirules/Utils/HookInvoker.php

Hope this helps :-)

6

All thanks to the code example link in Eric's answer here's a simplified example of how to offer up a hook (my_new_hook) which takes one argument, $the_argument:

CRM_Utils_Hook::singleton()->invoke(
  1, $the_argument,
  $dummy, $dummy, $dummy, $dummy, $dummy,
  'my_new_hook');                                                                      

Then other extensions can include functions like

function myextension_my_new_hook($arg) {
  do_something_with($arg);
}

The $dummy is just because we must pass a variable (since it's passed by reference), but this hook does not need it. If you had a hook that took two arguments, you'd use 2 in place of 1 and replace the first $dummy with your actual second argument etc. up to 6.

Nb. this code assumes CiviCRM 4.5+, but obviously you're not using anything older than that these days, right?!

  • 1
    Instead of $dummy you could use CRM_Core_DAO::$_nullObject – Jaap Jansma - CiviCooP May 24 '18 at 14:19
  • Yep, but not sure what the advantage of that is? – artfulrobot May 25 '18 at 13:41
  • There is no advantage except that is somehow a standard way of doing such things in CiviCRM. – Jaap Jansma - CiviCooP May 28 '18 at 7:22
  • 1
    Standards are important, I agree. However in this case it is a little odd as the reason parameters have to be passed in this way is because they are passed by reference so that the function receiving them can change them. Passing a static variable could result in that static var being changed, so I think the way I've done it is safer as well as clearer and more succinct. – artfulrobot Jun 4 '18 at 8:06
  • Ah I have never thought about your reasning and that makes sense. – Jaap Jansma - CiviCooP Jun 5 '18 at 7:53
3

Here is an example using the named parameter syntax:

CRM_Utils_Hook::singleton()->invoke(
  ['pdf', 'pdf_variables', 'receipt'],
  $pdf, $pdf_variables, $receipt,
  CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject,
  'cdntaxreceipts_writeReceipt');

One advantage of this syntax, is that extension can then implement the hook with the Symfony syntax. When managed by Symfony, it's easier to change the priority or add/remove hooks from other extensions.

Example:

/**
 * Implements hook_civicrm_container().
 *
 * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_container/
 */
function myext_civicrm_container($container) {
  // https://docs.civicrm.org/dev/en/latest/hooks/usage/symfony/
  $container->findDefinition('dispatcher')
    ->addMethodCall('addListener', ['hook_cdntaxreceipts_writeReceipt', 'myext_cdntaxreceipts_symfony_writeReceipt']);
}

/**
 * Implements hook_cdntaxreceipts_writeReceipt() via Symfony.
 *
 * @see myext_civicrm_container()
 */
function myext_cdntaxreceipts_symfony_writeReceipt($event) {
  // Access the parameters with:
  // $event->pdf;
  // $event->pdf_variables;
  // $event->receipt;
}

Note that the implementing function myext_cdntaxreceipts_symfony_writeReceipt has symfony in the name only to avoid being called by the traditional hook system. We can name the function however we want.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.