NFC - Short array syntax - auto-convert Civi dir
[civicrm-core.git] / Civi / Core / Event / GenericHookEvent.php
CommitLineData
762dc04d
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
762dc04d 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
762dc04d
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28namespace Civi\Core\Event;
29
30/**
31 * Class GenericHookEvent
32 * @package Civi\API\Event
33 *
34 * The GenericHookEvent is used to expose all traditional hooks to the
35 * Symfony EventDispatcher.
36 *
37 * The traditional notation for a hook is based on a function signature:
38 *
39 * function hook_civicrm_foo($bar, &$whiz, &$bang);
40 *
0d681395
TO
41 * The notation for Symfony Events is based on a class with properties
42 * and methods. This requires some kind of mapping. `GenericHookEvent`
43 * maps each parameter to a field (using magic methods):
762dc04d
TO
44 *
45 * @code
0d681395 46 * // Creating an event object.
762dc04d 47 * $event = GenericHookEvent::create(array(
0d681395
TO
48 * 'bar' => 'abc',
49 * 'whiz' => &$whiz,
50 * 'bang' => &$bang,
51 * );
52 *
53 * // Accessing event properties.
54 * echo $event->bar;
55 * $event->whiz['array_field'] = 123;
56 * $event->bang->objProperty = 'abcd';
57 *
58 * // Dispatching an event.
762dc04d 59 * Civi::service('dispatcher')->dispatch('hook_civicrm_foo', $event);
0d681395
TO
60 * @endCode
61 *
62 * Design Discussion:
63 *
64 * 1. Implementing new event classes for every hook would produce a
65 * large amount of boilerplate. Symfony Events have an interesting solution to
66 * that problem: use `GenericEvent` instead of custom event classes.
67 * `GenericHookEvent` is conceptually similar to `GenericEvent`, but it adds
68 * support for (a) altering properties and (b) mapping properties to hook notation
69 * (an ordered parameter list).
70 *
71 * 2. A handful of hooks define a return-value. The return-value is treated
72 * as an array, and all the returned values are merged into one big array.
73 * You can add and retrieve return-values using these methods:
74 *
75 * @code
76 * $event->addReturnValues(array(...));
77 * foreach ($event->getReturnValues() as $retVal) { ... }
762dc04d
TO
78 * @endCode
79 */
80class GenericHookEvent extends \Symfony\Component\EventDispatcher\Event {
81
82 /**
83 * @var array
0d681395 84 * Ex: array(0 => &$contactID, 1 => &$contentPlacement).
762dc04d
TO
85 */
86 protected $hookValues;
87
88 /**
89 * @var array
90 * Ex: array(0 => 'contactID', 1 => 'contentPlacement').
91 */
92 protected $hookFields;
93
94 /**
95 * @var array
96 * Ex: array('contactID' => 0, 'contentPlacement' => 1).
97 */
98 protected $hookFieldsFlip;
99
100 /**
101 * Some legacy hooks expect listener-functions to return a value.
102 * OOP listeners may set the $returnValue.
103 *
104 * This field is not recommended for use in new hooks. The return-value
105 * convention is not portable across different implementations of the hook
106 * system. Instead, it's more portable to provide an alterable, named field.
107 *
108 * @var mixed
109 * @deprecated
110 */
c64f69d9 111 private $returnValues = [];
762dc04d 112
0fcad357
TO
113 /**
114 * List of field names that are prohibited due to conflicts
115 * in the class-hierarchy.
116 *
117 * @var array
118 */
c64f69d9 119 private static $BLACKLIST = [
0fcad357
TO
120 'name',
121 'dispatcher',
122 'propagationStopped',
123 'hookBlacklist',
124 'hookValues',
125 'hookFields',
126 'hookFieldsFlip',
c64f69d9 127 ];
0fcad357 128
762dc04d
TO
129 /**
130 * Create a GenericHookEvent using key-value pairs.
131 *
132 * @param array $params
133 * Ex: array('contactID' => &$contactID, 'contentPlacement' => &$contentPlacement).
134 * @return \Civi\Core\Event\GenericHookEvent
135 */
136 public static function create($params) {
137 $e = new static();
138 $e->hookValues = array_values($params);
139 $e->hookFields = array_keys($params);
140 $e->hookFieldsFlip = array_flip($e->hookFields);
0fcad357 141 self::assertValidHookFields($e->hookFields);
762dc04d
TO
142 return $e;
143 }
144
145 /**
146 * Create a GenericHookEvent using ordered parameters.
147 *
148 * @param array $hookFields
149 * Ex: array(0 => 'contactID', 1 => 'contentPlacement').
150 * @param array $hookValues
151 * Ex: array(0 => &$contactID, 1 => &$contentPlacement).
152 * @return \Civi\Core\Event\GenericHookEvent
153 */
154 public static function createOrdered($hookFields, $hookValues) {
155 $e = new static();
156 if (count($hookValues) > count($hookFields)) {
157 $hookValues = array_slice($hookValues, 0, count($hookFields));
158 }
159 $e->hookValues = $hookValues;
160 $e->hookFields = $hookFields;
161 $e->hookFieldsFlip = array_flip($e->hookFields);
0fcad357 162 self::assertValidHookFields($e->hookFields);
762dc04d
TO
163 return $e;
164 }
165
0fcad357
TO
166 /**
167 * @param array $fields
168 * List of field names.
169 */
170 private static function assertValidHookFields($fields) {
171 $bad = array_intersect($fields, self::$BLACKLIST);
172 if ($bad) {
173 throw new \RuntimeException("Hook relies on conflicted field names: "
174 . implode(', ', $bad));
175 }
176 }
177
762dc04d
TO
178 /**
179 * @return array
180 * Ex: array(0 => &$contactID, 1 => &$contentPlacement).
181 */
182 public function getHookValues() {
183 return $this->hookValues;
184 }
185
186 /**
187 * @return mixed
188 * @deprecated
189 */
190 public function getReturnValues() {
191 return empty($this->returnValues) ? TRUE : $this->returnValues;
192 }
193
194 /**
195 * @param mixed $fResult
196 * @return GenericHookEvent
197 * @deprecated
198 */
199 public function addReturnValues($fResult) {
200 if (!empty($fResult) && is_array($fResult)) {
201 $this->returnValues = array_merge($this->returnValues, $fResult);
202 }
203 return $this;
204 }
205
206 /**
207 * @inheritDoc
208 */
209 public function &__get($name) {
210 if (isset($this->hookFieldsFlip[$name])) {
211 return $this->hookValues[$this->hookFieldsFlip[$name]];
212 }
213 }
214
215 /**
216 * @inheritDoc
217 */
218 public function __set($name, $value) {
219 if (isset($this->hookFieldsFlip[$name])) {
220 $this->hookValues[$this->hookFieldsFlip[$name]] = $value;
221 }
222 }
223
224 /**
225 * @inheritDoc
226 */
227 public function __isset($name) {
228 return isset($this->hookFieldsFlip[$name])
229 && isset($this->hookValues[$this->hookFieldsFlip[$name]]);
230 }
231
232 /**
233 * @inheritDoc
234 */
235 public function __unset($name) {
236 if (isset($this->hookFieldsFlip[$name])) {
237 // Unset while preserving order.
238 $this->hookValues[$this->hookFieldsFlip[$name]] = NULL;
239 }
240 }
241
242 /**
243 * Determine whether the hook supports the given field.
244 *
245 * The field may or may not be empty. Use isset() or empty() to
246 * check that.
247 *
248 * @param string $name
249 * @return bool
250 */
251 public function hasField($name) {
252 return isset($this->hookFieldsFlip[$name]);
253 }
254
255}