|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_EventManager |
|
17 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) |
|
18 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
19 */ |
|
20 |
|
21 require_once 'Zend/EventManager/Event.php'; |
|
22 require_once 'Zend/EventManager/EventCollection.php'; |
|
23 require_once 'Zend/EventManager/ResponseCollection.php'; |
|
24 require_once 'Zend/EventManager/SharedEventCollectionAware.php'; |
|
25 require_once 'Zend/EventManager/StaticEventManager.php'; |
|
26 require_once 'Zend/Stdlib/CallbackHandler.php'; |
|
27 require_once 'Zend/Stdlib/PriorityQueue.php'; |
|
28 |
|
29 /** |
|
30 * Event manager: notification system |
|
31 * |
|
32 * Use the EventManager when you want to create a per-instance notification |
|
33 * system for your objects. |
|
34 * |
|
35 * @category Zend |
|
36 * @package Zend_EventManager |
|
37 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) |
|
38 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
39 */ |
|
40 class Zend_EventManager_EventManager implements Zend_EventManager_EventCollection, Zend_EventManager_SharedEventCollectionAware |
|
41 { |
|
42 /** |
|
43 * Subscribed events and their listeners |
|
44 * @var array Array of Zend_Stdlib_PriorityQueue objects |
|
45 */ |
|
46 protected $events = array(); |
|
47 |
|
48 /** |
|
49 * @var string Class representing the event being emitted |
|
50 */ |
|
51 protected $eventClass = 'Zend_EventManager_Event'; |
|
52 |
|
53 /** |
|
54 * Identifiers, used to pull static signals from StaticEventManager |
|
55 * @var array |
|
56 */ |
|
57 protected $identifiers = array(); |
|
58 |
|
59 /** |
|
60 * Static collections |
|
61 * @var false|null|Zend_EventManager_StaticEventCollection |
|
62 */ |
|
63 protected $sharedCollections = null; |
|
64 |
|
65 /** |
|
66 * Constructor |
|
67 * |
|
68 * Allows optionally specifying identifier(s) to use to pull signals from a |
|
69 * StaticEventManager. |
|
70 * |
|
71 * @param null|string|int|array|Traversable $identifiers |
|
72 * @return void |
|
73 */ |
|
74 public function __construct($identifiers = null) |
|
75 { |
|
76 $this->setIdentifiers($identifiers); |
|
77 } |
|
78 |
|
79 /** |
|
80 * Set the event class to utilize |
|
81 * |
|
82 * @param string $class |
|
83 * @return Zend_EventManager_EventManager |
|
84 */ |
|
85 public function setEventClass($class) |
|
86 { |
|
87 $this->eventClass = $class; |
|
88 return $this; |
|
89 } |
|
90 |
|
91 /** |
|
92 * Set static collections container |
|
93 * |
|
94 * @param Zend_EventManager_StaticEventCollection $collections |
|
95 * @return void |
|
96 */ |
|
97 public function setSharedCollections(Zend_EventManager_SharedEventCollection $collections) |
|
98 { |
|
99 $this->sharedCollections = $collections; |
|
100 return $this; |
|
101 } |
|
102 |
|
103 /** |
|
104 * Remove any shared collections |
|
105 * |
|
106 * Sets {@link $sharedCollections} to boolean false to disable ability |
|
107 * to lazy-load static event manager instance. |
|
108 * |
|
109 * @return void |
|
110 */ |
|
111 public function unsetSharedCollections() |
|
112 { |
|
113 $this->sharedCollections = false; |
|
114 } |
|
115 |
|
116 /** |
|
117 * Get static collections container |
|
118 * |
|
119 * @return false|Zend_EventManager_SharedEventCollection |
|
120 */ |
|
121 public function getSharedCollections() |
|
122 { |
|
123 if (null === $this->sharedCollections) { |
|
124 $this->setSharedCollections(Zend_EventManager_StaticEventManager::getInstance()); |
|
125 } |
|
126 return $this->sharedCollections; |
|
127 } |
|
128 |
|
129 /** |
|
130 * Get the identifier(s) for this Zend_EventManager_EventManager |
|
131 * |
|
132 * @return array |
|
133 */ |
|
134 public function getIdentifiers() |
|
135 { |
|
136 return $this->identifiers; |
|
137 } |
|
138 |
|
139 /** |
|
140 * Set the identifiers (overrides any currently set identifiers) |
|
141 * |
|
142 * @param string|int|array|Traversable $identifiers |
|
143 * @return Zend_EventManager_EventManager |
|
144 */ |
|
145 public function setIdentifiers($identifiers) |
|
146 { |
|
147 if (is_array($identifiers) || $identifiers instanceof Traversable) { |
|
148 $this->identifiers = array_unique((array) $identifiers); |
|
149 } elseif ($identifiers !== null) { |
|
150 $this->identifiers = array($identifiers); |
|
151 } |
|
152 return $this; |
|
153 } |
|
154 |
|
155 /** |
|
156 * Add some identifier(s) (appends to any currently set identifiers) |
|
157 * |
|
158 * @param string|int|array|Traversable $identifiers |
|
159 * @return Zend_EventManager_EventManager |
|
160 */ |
|
161 public function addIdentifiers($identifiers) |
|
162 { |
|
163 if (is_array($identifiers) || $identifiers instanceof Traversable) { |
|
164 $this->identifiers = array_unique($this->identifiers + (array) $identifiers); |
|
165 } elseif ($identifiers !== null) { |
|
166 $this->identifiers = array_unique(array_merge($this->identifiers, array($identifiers))); |
|
167 } |
|
168 return $this; |
|
169 } |
|
170 |
|
171 /** |
|
172 * Trigger all listeners for a given event |
|
173 * |
|
174 * Can emulate triggerUntil() if the last argument provided is a callback. |
|
175 * |
|
176 * @param string $event |
|
177 * @param string|object $target Object calling emit, or symbol describing target (such as static method name) |
|
178 * @param array|ArrayAccess $argv Array of arguments; typically, should be associative |
|
179 * @param null|callback $callback |
|
180 * @return Zend_EventManager_ResponseCollection All listener return values |
|
181 */ |
|
182 public function trigger($event, $target = null, $argv = array(), $callback = null) |
|
183 { |
|
184 if ($event instanceof Zend_EventManager_EventDescription) { |
|
185 $e = $event; |
|
186 $event = $e->getName(); |
|
187 $callback = $target; |
|
188 } elseif ($target instanceof Zend_EventManager_EventDescription) { |
|
189 $e = $target; |
|
190 $e->setName($event); |
|
191 $callback = $argv; |
|
192 } elseif ($argv instanceof Zend_EventManager_EventDescription) { |
|
193 $e = $argv; |
|
194 $e->setName($event); |
|
195 $e->setTarget($target); |
|
196 } else { |
|
197 $e = new $this->eventClass(); |
|
198 $e->setName($event); |
|
199 $e->setTarget($target); |
|
200 $e->setParams($argv); |
|
201 } |
|
202 |
|
203 if ($callback && !is_callable($callback)) { |
|
204 require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php'; |
|
205 throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided'); |
|
206 } |
|
207 |
|
208 return $this->triggerListeners($event, $e, $callback); |
|
209 } |
|
210 |
|
211 /** |
|
212 * Trigger listeners until return value of one causes a callback to |
|
213 * evaluate to true |
|
214 * |
|
215 * Triggers listeners until the provided callback evaluates the return |
|
216 * value of one as true, or until all listeners have been executed. |
|
217 * |
|
218 * @param string $event |
|
219 * @param string|object $target Object calling emit, or symbol describing target (such as static method name) |
|
220 * @param array|ArrayAccess $argv Array of arguments; typically, should be associative |
|
221 * @param Callable $callback |
|
222 * @throws Zend_Stdlib_Exception_InvalidCallbackException if invalid callback provided |
|
223 */ |
|
224 public function triggerUntil($event, $target, $argv = null, $callback = null) |
|
225 { |
|
226 if ($event instanceof Zend_EventManager_EventDescription) { |
|
227 $e = $event; |
|
228 $event = $e->getName(); |
|
229 $callback = $target; |
|
230 } elseif ($target instanceof Zend_EventManager_EventDescription) { |
|
231 $e = $target; |
|
232 $e->setName($event); |
|
233 $callback = $argv; |
|
234 } elseif ($argv instanceof Zend_EventManager_EventDescription) { |
|
235 $e = $argv; |
|
236 $e->setName($event); |
|
237 $e->setTarget($target); |
|
238 } else { |
|
239 $e = new $this->eventClass(); |
|
240 $e->setName($event); |
|
241 $e->setTarget($target); |
|
242 $e->setParams($argv); |
|
243 } |
|
244 |
|
245 if (!is_callable($callback)) { |
|
246 require_once 'Zend/Stdlib/Exception/InvalidCallbackException.php'; |
|
247 throw new Zend_Stdlib_Exception_InvalidCallbackException('Invalid callback provided'); |
|
248 } |
|
249 |
|
250 return $this->triggerListeners($event, $e, $callback); |
|
251 } |
|
252 |
|
253 /** |
|
254 * Attach a listener to an event |
|
255 * |
|
256 * The first argument is the event, and the next argument describes a |
|
257 * callback that will respond to that event. A CallbackHandler instance |
|
258 * describing the event listener combination will be returned. |
|
259 * |
|
260 * The last argument indicates a priority at which the event should be |
|
261 * executed. By default, this value is 1; however, you may set it for any |
|
262 * integer value. Higher values have higher priority (i.e., execute first). |
|
263 * |
|
264 * You can specify "*" for the event name. In such cases, the listener will |
|
265 * be triggered for every event. |
|
266 * |
|
267 * @param string|array|Zend_EventManager_ListenerAggregate $event An event or array of event names. If a ListenerAggregate, proxies to {@link attachAggregate()}. |
|
268 * @param callback|int $callback If string $event provided, expects PHP callback; for a ListenerAggregate $event, this will be the priority |
|
269 * @param int $priority If provided, the priority at which to register the callback |
|
270 * @return Zend_Stdlib_CallbackHandler|mixed CallbackHandler if attaching callback (to allow later unsubscribe); mixed if attaching aggregate |
|
271 */ |
|
272 public function attach($event, $callback = null, $priority = 1) |
|
273 { |
|
274 // Proxy ListenerAggregate arguments to attachAggregate() |
|
275 if ($event instanceof Zend_EventManager_ListenerAggregate) { |
|
276 return $this->attachAggregate($event, $callback); |
|
277 } |
|
278 |
|
279 // Null callback is invalid |
|
280 if (null === $callback) { |
|
281 require_once 'Zend/EventManager/Exception/InvalidArgumentException.php'; |
|
282 throw new Zend_EventManager_Exception_InvalidArgumentException(sprintf( |
|
283 '%s: expects a callback; none provided', |
|
284 __METHOD__ |
|
285 )); |
|
286 } |
|
287 |
|
288 // Array of events should be registered individually, and return an array of all listeners |
|
289 if (is_array($event)) { |
|
290 $listeners = array(); |
|
291 foreach ($event as $name) { |
|
292 $listeners[] = $this->attach($name, $callback, $priority); |
|
293 } |
|
294 return $listeners; |
|
295 } |
|
296 |
|
297 // If we don't have a priority queue for the event yet, create one |
|
298 if (empty($this->events[$event])) { |
|
299 $this->events[$event] = new Zend_Stdlib_PriorityQueue(); |
|
300 } |
|
301 |
|
302 // Create a callback handler, setting the event and priority in its metadata |
|
303 $listener = new Zend_Stdlib_CallbackHandler($callback, array('event' => $event, 'priority' => $priority)); |
|
304 |
|
305 // Inject the callback handler into the queue |
|
306 $this->events[$event]->insert($listener, $priority); |
|
307 return $listener; |
|
308 } |
|
309 |
|
310 /** |
|
311 * Attach a listener aggregate |
|
312 * |
|
313 * Listener aggregates accept an EventCollection instance, and call attach() |
|
314 * one or more times, typically to attach to multiple events using local |
|
315 * methods. |
|
316 * |
|
317 * @param Zend_EventManager_ListenerAggregate $aggregate |
|
318 * @param int $priority If provided, a suggested priority for the aggregate to use |
|
319 * @return mixed return value of {@link Zend_EventManager_ListenerAggregate::attach()} |
|
320 */ |
|
321 public function attachAggregate(Zend_EventManager_ListenerAggregate $aggregate, $priority = 1) |
|
322 { |
|
323 return $aggregate->attach($this, $priority); |
|
324 } |
|
325 |
|
326 /** |
|
327 * Unsubscribe a listener from an event |
|
328 * |
|
329 * @param Zend_Stdlib_CallbackHandler|Zend_EventManager_ListenerAggregate $listener |
|
330 * @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found |
|
331 * @throws Zend_EventManager_Exception_InvalidArgumentException if invalid listener provided |
|
332 */ |
|
333 public function detach($listener) |
|
334 { |
|
335 if ($listener instanceof Zend_EventManager_ListenerAggregate) { |
|
336 return $this->detachAggregate($listener); |
|
337 } |
|
338 |
|
339 if (!$listener instanceof Zend_Stdlib_CallbackHandler) { |
|
340 require_once 'Zend/EventManager/Exception/InvalidArgumentException.php'; |
|
341 throw new Zend_EventManager_Exception_InvalidArgumentException(sprintf( |
|
342 '%s: expected a Zend_EventManager_ListenerAggregate or Zend_Stdlib_CallbackHandler; received "%s"', |
|
343 __METHOD__, |
|
344 (is_object($listener) ? get_class($listener) : gettype($listener)) |
|
345 )); |
|
346 } |
|
347 |
|
348 $event = $listener->getMetadatum('event'); |
|
349 if (!$event || empty($this->events[$event])) { |
|
350 return false; |
|
351 } |
|
352 $return = $this->events[$event]->remove($listener); |
|
353 if (!$return) { |
|
354 return false; |
|
355 } |
|
356 if (!count($this->events[$event])) { |
|
357 unset($this->events[$event]); |
|
358 } |
|
359 return true; |
|
360 } |
|
361 |
|
362 /** |
|
363 * Detach a listener aggregate |
|
364 * |
|
365 * Listener aggregates accept an EventCollection instance, and call detach() |
|
366 * of all previously attached listeners. |
|
367 * |
|
368 * @param Zend_EventManager_ListenerAggregate $aggregate |
|
369 * @return mixed return value of {@link Zend_EventManager_ListenerAggregate::detach()} |
|
370 */ |
|
371 public function detachAggregate(Zend_EventManager_ListenerAggregate $aggregate) |
|
372 { |
|
373 return $aggregate->detach($this); |
|
374 } |
|
375 |
|
376 /** |
|
377 * Retrieve all registered events |
|
378 * |
|
379 * @return array |
|
380 */ |
|
381 public function getEvents() |
|
382 { |
|
383 return array_keys($this->events); |
|
384 } |
|
385 |
|
386 /** |
|
387 * Retrieve all listeners for a given event |
|
388 * |
|
389 * @param string $event |
|
390 * @return Zend_Stdlib_PriorityQueue |
|
391 */ |
|
392 public function getListeners($event) |
|
393 { |
|
394 if (!array_key_exists($event, $this->events)) { |
|
395 return new Zend_Stdlib_PriorityQueue(); |
|
396 } |
|
397 return $this->events[$event]; |
|
398 } |
|
399 |
|
400 /** |
|
401 * Clear all listeners for a given event |
|
402 * |
|
403 * @param string $event |
|
404 * @return void |
|
405 */ |
|
406 public function clearListeners($event) |
|
407 { |
|
408 if (!empty($this->events[$event])) { |
|
409 unset($this->events[$event]); |
|
410 } |
|
411 } |
|
412 |
|
413 /** |
|
414 * Prepare arguments |
|
415 * |
|
416 * Use this method if you want to be able to modify arguments from within a |
|
417 * listener. It returns an ArrayObject of the arguments, which may then be |
|
418 * passed to trigger() or triggerUntil(). |
|
419 * |
|
420 * @param array $args |
|
421 * @return ArrayObject |
|
422 */ |
|
423 public function prepareArgs(array $args) |
|
424 { |
|
425 return new ArrayObject($args); |
|
426 } |
|
427 |
|
428 /** |
|
429 * Trigger listeners |
|
430 * |
|
431 * Actual functionality for triggering listeners, to which both trigger() and triggerUntil() |
|
432 * delegate. |
|
433 * |
|
434 * @param string $event Event name |
|
435 * @param EventDescription $e |
|
436 * @param null|callback $callback |
|
437 * @return ResponseCollection |
|
438 */ |
|
439 protected function triggerListeners($event, Zend_EventManager_EventDescription $e, $callback = null) |
|
440 { |
|
441 $responses = new Zend_EventManager_ResponseCollection; |
|
442 $listeners = $this->getListeners($event); |
|
443 |
|
444 // Add shared/wildcard listeners to the list of listeners, |
|
445 // but don't modify the listeners object |
|
446 $sharedListeners = $this->getSharedListeners($event); |
|
447 $sharedWildcardListeners = $this->getSharedListeners('*'); |
|
448 $wildcardListeners = $this->getListeners('*'); |
|
449 if (count($sharedListeners) || count($sharedWildcardListeners) || count($wildcardListeners)) { |
|
450 $listeners = clone $listeners; |
|
451 } |
|
452 |
|
453 // Shared listeners on this specific event |
|
454 $this->insertListeners($listeners, $sharedListeners); |
|
455 |
|
456 // Shared wildcard listeners |
|
457 $this->insertListeners($listeners, $sharedWildcardListeners); |
|
458 |
|
459 // Add wildcard listeners |
|
460 $this->insertListeners($listeners, $wildcardListeners); |
|
461 |
|
462 if ($listeners->isEmpty()) { |
|
463 return $responses; |
|
464 } |
|
465 |
|
466 foreach ($listeners as $listener) { |
|
467 // Trigger the listener's callback, and push its result onto the |
|
468 // response collection |
|
469 $responses->push(call_user_func($listener->getCallback(), $e)); |
|
470 |
|
471 // If the event was asked to stop propagating, do so |
|
472 if ($e->propagationIsStopped()) { |
|
473 $responses->setStopped(true); |
|
474 break; |
|
475 } |
|
476 |
|
477 // If the result causes our validation callback to return true, |
|
478 // stop propagation |
|
479 if ($callback && call_user_func($callback, $responses->last())) { |
|
480 $responses->setStopped(true); |
|
481 break; |
|
482 } |
|
483 } |
|
484 |
|
485 return $responses; |
|
486 } |
|
487 |
|
488 /** |
|
489 * Get list of all listeners attached to the shared collection for |
|
490 * identifiers registered by this instance |
|
491 * |
|
492 * @param string $event |
|
493 * @return array |
|
494 */ |
|
495 protected function getSharedListeners($event) |
|
496 { |
|
497 if (!$sharedCollections = $this->getSharedCollections()) { |
|
498 return array(); |
|
499 } |
|
500 |
|
501 $identifiers = $this->getIdentifiers(); |
|
502 $sharedListeners = array(); |
|
503 |
|
504 foreach ($identifiers as $id) { |
|
505 if (!$listeners = $sharedCollections->getListeners($id, $event)) { |
|
506 continue; |
|
507 } |
|
508 |
|
509 if (!is_array($listeners) && !($listeners instanceof Traversable)) { |
|
510 continue; |
|
511 } |
|
512 |
|
513 foreach ($listeners as $listener) { |
|
514 if (!$listener instanceof Zend_Stdlib_CallbackHandler) { |
|
515 continue; |
|
516 } |
|
517 $sharedListeners[] = $listener; |
|
518 } |
|
519 } |
|
520 |
|
521 return $sharedListeners; |
|
522 } |
|
523 |
|
524 /** |
|
525 * Add listeners to the master queue of listeners |
|
526 * |
|
527 * Used to inject shared listeners and wildcard listeners. |
|
528 * |
|
529 * @param Zend_Stdlib_PriorityQueue $masterListeners |
|
530 * @param Zend_Stdlib_PriorityQueue $listeners |
|
531 * @return void |
|
532 */ |
|
533 protected function insertListeners($masterListeners, $listeners) |
|
534 { |
|
535 if (!count($listeners)) { |
|
536 return; |
|
537 } |
|
538 |
|
539 foreach ($listeners as $listener) { |
|
540 $priority = $listener->getMetadatum('priority'); |
|
541 if (null === $priority) { |
|
542 $priority = 1; |
|
543 } elseif (is_array($priority)) { |
|
544 // If we have an array, likely using PriorityQueue. Grab first |
|
545 // element of the array, as that's the actual priority. |
|
546 $priority = array_shift($priority); |
|
547 } |
|
548 $masterListeners->insert($listener, $priority); |
|
549 } |
|
550 } |
|
551 } |