PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/EventManager/EventManager.php

http://github.com/zendframework/zf2
PHP | 470 lines | 221 code | 40 blank | 209 comment | 32 complexity | 26b7acf47771ff10f357c3ea05f3ec86 MD5 | raw file
Possible License(s): BSD-3-Clause
  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. * @namespace
  22. */
  23. namespace Zend\EventManager;
  24. use Zend\Stdlib\CallbackHandler,
  25. Zend\Stdlib\Exception\InvalidCallbackException,
  26. Zend\Stdlib\PriorityQueue,
  27. ArrayObject,
  28. SplPriorityQueue,
  29. Traversable;
  30. /**
  31. * Event manager: notification system
  32. *
  33. * Use the EventManager when you want to create a per-instance notification
  34. * system for your objects.
  35. *
  36. * @category Zend
  37. * @package Zend_EventManager
  38. * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  39. * @license http://framework.zend.com/license/new-bsd New BSD License
  40. */
  41. class EventManager implements EventCollection
  42. {
  43. /**
  44. * Subscribed events and their listeners
  45. * @var array Array of PriorityQueue objects
  46. */
  47. protected $events = array();
  48. /**
  49. * @var string Class representing the event being emitted
  50. */
  51. protected $eventClass = 'Zend\EventManager\Event';
  52. /**
  53. * Identifiers, used to pull static signals from StaticEventManager
  54. * @var array
  55. */
  56. protected $identifiers = array();
  57. /**
  58. * Static connections
  59. * @var false|null|StaticEventCollection
  60. */
  61. protected $staticConnections = null;
  62. /**
  63. * Constructor
  64. *
  65. * Allows optionally specifying identifier(s) to use to pull signals from a
  66. * StaticEventManager.
  67. *
  68. * @param null|string|int|array|Traversable $identifiers
  69. * @return void
  70. */
  71. public function __construct($identifiers = null)
  72. {
  73. $this->setIdentifiers($identifiers);
  74. }
  75. /**
  76. * Set the event class to utilize
  77. *
  78. * @param string $class
  79. * @return EventManager
  80. */
  81. public function setEventClass($class)
  82. {
  83. $this->eventClass = $class;
  84. return $this;
  85. }
  86. /**
  87. * Set static connections container
  88. *
  89. * @param null|StaticEventCollection $connections
  90. * @return void
  91. */
  92. public function setStaticConnections(StaticEventCollection $connections = null)
  93. {
  94. if (null === $connections) {
  95. $this->staticConnections = false;
  96. } else {
  97. $this->staticConnections = $connections;
  98. }
  99. return $this;
  100. }
  101. /**
  102. * Get static connections container
  103. *
  104. * @return false|StaticEventCollection
  105. */
  106. public function getStaticConnections()
  107. {
  108. if (null === $this->staticConnections) {
  109. $this->setStaticConnections(StaticEventManager::getInstance());
  110. }
  111. return $this->staticConnections;
  112. }
  113. /**
  114. * Get the identifier(s) for this EventManager
  115. *
  116. * @return array
  117. */
  118. public function getIdentifiers()
  119. {
  120. return $this->identifiers;
  121. }
  122. /**
  123. * Set the identifiers (overrides any currently set identifiers)
  124. *
  125. * @param string|int|array|Traversable $identifiers
  126. * @return ModuleManager
  127. */
  128. public function setIdentifiers($identifiers)
  129. {
  130. if (is_array($identifiers) || $identifiers instanceof \Traversable) {
  131. $this->identifiers = array_unique((array) $identifiers);
  132. } elseif ($identifiers !== null) {
  133. $this->identifiers = array($identifiers);
  134. }
  135. return $this;
  136. }
  137. /**
  138. * Add some identifier(s) (appends to any currently set identifiers)
  139. *
  140. * @param string|int|array|Traversable $identifiers
  141. * @return ModuleManager
  142. */
  143. public function addIdentifiers($identifiers)
  144. {
  145. if (is_array($identifiers) || $identifiers instanceof \Traversable) {
  146. $this->identifiers = array_unique($this->identifiers + (array) $identifiers);
  147. } elseif ($identifiers !== null) {
  148. $this->identifiers = array_unique($this->identifiers + array($identifiers));
  149. }
  150. return $this;
  151. }
  152. /**
  153. * Trigger all listeners for a given event
  154. *
  155. * Can emulate triggerUntil() if the last argument provided is a callback.
  156. *
  157. * @param string $event
  158. * @param string|object $target Object calling emit, or symbol describing target (such as static method name)
  159. * @param array|ArrayAccess $argv Array of arguments; typically, should be associative
  160. * @param null|callback $callback
  161. * @return ResponseCollection All listener return values
  162. */
  163. public function trigger($event, $target = null, $argv = array(), $callback = null)
  164. {
  165. if ($event instanceof EventDescription) {
  166. $e = $event;
  167. $event = $e->getName();
  168. $callback = $target;
  169. } elseif ($target instanceof EventDescription) {
  170. $e = $target;
  171. $e->setName($event);
  172. $callback = $argv;
  173. } elseif ($argv instanceof EventDescription) {
  174. $e = $argv;
  175. $e->setName($event);
  176. $e->setTarget($target);
  177. } else {
  178. $e = new $this->eventClass();
  179. $e->setName($event);
  180. $e->setTarget($target);
  181. $e->setParams($argv);
  182. }
  183. if ($callback && !is_callable($callback)) {
  184. throw new InvalidCallbackException('Invalid callback provided');
  185. }
  186. return $this->triggerListeners($event, $e, $callback);
  187. }
  188. /**
  189. * Trigger listeners until return value of one causes a callback to
  190. * evaluate to true
  191. *
  192. * Triggers listeners until the provided callback evaluates the return
  193. * value of one as true, or until all listeners have been executed.
  194. *
  195. * @param string $event
  196. * @param string|object $target Object calling emit, or symbol describing target (such as static method name)
  197. * @param array|ArrayAccess $argv Array of arguments; typically, should be associative
  198. * @param Callable $callback
  199. * @throws InvalidCallbackException if invalid callback provided
  200. */
  201. public function triggerUntil($event, $target, $argv = null, $callback = null)
  202. {
  203. if ($event instanceof EventDescription) {
  204. $e = $event;
  205. $event = $e->getName();
  206. $callback = $target;
  207. } elseif ($target instanceof EventDescription) {
  208. $e = $target;
  209. $e->setName($event);
  210. $callback = $argv;
  211. } elseif ($argv instanceof EventDescription) {
  212. $e = $argv;
  213. $e->setName($event);
  214. $e->setTarget($target);
  215. } else {
  216. $e = new $this->eventClass();
  217. $e->setName($event);
  218. $e->setTarget($target);
  219. $e->setParams($argv);
  220. }
  221. if (!is_callable($callback)) {
  222. throw new InvalidCallbackException('Invalid callback provided');
  223. }
  224. return $this->triggerListeners($event, $e, $callback);
  225. }
  226. /**
  227. * Attach a listener to an event
  228. *
  229. * The first argument is the event, and the next argument describes a
  230. * callback that will respond to that event. A CallbackHandler instance
  231. * describing the event listener combination will be returned.
  232. *
  233. * The last argument indicates a priority at which the event should be
  234. * executed. By default, this value is 1; however, you may set it for any
  235. * integer value. Higher values have higher priority (i.e., execute first).
  236. *
  237. * @param string $event
  238. * @param callback $callback PHP callback
  239. * @param int $priority If provided, the priority at which to register the callback
  240. * @return ListenerAggregate (to allow later unsubscribe)
  241. */
  242. public function attach($event, $callback, $priority = 1)
  243. {
  244. if (empty($this->events[$event])) {
  245. $this->events[$event] = new PriorityQueue();
  246. }
  247. $listener = new CallbackHandler($callback, array('event' => $event, 'priority' => $priority));
  248. $this->events[$event]->insert($listener, $priority);
  249. return $listener;
  250. }
  251. /**
  252. * Attach a listener aggregate
  253. *
  254. * Listener aggregates accept an EventCollection instance, and call attach()
  255. * one or more times, typically to attach to multiple events using local
  256. * methods.
  257. *
  258. * @param ListenerAggregate $aggregate
  259. * @return mixed return value of {@link ListenerAggregate::attach()}
  260. */
  261. public function attachAggregate(ListenerAggregate $aggregate)
  262. {
  263. return $aggregate->attach($this);
  264. }
  265. /**
  266. * Unsubscribe a listener from an event
  267. *
  268. * @param CallbackHandler $listener
  269. * @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found
  270. */
  271. public function detach(CallbackHandler $listener)
  272. {
  273. $event = $listener->getMetadatum('event');
  274. if (!$event || empty($this->events[$event])) {
  275. return false;
  276. }
  277. $return = $this->events[$event]->remove($listener);
  278. if (!$return) {
  279. return false;
  280. }
  281. if (!count($this->events[$event])) {
  282. unset($this->events[$event]);
  283. }
  284. return true;
  285. }
  286. /**
  287. * Detach a listener aggregate
  288. *
  289. * Listener aggregates accept an EventCollection instance, and call detach()
  290. * of all previously attached listeners.
  291. *
  292. * @param ListenerAggregate $aggregate
  293. * @return mixed return value of {@link ListenerAggregate::detach()}
  294. */
  295. public function detachAggregate(ListenerAggregate $aggregate)
  296. {
  297. return $aggregate->detach($this);
  298. }
  299. /**
  300. * Retrieve all registered events
  301. *
  302. * @return array
  303. */
  304. public function getEvents()
  305. {
  306. return array_keys($this->events);
  307. }
  308. /**
  309. * Retrieve all listeners for a given event
  310. *
  311. * @param string $event
  312. * @return PriorityQueue
  313. */
  314. public function getListeners($event)
  315. {
  316. if (!array_key_exists($event, $this->events)) {
  317. return new PriorityQueue();
  318. }
  319. return $this->events[$event];
  320. }
  321. /**
  322. * Clear all listeners for a given event
  323. *
  324. * @param string $event
  325. * @return void
  326. */
  327. public function clearListeners($event)
  328. {
  329. if (!empty($this->events[$event])) {
  330. unset($this->events[$event]);
  331. }
  332. }
  333. /**
  334. * Prepare arguments
  335. *
  336. * Use this method if you want to be able to modify arguments from within a
  337. * listener. It returns an ArrayObject of the arguments, which may then be
  338. * passed to trigger() or triggerUntil().
  339. *
  340. * @param array $args
  341. * @return ArrayObject
  342. */
  343. public function prepareArgs(array $args)
  344. {
  345. return new ArrayObject($args);
  346. }
  347. /**
  348. * Trigger listeners
  349. *
  350. * Actual functionality for triggering listeners, to which both trigger() and triggerUntil()
  351. * delegate.
  352. *
  353. * @param string $event Event name
  354. * @param EventDescription $e
  355. * @param null|callback $callback
  356. * @return ResponseCollection
  357. */
  358. protected function triggerListeners($event, EventDescription $e, $callback = null)
  359. {
  360. $responses = new ResponseCollection;
  361. $listeners = $this->getListeners($event);
  362. // add static listeners to the list of listeners
  363. // but don't modify the listeners object
  364. $staticListeners = $this->getStaticListeners($event);
  365. if (count($staticListeners)) {
  366. $listeners = clone $listeners;
  367. foreach ($staticListeners as $listener) {
  368. $priority = $listener->getMetadatum('priority');
  369. if (null === $priority) {
  370. $priority = 1;
  371. } elseif (is_array($priority)) {
  372. // If we have an array, likely using PriorityQueue. Grab first
  373. // element of the array, as that's the actual priority.
  374. $priority = array_shift($priority);
  375. }
  376. $listeners->insert($listener, $priority);
  377. }
  378. }
  379. if ($listeners->isEmpty()) {
  380. return $responses;
  381. }
  382. foreach ($listeners as $listener) {
  383. // Trigger the listener's callback, and push its result onto the
  384. // response collection
  385. $responses->push(call_user_func($listener->getCallback(), $e));
  386. // If the event was asked to stop propagating, do so
  387. if ($e->propagationIsStopped()) {
  388. $responses->setStopped(true);
  389. break;
  390. }
  391. // If the result causes our validation callback to return true,
  392. // stop propagation
  393. if ($callback && call_user_func($callback, $responses->last())) {
  394. $responses->setStopped(true);
  395. break;
  396. }
  397. }
  398. return $responses;
  399. }
  400. /**
  401. * Get list of all listeners attached to the static collection for
  402. * identifiers registered by this instance
  403. *
  404. * @param string $event
  405. * @return array
  406. */
  407. protected function getStaticListeners($event)
  408. {
  409. if (!$staticConnections = $this->getStaticConnections()) {
  410. return array();
  411. }
  412. $identifiers = $this->getIdentifiers();
  413. $staticListeners = array();
  414. foreach ($identifiers as $id) {
  415. if (!$listeners = $staticConnections->getListeners($id, $event)) {
  416. continue;
  417. }
  418. if (!is_array($listeners) && !($listeners instanceof Traversable)) {
  419. continue;
  420. }
  421. foreach ($listeners as $listener) {
  422. if (!$listener instanceof CallbackHandler) {
  423. continue;
  424. }
  425. $staticListeners[] = $listener;
  426. }
  427. }
  428. return $staticListeners;
  429. }
  430. }