3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
12 namespace Civi\Core\Event
;
15 * Class GenericHookEvent
16 * @package Civi\API\Event
18 * The GenericHookEvent is used to expose all traditional hooks to the
19 * Symfony EventDispatcher.
21 * The traditional notation for a hook is based on a function signature:
23 * function hook_civicrm_foo($bar, &$whiz, &$bang);
25 * The notation for Symfony Events is based on a class with properties
26 * and methods. This requires some kind of mapping. `GenericHookEvent`
27 * maps each parameter to a field (using magic methods):
30 * // Creating an event object.
31 * $event = GenericHookEvent::create(array(
37 * // Accessing event properties.
39 * $event->whiz['array_field'] = 123;
40 * $event->bang->objProperty = 'abcd';
42 * // Dispatching an event.
43 * Civi::dispatcher()->dispatch('hook_civicrm_foo', $event);
48 * 1. Implementing new event classes for every hook would produce a
49 * large amount of boilerplate. Symfony Events have an interesting solution to
50 * that problem: use `GenericEvent` instead of custom event classes.
51 * `GenericHookEvent` is conceptually similar to `GenericEvent`, but it adds
52 * support for (a) altering properties and (b) mapping properties to hook notation
53 * (an ordered parameter list).
55 * 2. A handful of hooks define a return-value. The return-value is treated
56 * as an array, and all the returned values are merged into one big array.
57 * You can add and retrieve return-values using these methods:
60 * $event->addReturnValues(array(...));
61 * foreach ($event->getReturnValues() as $retVal) { ... }
64 class GenericHookEvent
extends \Symfony\Component\EventDispatcher\Event
{
68 * Ex: array(0 => &$contactID, 1 => &$contentPlacement).
70 protected $hookValues;
74 * Ex: array(0 => 'contactID', 1 => 'contentPlacement').
76 protected $hookFields;
80 * Ex: array('contactID' => 0, 'contentPlacement' => 1).
82 protected $hookFieldsFlip;
85 * Some legacy hooks expect listener-functions to return a value.
86 * OOP listeners may set the $returnValue.
88 * This field is not recommended for use in new hooks. The return-value
89 * convention is not portable across different implementations of the hook
90 * system. Instead, it's more portable to provide an alterable, named field.
95 private $returnValues = [];
98 * List of field names that are prohibited due to conflicts
99 * in the class-hierarchy.
103 private static $BLACKLIST = [
106 'propagationStopped',
114 * Create a GenericHookEvent using key-value pairs.
116 * @param array $params
117 * Ex: array('contactID' => &$contactID, 'contentPlacement' => &$contentPlacement).
118 * @return \Civi\Core\Event\GenericHookEvent
120 public static function create($params) {
122 $e->hookValues
= array_values($params);
123 $e->hookFields
= array_keys($params);
124 $e->hookFieldsFlip
= array_flip($e->hookFields
);
125 self
::assertValidHookFields($e->hookFields
);
130 * Create a GenericHookEvent using ordered parameters.
132 * @param array $hookFields
133 * Ex: array(0 => 'contactID', 1 => 'contentPlacement').
134 * @param array $hookValues
135 * Ex: array(0 => &$contactID, 1 => &$contentPlacement).
136 * @return \Civi\Core\Event\GenericHookEvent
138 public static function createOrdered($hookFields, $hookValues) {
140 if (count($hookValues) > count($hookFields)) {
141 $hookValues = array_slice($hookValues, 0, count($hookFields));
143 $e->hookValues
= $hookValues;
144 $e->hookFields
= $hookFields;
145 $e->hookFieldsFlip
= array_flip($e->hookFields
);
146 self
::assertValidHookFields($e->hookFields
);
151 * @param array $fields
152 * List of field names.
154 private static function assertValidHookFields($fields) {
155 $bad = array_intersect($fields, self
::$BLACKLIST);
157 throw new \
RuntimeException("Hook relies on conflicted field names: "
158 . implode(', ', $bad));
164 * Ex: array(0 => &$contactID, 1 => &$contentPlacement).
166 public function getHookValues() {
167 return $this->hookValues
;
174 public function getReturnValues() {
175 return empty($this->returnValues
) ?
TRUE : $this->returnValues
;
179 * @param mixed $fResult
180 * @return GenericHookEvent
183 public function addReturnValues($fResult) {
184 if (!empty($fResult) && is_array($fResult)) {
185 $this->returnValues
= array_merge($this->returnValues
, $fResult);
193 public function &__get($name) {
194 if (isset($this->hookFieldsFlip
[$name])) {
195 return $this->hookValues
[$this->hookFieldsFlip
[$name]];
202 public function __set($name, $value) {
203 if (isset($this->hookFieldsFlip
[$name])) {
204 $this->hookValues
[$this->hookFieldsFlip
[$name]] = $value;
211 public function __isset($name) {
212 return isset($this->hookFieldsFlip
[$name])
213 && isset($this->hookValues
[$this->hookFieldsFlip
[$name]]);
219 public function __unset($name) {
220 if (isset($this->hookFieldsFlip
[$name])) {
221 // Unset while preserving order.
222 $this->hookValues
[$this->hookFieldsFlip
[$name]] = NULL;
227 * Determine whether the hook supports the given field.
229 * The field may or may not be empty. Use isset() or empty() to
232 * @param string $name
235 public function hasField($name) {
236 return isset($this->hookFieldsFlip
[$name]);