Behaviors
FormBuilder Behaviors¶
Overview¶
Simply put, a Behavior extends the functionality of an entity. Behaviors are PHP classes written by a developer, which can be enabled in the GUI to affect how entities behave on a form.
Each Behavior class declares some configuration metadata which automatically appears in the Afform GUI. The user can make selections to enable and configure the behavior.
To enact its functionality, a behavior class can listen to any Civi hook or event, notably civi.afform.prefill
and civi.afform.submit
.
These events are called for every entity on a form, and will include information such as the entity name, type, and any
behaviors that have been configured for that entity.
Examples¶
A good example to emulate is the Contact Dedupe Behavior.
class ContactDedupe extends AbstractBehavior implements EventSubscriberInterface {
It starts off by extending the AbstractBehavior
class, which makes it discoverable to Afform,
and also implementing EventSubscriberInterface
, which is the recommended way to subscribe to events. That interface
requires a getSubscribedEvents
function:
/**
* @return array
*/
public static function getSubscribedEvents() {
return [
'civi.afform.submit' => ['onAfformSubmit', 101],
];
}
That registers the callback function named onAfformSubmit
in the same class, to be called every time an Afform entity
is about to be saved.
The next 4 functions are part of the AfformBehavior
interface, and provide enough information for the AfformAdmin GUI
to show the behavior to the user for configuration:
public static function getEntities():array {
return \CRM_Contact_BAO_ContactType::basicTypes();
}
public static function getTitle():string {
return E::ts('Duplicate Matching');
}
public static function getDescription():string {
return E::ts('Update existing contact instead of creating a new one based on a dedupe rule.');
}
public static function getModes(string $entityName):array {
...
}
Note that getEntities
does not simply return "Contact" because Afform considers "Individual", "Household" and "Organization"
to all be their own entities.
The getModes
function returns an array of operation modes (in this case dedupe rules) for a particular entity type.
So if your Behavior can act on more than one entity type as this one can, pay attention to the $entityName
parameter
and only return modes relevant to that type of entity (in this case, there are different dedupe rules for "Individual" vs
"Organization", etc).
Finally, the callback registered with getSubscribedEvents
:
public static function onAfformSubmit(AfformSubmitEvent $event) {
$entity = $event->getEntity();
$dedupeMode = $entity['contact-dedupe'] ?? NULL;
if ($event->getEntityType() !== 'Contact' || !$dedupeMode) {
return;
}
// Apply dedupe rule if contact isn't already identified
foreach ($event->records as $index => $record) {
$supportedJoins = ['Address', 'Email', 'Phone', 'IM'];
$values = $record['fields'] ?? [];
foreach ($supportedJoins as $joinEntity) {
if (!empty($record['joins'][$joinEntity][0])) {
$values += \CRM_Utils_Array::prefixKeys($record['joins'][$joinEntity][0], strtolower($joinEntity) . '_primary.');
}
}
$match = Contact::getDuplicates(FALSE)
->setValues($values)
->setDedupeRule($dedupeMode)
->execute()->first();
if (!empty($match['id'])) {
$event->setEntityId($index, $match['id']);
}
}
}
This function checks the contact-dedupe
mode set by the Admin (this is a kebab-case version of the class name)
and takes action on every record being saved for that entity (normally one entity saves one record, but because of the
AfRepeat feature, entities should always be treated as if they may be multivalued).