From d7a2faf3a893de08c45c49bdab4817100f1a5b1b Mon Sep 17 00:00:00 2001 From: Ron Rise Date: Tue, 11 Nov 2025 10:49:42 -0500 Subject: [PATCH 1/3] feat: implement custom event dispatcher and listener system --- src/Annotations/Events/ListensFor.php | 15 +++ src/Events/Dispatcher.php | 135 ++++++++++++++++++++ src/Events/Listeners/Database/Connected.php | 28 ++++ src/Events/Listeners/Listener.php | 9 ++ src/Events/Listeners/ListenerInterface.php | 10 ++ src/Kernel.php | 2 + 6 files changed, 199 insertions(+) create mode 100644 src/Annotations/Events/ListensFor.php create mode 100644 src/Events/Dispatcher.php create mode 100644 src/Events/Listeners/Database/Connected.php create mode 100644 src/Events/Listeners/Listener.php create mode 100644 src/Events/Listeners/ListenerInterface.php diff --git a/src/Annotations/Events/ListensFor.php b/src/Annotations/Events/ListensFor.php new file mode 100644 index 0000000..60b8f2e --- /dev/null +++ b/src/Annotations/Events/ListensFor.php @@ -0,0 +1,15 @@ +pushed = new Collection(); + $this->registerListeners(); + } + + 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()); + } + } + } + } + } + + public function listen($events, $listener = null): void + { + $this->listeners[$events][] = $listener; + } + + public function hasListeners($eventName): bool + { + return isset($this->listeners[$eventName]) && !empty($this->listeners[$eventName]); + } + + public function subscribe($subscriber): void + { + $this->listeners = array_merge($this->listeners, (array) $subscriber); + } + + public function until($event, $payload = []) + { + return $this->dispatch($event, $payload, true); + } + + public function dispatch($event, $payload = [], $halt = false): array|null + { + if (is_object($event)) { + $eventClass = get_class($event); + } else { + $eventClass = $event; + } + + $listeners = $this->listeners[$eventClass] ?? null; + + if ($listeners === null) { + return null; + } + + $responses = []; + + foreach ($listeners as $listener) { + $response = $listener($event, $payload); + $responses[] = $response; + + if ($halt && $response !== null) { + return $response; + } + } + + return $responses; + } + + public function push($event, $payload = []): void + { + $this->pushed->put($event, $payload); + } + + public function flush($event): void + { + if ($this->pushed->has($event)) { + $payload = $this->pushed->get($event); + $this->dispatch($event, $payload); + $this->pushed->forget([$event]); + } + } + + public function forget($event): void + { + $this->pushed->forget([$event]); + } + + public function forgetPushed(): void + { + $this->pushed = new Collection(); + } +} diff --git a/src/Events/Listeners/Database/Connected.php b/src/Events/Listeners/Database/Connected.php new file mode 100644 index 0000000..e0c3cb5 --- /dev/null +++ b/src/Events/Listeners/Database/Connected.php @@ -0,0 +1,28 @@ +connectionName]); + + return null; + } +} diff --git a/src/Events/Listeners/Listener.php b/src/Events/Listeners/Listener.php new file mode 100644 index 0000000..ba21dbb --- /dev/null +++ b/src/Events/Listeners/Listener.php @@ -0,0 +1,9 @@ +setEventDispatcher(new Dispatcher()); $capsule->addConnection(Config::get('db')); $capsule->setAsGlobal(); $capsule->bootEloquent(); -- 2.49.1 From 291f6ced87b7b228ca1f468c9d94b504862db447 Mon Sep 17 00:00:00 2001 From: Ron Rise Date: Tue, 11 Nov 2025 10:58:46 -0500 Subject: [PATCH 2/3] feat: add event dispatcher facade and service provider --- src/Kernel.php | 8 +++-- src/Services/Facades/Dispatcher.php | 35 +++++++++++++++++++ .../DispatcherServiceProvider.php | 18 ++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/Services/Facades/Dispatcher.php create mode 100644 src/Services/ServiceProviders/DispatcherServiceProvider.php diff --git a/src/Kernel.php b/src/Kernel.php index 4b78332..e7cb9e5 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -6,9 +6,10 @@ use Illuminate\Container\Container; use Illuminate\Database\Capsule\Manager; use Illuminate\Support\ServiceProvider; use Siteworx\Config\Config as SWConfig; -use Siteworxpro\App\Events\Dispatcher; use Siteworxpro\App\Services\Facade; use Siteworxpro\App\Services\Facades\Config; +use Siteworxpro\App\Services\Facades\Dispatcher; +use Siteworxpro\App\Services\ServiceProviders\DispatcherServiceProvider; use Siteworxpro\App\Services\ServiceProviders\LoggerServiceProvider; use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider; @@ -16,7 +17,8 @@ class Kernel { private static array $serviceProviders = [ LoggerServiceProvider::class, - RedisServiceProvider::class + RedisServiceProvider::class, + DispatcherServiceProvider::class, ]; /** @@ -70,7 +72,7 @@ class Kernel private static function bootModelCapsule(): void { $capsule = new Manager(); - $capsule->setEventDispatcher(new Dispatcher()); + $capsule->setEventDispatcher(Dispatcher::getFacadeRoot()); $capsule->addConnection(Config::get('db')); $capsule->setAsGlobal(); $capsule->bootEloquent(); diff --git a/src/Services/Facades/Dispatcher.php b/src/Services/Facades/Dispatcher.php new file mode 100644 index 0000000..9fec1dd --- /dev/null +++ b/src/Services/Facades/Dispatcher.php @@ -0,0 +1,35 @@ +app->singleton(Dispatcher::class, function () { + return new Dispatcher(); + }); + } +} -- 2.49.1 From daffbef2636c6b775b423db8b78b158174a27eae Mon Sep 17 00:00:00 2001 From: Ron Rise Date: Tue, 11 Nov 2025 11:07:20 -0500 Subject: [PATCH 3/3] feat: add event dispatcher facade and service provider --- src/Events/Dispatcher.php | 74 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/src/Events/Dispatcher.php b/src/Events/Dispatcher.php index 253c936..5f0c790 100644 --- a/src/Events/Dispatcher.php +++ b/src/Events/Dispatcher.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Siteworxpro\App\Events; +use Illuminate\Contracts\Events\Dispatcher as DispatcherContract; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Collection; use Siteworxpro\App\Annotations\Events\ListensFor; @@ -15,7 +17,7 @@ use Siteworxpro\App\Annotations\Events\ListensFor; * * @package Siteworxpro\App\Events */ -class Dispatcher implements \Illuminate\Contracts\Events\Dispatcher +class Dispatcher implements DispatcherContract, Arrayable { /** * @var array $listeners Registered event listeners @@ -38,6 +40,11 @@ class Dispatcher implements \Illuminate\Contracts\Events\Dispatcher $this->registerListeners(); } + /** + * Register event listeners based on the ListensFor attribute. + * + * @return void + */ private function registerListeners(): void { // traverse the Listeners directory and register all listeners @@ -61,26 +68,60 @@ class Dispatcher implements \Illuminate\Contracts\Events\Dispatcher } } + /** + * 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->listeners = array_merge($this->listeners, (array) $subscriber); } - public function until($event, $payload = []) + /** + * Dispatch an event and halt on the first non-null response. + * + * @param $event + * @param array $payload + * @return array|null + */ + 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 + */ public function dispatch($event, $payload = [], $halt = false): array|null { if (is_object($event)) { @@ -109,11 +150,24 @@ class Dispatcher implements \Illuminate\Contracts\Events\Dispatcher 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 + */ public function flush($event): void { if ($this->pushed->has($event)) { @@ -123,13 +177,29 @@ class Dispatcher implements \Illuminate\Contracts\Events\Dispatcher } } + /** + * 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; + } } -- 2.49.1