'abc', * 'whiz' => &$whiz, * 'bang' => &$bang, * ); * * // Accessing event properties. * echo $event->bar; * $event->whiz['array_field'] = 123; * $event->bang->objProperty = 'abcd'; * * // Dispatching an event. * Civi::service('dispatcher')->dispatch('hook_civicrm_foo', $event); * @endCode * * Design Discussion: * * 1. Implementing new event classes for every hook would produce a * large amount of boilerplate. Symfony Events have an interesting solution to * that problem: use `GenericEvent` instead of custom event classes. * `GenericHookEvent` is conceptually similar to `GenericEvent`, but it adds * support for (a) altering properties and (b) mapping properties to hook notation * (an ordered parameter list). * * 2. A handful of hooks define a return-value. The return-value is treated * as an array, and all the returned values are merged into one big array. * You can add and retrieve return-values using these methods: * * @code * $event->addReturnValues(array(...)); * foreach ($event->getReturnValues() as $retVal) { ... } * @endCode */ class GenericHookEvent extends \Symfony\Component\EventDispatcher\Event { /** * @var array * Ex: array(0 => &$contactID, 1 => &$contentPlacement). */ protected $hookValues; /** * @var array * Ex: array(0 => 'contactID', 1 => 'contentPlacement'). */ protected $hookFields; /** * @var array * Ex: array('contactID' => 0, 'contentPlacement' => 1). */ protected $hookFieldsFlip; /** * Some legacy hooks expect listener-functions to return a value. * OOP listeners may set the $returnValue. * * This field is not recommended for use in new hooks. The return-value * convention is not portable across different implementations of the hook * system. Instead, it's more portable to provide an alterable, named field. * * @var mixed * @deprecated */ private $returnValues = array(); /** * List of field names that are prohibited due to conflicts * in the class-hierarchy. * * @var array */ private static $BLACKLIST = array( 'name', 'dispatcher', 'propagationStopped', 'hookBlacklist', 'hookValues', 'hookFields', 'hookFieldsFlip', ); /** * Create a GenericHookEvent using key-value pairs. * * @param array $params * Ex: array('contactID' => &$contactID, 'contentPlacement' => &$contentPlacement). * @return \Civi\Core\Event\GenericHookEvent */ public static function create($params) { $e = new static(); $e->hookValues = array_values($params); $e->hookFields = array_keys($params); $e->hookFieldsFlip = array_flip($e->hookFields); self::assertValidHookFields($e->hookFields); return $e; } /** * Create a GenericHookEvent using ordered parameters. * * @param array $hookFields * Ex: array(0 => 'contactID', 1 => 'contentPlacement'). * @param array $hookValues * Ex: array(0 => &$contactID, 1 => &$contentPlacement). * @return \Civi\Core\Event\GenericHookEvent */ public static function createOrdered($hookFields, $hookValues) { $e = new static(); if (count($hookValues) > count($hookFields)) { $hookValues = array_slice($hookValues, 0, count($hookFields)); } $e->hookValues = $hookValues; $e->hookFields = $hookFields; $e->hookFieldsFlip = array_flip($e->hookFields); self::assertValidHookFields($e->hookFields); return $e; } /** * @param array $fields * List of field names. */ private static function assertValidHookFields($fields) { $bad = array_intersect($fields, self::$BLACKLIST); if ($bad) { throw new \RuntimeException("Hook relies on conflicted field names: " . implode(', ', $bad)); } } /** * @return array * Ex: array(0 => &$contactID, 1 => &$contentPlacement). */ public function getHookValues() { return $this->hookValues; } /** * @return mixed * @deprecated */ public function getReturnValues() { return empty($this->returnValues) ? TRUE : $this->returnValues; } /** * @param mixed $fResult * @return GenericHookEvent * @deprecated */ public function addReturnValues($fResult) { if (!empty($fResult) && is_array($fResult)) { $this->returnValues = array_merge($this->returnValues, $fResult); } return $this; } /** * @inheritDoc */ public function &__get($name) { if (isset($this->hookFieldsFlip[$name])) { return $this->hookValues[$this->hookFieldsFlip[$name]]; } } /** * @inheritDoc */ public function __set($name, $value) { if (isset($this->hookFieldsFlip[$name])) { $this->hookValues[$this->hookFieldsFlip[$name]] = $value; } } /** * @inheritDoc */ public function __isset($name) { return isset($this->hookFieldsFlip[$name]) && isset($this->hookValues[$this->hookFieldsFlip[$name]]); } /** * @inheritDoc */ public function __unset($name) { if (isset($this->hookFieldsFlip[$name])) { // Unset while preserving order. $this->hookValues[$this->hookFieldsFlip[$name]] = NULL; } } /** * Determine whether the hook supports the given field. * * The field may or may not be empty. Use isset() or empty() to * check that. * * @param string $name * @return bool */ public function hasField($name) { return isset($this->hookFieldsFlip[$name]); } }