Hooks Introduction
Overview¶
Hooks are a common way to extend systems. Let's say you want to send a message to someone in your organization every time a contact is created. An easy way to do this would be to insert code to send the message in the CiviCRM core code right where the contact is created. However, as soon as we upgrade to a newer version all this code will be overwritten. This is where hooks come in to save the day.
At key points in processing - for example saving something to the database - CiviCRM checks to see whether you've "hooked in" some custom code, and runs any valid code it finds.
Hooks allow you to do this by defining a function with a specific name and adding it to your organisation's CiviCRM installation. The name of the function indicates the point at which CiviCRM should call it. CiviCRM looks for appropriate function names and calls the functions whenever it performs the indicated operations.
Hooks are a powerful way to extend CiviCRM's functionality, incorporate additional business logic, and even integrate CiviCRM with external systems. Many CiviCRM developers find themselves using them in nearly every customization project.
Tip
A good test for whether or not to use a hook is to ask yourself whether what you're trying to do can be expressed with a sentence like this: "I want X to happen every time someone does Y."
Usage¶
There are two ways to use hooks: the traditional method and the Symfony events method.
Traditional method¶
The traditional method of using a hook is to create a function with a specific name such as:
function myextension_civicrm_buildForm($formName, &$form) {
// do something
}
This works well in many cases but has its limitations. For example, if two extensions call the same hook there is no way to determine which runs first.
For details, see Traditional Hooks
Symfony method¶
A newer method that provides greater flexibility is to use Symfony events.
For example:
Civi::dispatcher()->addListener('hook_civicrm_buildForm', "myextension_buildForm", $priority);
function myextension_buildForm($event) {
// do something
}
For more details see Hooks with Symfony
Targeting Certain Events¶
When you create a hook, it will be called for all the types of entities. For
instance, a civicrm_post
is called after the creation or modification of any
object of any type (contact, tag, group, activity, etc.). But usually, you want
to launch an action only for a specific type of entity.
So a hook generally starts with a test on the type of entity or type of action.
For instance, if you want to act only when an address was edited, start your
civicrm_post
hook with:
if ($objectName != "Address" || $op != "edit") {
return;
}
Pitfalls of Hooks¶
Because you have little control over what CiviCRM passes to your hook function,
it is very helpful to look inside those objects (especially $objectRef
) to
make sure you're getting what you expect.
While the hook contract specifies what arrays and objects will be passed out to your
hook the arrays and objects themselves may not be subject to an explicit contract
and could change over time. Where values are documented in the hook documentation or
you are using functions that have been given the docblock annotation @api
you
should have more certainty. In general, however, you are advised to ensure your custom code
has unit tests.
Supported form functions (these will not change in minor versions but they might become available across more forms)
Function | Used to | Version |
---|---|---|
getSubmittedValue() |
get a single submitted value (processed for money formatting) | 5.50-ish? |
getSubmittedValues() |
get all submitted values | 5.50-ish? |
getSubmittableValues() |
get all available submitted values | 5.50-ish? |
getAuthenticatedContactID() |
All | Get the id of the user authenticated by login or checksum |
getEventID() |
Get the Event ID in play | 5.66+ |
getEventValue() |
---- | 5.66ish? |
getDiscountID() |
Get the Discount ID (civicrm_discount.id) in play | 5.66+ |
getPriceSetID() |
---- | 5.66+ |
getParticipantID() |
---- | 5.66+ |
getParticipantValue() |
---- | 5.66ish? |
getEventValue() |
---- | 5.66ish? |
getContributionPageID() |
---- | 5.66ish? |
getContributionPageValue() |
---- | 5.66ish? |
getContactID() |
---- | 5.66+ |
isTest() |
---- | 5.69 added to contribution page flow |
getLineItems() |
---- | 5.69 added to contribution page flow |
setLineItems() |
---- | 5.69 added to contribution page flow |
A good debugger is indispensable here. See the page on debugging for more information on setting up a debugger for your development environment.
Warning
From time to time a new release of CiviCRM can deprecate or change certain hooks. Keep this in mind when upgrading, and make sure you check the release notes before upgrading.
Packaging Hooks¶
Hooks are packaged in CMS-agnostic extensions.
Organizing Your Hooks¶
You may find that some of your hooks target a lot of different cases. Such hooks can quickly get out of control, and maintaining them can be a nightmare.
You might find it helpful when implementing a hook to delegate certain operations to different functions instead of lumping it all in together in the main hook.
If you're using Civix to create your extension it will automatically generate wrapper code for your hook.