You've already forked Php-Template
All checks were successful
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 2m32s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 2m48s
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 2m33s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 2m44s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Successful in 2m53s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Successful in 3m5s
Reviewed-on: #19 Co-authored-by: Ron Rise <ron@siteworxpro.com> Co-committed-by: Ron Rise <ron@siteworxpro.com>
237 lines
6.2 KiB
PHP
237 lines
6.2 KiB
PHP
<?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\Attributes\Events\ListensFor;
|
|
|
|
use function React\Async\await;
|
|
use function React\Async\coroutine;
|
|
|
|
/**
|
|
* 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;
|
|
|
|
private array $subscribers = [];
|
|
|
|
/**
|
|
* @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->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;
|
|
}
|
|
}
|