1: <?php declare(strict_types=1);
2:
3: namespace Salient\Core\Event;
4:
5: use Psr\EventDispatcher\ListenerProviderInterface as PsrListenerProviderInterface;
6: use Psr\EventDispatcher\StoppableEventInterface as PsrStoppableEventInterface;
7: use Salient\Contract\Core\Event\EventDispatcherInterface;
8: use Salient\Contract\Core\HasName;
9: use Salient\Utility\Reflect;
10: use Salient\Utility\Str;
11: use InvalidArgumentException;
12: use LogicException;
13:
14: /**
15: * @api
16: */
17: final class EventDispatcher implements EventDispatcherInterface
18: {
19: /**
20: * Listener ID => list of events
21: *
22: * @var array<int,string[]>
23: */
24: private array $Listeners = [];
25:
26: /**
27: * Event => [ listener ID => listener ]
28: *
29: * @var array<string,array<int,callable>>
30: */
31: private array $EventListeners = [];
32:
33: private int $NextListenerId = 0;
34: private PsrListenerProviderInterface $ListenerProvider;
35:
36: /**
37: * @api
38: */
39: public function __construct(?PsrListenerProviderInterface $listenerProvider = null)
40: {
41: $this->ListenerProvider = $listenerProvider ?? $this;
42: }
43:
44: /**
45: * @inheritDoc
46: */
47: public function listen(callable $listener, $event = null): int
48: {
49: $this->assertIsListenerProvider();
50:
51: if ($event === null) {
52: $event = [];
53: foreach (Reflect::getAcceptedTypes($listener, true) as $name) {
54: if (is_string($name)) {
55: $event[] = $name;
56: }
57: }
58: }
59:
60: if ($event === []) {
61: throw new InvalidArgumentException('At least one event must be given');
62: }
63:
64: $id = $this->NextListenerId++;
65: foreach ((array) $event as $event) {
66: $event = Str::lower($event);
67: $this->Listeners[$id][] = $event;
68: $this->EventListeners[$event][$id] = $listener;
69: }
70:
71: return $id;
72: }
73:
74: /**
75: * @inheritDoc
76: */
77: public function dispatch(object $event): object
78: {
79: $listeners = $this->ListenerProvider->getListenersForEvent($event);
80:
81: foreach ($listeners as $listener) {
82: if (
83: $event instanceof PsrStoppableEventInterface
84: && $event->isPropagationStopped()
85: ) {
86: break;
87: }
88:
89: $listener($event);
90: }
91:
92: return $event;
93: }
94:
95: /**
96: * @inheritDoc
97: */
98: public function getListenersForEvent(object $event): iterable
99: {
100: $this->assertIsListenerProvider();
101:
102: $events = array_merge(
103: [get_class($event)],
104: class_parents($event),
105: class_implements($event),
106: );
107:
108: if ($event instanceof HasName) {
109: $events[] = $event->getName();
110: }
111:
112: $listenersByEvent = array_intersect_key(
113: $this->EventListeners,
114: array_change_key_case(array_fill_keys($events, null)),
115: );
116:
117: $listeners = [];
118: foreach ($listenersByEvent as $eventListeners) {
119: $listeners += $eventListeners;
120: }
121:
122: return array_values($listeners);
123: }
124:
125: /**
126: * @inheritDoc
127: */
128: public function removeListener(int $id): void
129: {
130: $this->assertIsListenerProvider();
131:
132: if (!array_key_exists($id, $this->Listeners)) {
133: throw new InvalidArgumentException('No listener with that ID');
134: }
135:
136: foreach ($this->Listeners[$id] as $event) {
137: unset($this->EventListeners[$event][$id]);
138: if (!$this->EventListeners[$event]) {
139: unset($this->EventListeners[$event]);
140: }
141: }
142:
143: unset($this->Listeners[$id]);
144: }
145:
146: private function assertIsListenerProvider(): void
147: {
148: if ($this->ListenerProvider !== $this) {
149: throw new LogicException('Not a listener provider');
150: }
151: }
152: }
153: