You've already forked Php-Template
feat: implement custom event dispatcher and listener system #13
15
src/Annotations/Events/ListensFor.php
Normal file
15
src/Annotations/Events/ListensFor.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Annotations\Events;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||||
|
readonly class ListensFor
|
||||||
|
{
|
||||||
|
public function __construct(public string $eventClass)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
205
src/Events/Dispatcher.php
Normal file
205
src/Events/Dispatcher.php
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Dispatcher
|
||||||
|
*
|
||||||
|
* A custom event dispatcher that automatically registers event listeners
|
||||||
|
* based on the ListensFor attribute.
|
||||||
|
*
|
||||||
|
* @package Siteworxpro\App\Events
|
||||||
|
*/
|
||||||
|
class Dispatcher implements DispatcherContract, Arrayable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array $listeners Registered event listeners
|
||||||
|
*/
|
||||||
|
private array $listeners = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection $pushed Pushed events collection
|
||||||
|
*/
|
||||||
|
private Collection $pushed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string LISTENERS_NAMESPACE The namespace where listeners are located
|
||||||
|
*/
|
||||||
|
private const string LISTENERS_NAMESPACE = 'Siteworxpro\\App\\Events\\Listeners\\';
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->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->listeners = array_merge($this->listeners, (array) $subscriber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)) {
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)) {
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Events/Listeners/Database/Connected.php
Normal file
28
src/Events/Listeners/Database/Connected.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Events\Listeners\Database;
|
||||||
|
|
||||||
|
use Illuminate\Database\Events\ConnectionEstablished;
|
||||||
|
use Illuminate\Database\Events\ConnectionEvent;
|
||||||
|
use Siteworxpro\App\Annotations\Events\ListensFor;
|
||||||
|
use Siteworxpro\App\Events\Listeners\Listener;
|
||||||
|
use Siteworxpro\App\Services\Facades\Logger;
|
||||||
|
|
||||||
|
#[ListensFor(ConnectionEstablished::class)]
|
||||||
|
class Connected extends Listener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param ConnectionEvent $event
|
||||||
|
* @param array $payload
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function __invoke($event, array $payload = []): null
|
||||||
|
{
|
||||||
|
|
||||||
|
Logger::info("Database connection event", [get_class($event), $event->connectionName]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/Events/Listeners/Listener.php
Normal file
9
src/Events/Listeners/Listener.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Events\Listeners;
|
||||||
|
|
||||||
|
abstract class Listener implements ListenerInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
10
src/Events/Listeners/ListenerInterface.php
Normal file
10
src/Events/Listeners/ListenerInterface.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Events\Listeners;
|
||||||
|
|
||||||
|
interface ListenerInterface
|
||||||
|
{
|
||||||
|
public function __invoke(mixed $event, array $payload = []): mixed;
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ use Illuminate\Support\ServiceProvider;
|
|||||||
use Siteworx\Config\Config as SWConfig;
|
use Siteworx\Config\Config as SWConfig;
|
||||||
use Siteworxpro\App\Services\Facade;
|
use Siteworxpro\App\Services\Facade;
|
||||||
use Siteworxpro\App\Services\Facades\Config;
|
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\LoggerServiceProvider;
|
||||||
use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider;
|
use Siteworxpro\App\Services\ServiceProviders\RedisServiceProvider;
|
||||||
|
|
||||||
@@ -15,7 +17,8 @@ class Kernel
|
|||||||
{
|
{
|
||||||
private static array $serviceProviders = [
|
private static array $serviceProviders = [
|
||||||
LoggerServiceProvider::class,
|
LoggerServiceProvider::class,
|
||||||
RedisServiceProvider::class
|
RedisServiceProvider::class,
|
||||||
|
DispatcherServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,6 +72,7 @@ class Kernel
|
|||||||
private static function bootModelCapsule(): void
|
private static function bootModelCapsule(): void
|
||||||
{
|
{
|
||||||
$capsule = new Manager();
|
$capsule = new Manager();
|
||||||
|
$capsule->setEventDispatcher(Dispatcher::getFacadeRoot());
|
||||||
$capsule->addConnection(Config::get('db'));
|
$capsule->addConnection(Config::get('db'));
|
||||||
$capsule->setAsGlobal();
|
$capsule->setAsGlobal();
|
||||||
$capsule->bootEloquent();
|
$capsule->bootEloquent();
|
||||||
|
|||||||
35
src/Services/Facades/Dispatcher.php
Normal file
35
src/Services/Facades/Dispatcher.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Services\Facades;
|
||||||
|
|
||||||
|
use Siteworxpro\App\Events\Dispatcher as DispatcherConcrete;
|
||||||
|
use Siteworxpro\App\Services\Facade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Dispatcher
|
||||||
|
*
|
||||||
|
* A facade for the event dispatcher.
|
||||||
|
*
|
||||||
|
* @package Siteworxpro\App\Services\Facades
|
||||||
|
*
|
||||||
|
* @method static void listen(string $event, callable|string $listener)
|
||||||
|
* @method static void dispatch(object|string $event, array $payload = [], bool $halt = false)
|
||||||
|
* @method static void push(object|string $event, array $payload = [])
|
||||||
|
* @method static array|null until(object|string $event, array $payload = [])
|
||||||
|
* @method static bool hasListeners(string $eventName)
|
||||||
|
* @method static void subscribe(mixed $subscriber)
|
||||||
|
*/
|
||||||
|
class Dispatcher extends Facade
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the registered name of the component.
|
||||||
|
*
|
||||||
|
* @return string The name of the component.
|
||||||
|
*/
|
||||||
|
protected static function getFacadeAccessor(): string
|
||||||
|
{
|
||||||
|
return DispatcherConcrete::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Services/ServiceProviders/DispatcherServiceProvider.php
Normal file
18
src/Services/ServiceProviders/DispatcherServiceProvider.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Siteworxpro\App\Services\ServiceProviders;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Siteworxpro\App\Events\Dispatcher;
|
||||||
|
|
||||||
|
class DispatcherServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->singleton(Dispatcher::class, function () {
|
||||||
|
return new Dispatcher();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user