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: | |
16: | |
17: | final class EventDispatcher implements EventDispatcherInterface |
18: | { |
19: | |
20: | |
21: | |
22: | |
23: | |
24: | private array $Listeners = []; |
25: | |
26: | |
27: | |
28: | |
29: | |
30: | |
31: | private array $EventListeners = []; |
32: | |
33: | private int $NextListenerId = 0; |
34: | private PsrListenerProviderInterface $ListenerProvider; |
35: | |
36: | |
37: | |
38: | |
39: | public function __construct(?PsrListenerProviderInterface $listenerProvider = null) |
40: | { |
41: | $this->ListenerProvider = $listenerProvider ?? $this; |
42: | } |
43: | |
44: | |
45: | |
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: | |
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: | |
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: | |
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: | |