pushed = new Collection(); $this->registerListeners(); } /** * Register event listeners based on the ListensFor attribute. * * @return void */ private function registerListeners(): void { // traverse the Listeners directory and register all listeners $listenersPath = __DIR__ . '/Listeners'; $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($listenersPath)); foreach ($iterator as $file) { if ($file->isFile() && $file->getExtension() === 'php') { $relativePath = str_replace($listenersPath . '/', '', $file->getPathname()); $className = self::LISTENERS_NAMESPACE . str_replace(['/', '.php'], ['\\', ''], $relativePath); if (class_exists($className)) { $reflectionClass = new \ReflectionClass($className); $attributes = $reflectionClass->getAttributes(ListensFor::class); foreach ($attributes as $attribute) { $instance = $attribute->newInstance(); $eventClass = $instance->eventClass; $this->listen($eventClass, new $className()); } } } } } /** * Register a listener for the given events. * * @param $events * @param $listener * @return void */ public function listen($events, $listener = null): void { $this->listeners[$events][] = $listener; } /** * Check if there are listeners for the given event. * * @param $eventName * @return bool */ public function hasListeners($eventName): bool { return isset($this->listeners[$eventName]) && !empty($this->listeners[$eventName]); } /** * Subscribe a subscriber to the dispatcher. * * @param Arrayable $subscriber * @return void */ public function subscribe($subscriber): void { $this->subscribers[] = $subscriber; } /** * Dispatch an event and halt on the first non-null response. * * @param $event * @param array $payload * @return array|null * @throws \Throwable */ public function until($event, $payload = []): array|null { return $this->dispatch($event, $payload, true); } /** * Dispatch an event to its listeners. * * @param $event * @param array $payload * @param bool $halt * @return array|null * @throws \Throwable */ public function dispatch($event, $payload = [], $halt = false): array|null { if (is_object($event)) { $eventClass = get_class($event); } else { $eventClass = $event; } // Handle subscribers as a coroutine $promise = coroutine(function () use ($event, $payload, $halt, $eventClass, &$responses) { foreach ($this->subscribers as $subscriber) { if (method_exists($subscriber, 'handle')) { $response = $subscriber->handle($event, $payload); $responses[$eventClass] = $response; if ($halt && $response !== null) { return $responses; } } } return null; }); $listeners = $this->listeners[$eventClass] ?? null; // If no listeners, just await the subscriber promise if ($listeners === null) { return await($promise); } $responses = []; foreach ($listeners as $listener) { $response = $listener($event, $payload); $responses[$eventClass] = $response; if ($halt && $response !== null) { return $response; } } // Await the subscriber promise and merge responses $promiseResponses = await($promise); if (is_array($promiseResponses)) { $responses = array_merge($responses, $promiseResponses); } return $responses; } /** * Push an event to be dispatched later. * * @param $event * @param array $payload * @return void */ public function push($event, $payload = []): void { $this->pushed->put($event, $payload); } /** * Flush a pushed event, dispatching it if it exists. * * @param $event * @return void * @throws \Throwable */ public function flush($event): void { if ($this->pushed->has($event)) { $payload = $this->pushed->get($event); $this->dispatch($event, $payload); $this->pushed->forget([$event]); } } /** * Forget a pushed event without dispatching it. * * @param $event * @return void */ public function forget($event): void { $this->pushed->forget([$event]); } /** * Forget all pushed events. * * @return void */ public function forgetPushed(): void { $this->pushed = new Collection(); } public function toArray(): array { return $this->listeners; } }