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