1<?php 2 3declare(strict_types=1); 4 5namespace JMS\Serializer\EventDispatcher; 6 7use JMS\Serializer\Exception\InvalidArgumentException; 8 9/** 10 * Light-weight event dispatcher. 11 * 12 * This implementation focuses primarily on performance, and dispatching 13 * events for certain classes. It is not a general purpose event dispatcher. 14 * 15 * @author Johannes M. Schmitt <schmittjoh@gmail.com> 16 */ 17class EventDispatcher implements EventDispatcherInterface 18{ 19 /** 20 * @var array 21 */ 22 private $listeners = []; 23 24 /** 25 * ClassListeners cache 26 * 27 * @var array 28 */ 29 private $classListeners = []; 30 31 public static function getDefaultMethodName(string $eventName): string 32 { 33 return 'on' . str_replace(['_', '.'], '', $eventName); 34 } 35 36 /** 37 * Sets the listeners. 38 * 39 * @param array $listeners 40 */ 41 public function setListeners(array $listeners): void 42 { 43 $this->listeners = $listeners; 44 $this->classListeners = []; 45 } 46 47 /** 48 * {@inheritdoc} 49 */ 50 public function addListener(string $eventName, $callable, ?string $class = null, ?string $format = null, ?string $interface = null): void 51 { 52 $this->listeners[$eventName][] = [$callable, $class, $format, $interface]; 53 unset($this->classListeners[$eventName]); 54 } 55 56 public function addSubscriber(EventSubscriberInterface $subscriber): void 57 { 58 foreach ($subscriber->getSubscribedEvents() as $eventData) { 59 if (!isset($eventData['event'])) { 60 throw new InvalidArgumentException(sprintf('Each event must have a "event" key.')); 61 } 62 63 $method = $eventData['method'] ?? self::getDefaultMethodName($eventData['event']); 64 $class = $eventData['class'] ?? null; 65 $format = $eventData['format'] ?? null; 66 $interface = $eventData['interface'] ?? null; 67 $this->listeners[$eventData['event']][] = [[$subscriber, $method], $class, $format, $interface]; 68 unset($this->classListeners[$eventData['event']]); 69 } 70 } 71 72 public function hasListeners(string $eventName, string $class, string $format): bool 73 { 74 if (!isset($this->listeners[$eventName])) { 75 return false; 76 } 77 78 if (!isset($this->classListeners[$eventName][$class][$format])) { 79 $this->classListeners[$eventName][$class][$format] = $this->initializeListeners($eventName, $class, $format); 80 } 81 82 return !!$this->classListeners[$eventName][$class][$format]; 83 } 84 85 public function dispatch(string $eventName, string $class, string $format, Event $event): void 86 { 87 if (!isset($this->listeners[$eventName])) { 88 return; 89 } 90 91 $object = $event instanceof ObjectEvent ? $event->getObject() : null; 92 $realClass = is_object($object) ? get_class($object) : ''; 93 $objectClass = $realClass !== $class ? ($realClass . $class) : $class; 94 95 if (!isset($this->classListeners[$eventName][$objectClass][$format])) { 96 $this->classListeners[$eventName][$objectClass][$format] = $this->initializeListeners($eventName, $class, $format); 97 } 98 99 foreach ($this->classListeners[$eventName][$objectClass][$format] as $listener) { 100 if (!empty($listener[3]) && !($object instanceof $listener[3])) { 101 continue; 102 } 103 104 \call_user_func($listener[0], $event, $eventName, $class, $format, $this); 105 106 if ($event->isPropagationStopped()) { 107 break; 108 } 109 } 110 } 111 112 /** 113 * @return array An array of listeners 114 */ 115 protected function initializeListeners(string $eventName, string $loweredClass, string $format): array 116 { 117 $listeners = []; 118 foreach ($this->listeners[$eventName] as $listener) { 119 if (null !== $listener[1] && $loweredClass !== $listener[1]) { 120 continue; 121 } 122 if (null !== $listener[2] && $format !== $listener[2]) { 123 continue; 124 } 125 126 $listeners[] = $listener; 127 } 128 129 return $listeners; 130 } 131} 132