Version 5.1 - All the GraphQL #32
@ -27,6 +27,7 @@ use Aviat\AnimeClient\API\{
|
|||||||
};
|
};
|
||||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
|
use Aviat\Ion\Event;
|
||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use const PHP_SAPI;
|
use const PHP_SAPI;
|
||||||
@ -66,6 +67,8 @@ final class Auth {
|
|||||||
$this->segment = $container->get('session')
|
$this->segment = $container->get('session')
|
||||||
->getSegment(SESSION_SEGMENT);
|
->getSegment(SESSION_SEGMENT);
|
||||||
$this->model = $container->get('kitsu-model');
|
$this->model = $container->get('kitsu-model');
|
||||||
|
|
||||||
|
Event::on('::unauthorized::', [$this, 'reAuthenticate']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,6 +90,28 @@ final class Auth {
|
|||||||
return $this->storeAuth($auth);
|
return $this->storeAuth($auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the call to re-authenticate with the existing refresh token
|
||||||
|
*
|
||||||
|
* @param string $refreshToken
|
||||||
|
* @return boolean
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function reAuthenticate(?string $refreshToken): bool
|
||||||
|
{
|
||||||
|
$refreshToken ??= $this->getAuthToken();
|
||||||
|
|
||||||
|
if (empty($refreshToken))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth = $this->model->reAuthenticate($refreshToken);
|
||||||
|
|
||||||
|
return $this->storeAuth($auth);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the current user is authenticated
|
* Check whether the current user is authenticated
|
||||||
*
|
*
|
||||||
@ -110,7 +135,7 @@ final class Auth {
|
|||||||
/**
|
/**
|
||||||
* Retrieve the authentication token from the session
|
* Retrieve the authentication token from the session
|
||||||
*
|
*
|
||||||
* @return string|false
|
* @return string
|
||||||
*/
|
*/
|
||||||
private function getAuthToken(): ?string
|
private function getAuthToken(): ?string
|
||||||
{
|
{
|
||||||
@ -146,22 +171,14 @@ final class Auth {
|
|||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function getRefreshToken(): ?string
|
||||||
* Make the call to re-authenticate with the existing refresh token
|
|
||||||
*
|
|
||||||
* @param string $token
|
|
||||||
* @return boolean
|
|
||||||
* @throws InvalidArgumentException
|
|
||||||
* @throws Throwable
|
|
||||||
*/
|
|
||||||
private function reAuthenticate(string $token): bool
|
|
||||||
{
|
{
|
||||||
$auth = $this->model->reAuthenticate($token);
|
return (PHP_SAPI === 'cli')
|
||||||
|
? $this->cacheGet(K::AUTH_TOKEN_REFRESH_CACHE_KEY, NULL)
|
||||||
return $this->storeAuth($auth);
|
: $this->segment->get('refresh_token');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function storeAuth($auth): bool
|
private function storeAuth(bool $auth): bool
|
||||||
{
|
{
|
||||||
if (FALSE !== $auth)
|
if (FALSE !== $auth)
|
||||||
{
|
{
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\API\Kitsu;
|
namespace Aviat\AnimeClient\API\Kitsu;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Enum\EventType;
|
||||||
|
use function in_array;
|
||||||
use const PHP_SAPI;
|
use const PHP_SAPI;
|
||||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
||||||
|
|
||||||
@ -24,10 +26,8 @@ use function Aviat\AnimeClient\getResponse;
|
|||||||
|
|
||||||
use Amp\Http\Client\Request;
|
use Amp\Http\Client\Request;
|
||||||
use Amp\Http\Client\Response;
|
use Amp\Http\Client\Response;
|
||||||
use Aviat\AnimeClient\API\{
|
use Aviat\AnimeClient\API\{FailedResponseException, Kitsu as K};
|
||||||
FailedResponseException,
|
use Aviat\Ion\Event;
|
||||||
Kitsu as K
|
|
||||||
};
|
|
||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
use Aviat\Ion\JsonException;
|
use Aviat\Ion\JsonException;
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ trait KitsuTrait {
|
|||||||
else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
|
else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
|
||||||
{
|
{
|
||||||
$token = $sessionSegment->get('auth_token');
|
$token = $sessionSegment->get('auth_token');
|
||||||
if ( ! $cacheItem->isHit())
|
if ( ! (empty($token) || $cacheItem->isHit()))
|
||||||
{
|
{
|
||||||
$cacheItem->set($token);
|
$cacheItem->set($token);
|
||||||
$cacheItem->save();
|
$cacheItem->save();
|
||||||
@ -168,12 +168,20 @@ trait KitsuTrait {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$response = $this->getResponse($type, $url, $options);
|
$response = $this->getResponse($type, $url, $options);
|
||||||
|
$statusCode = $response->getStatus();
|
||||||
|
|
||||||
if ((int) $response->getStatus() > 299 || (int) $response->getStatus() < 200)
|
// Check for requests that are unauthorized
|
||||||
|
if ($statusCode === 401 || $statusCode === 403)
|
||||||
|
{
|
||||||
|
Event::emit(EventType::UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other type of failed request
|
||||||
|
if ($statusCode > 299 || $statusCode < 200)
|
||||||
{
|
{
|
||||||
if ($logger)
|
if ($logger)
|
||||||
{
|
{
|
||||||
$logger->warning('Non 200 response for api call', (array)$response);
|
$logger->warning('Non 2xx response for api call', (array)$response);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new FailedResponseException('Failed to get the proper response from the API');
|
throw new FailedResponseException('Failed to get the proper response from the API');
|
||||||
@ -188,7 +196,6 @@ trait KitsuTrait {
|
|||||||
print_r($e);
|
print_r($e);
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -233,13 +240,10 @@ trait KitsuTrait {
|
|||||||
$response = $this->getResponse('POST', ...$args);
|
$response = $this->getResponse('POST', ...$args);
|
||||||
$validResponseCodes = [200, 201];
|
$validResponseCodes = [200, 201];
|
||||||
|
|
||||||
if ( ! \in_array((int) $response->getStatus(), $validResponseCodes, TRUE))
|
if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE) && $logger)
|
||||||
{
|
|
||||||
if ($logger)
|
|
||||||
{
|
{
|
||||||
$logger->warning('Non 201 response for POST api call', $response->getBody());
|
$logger->warning('Non 201 response for POST api call', $response->getBody());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return JSON::decode(wait($response->getBody()->buffer()), TRUE);
|
return JSON::decode(wait($response->getBody()->buffer()), TRUE);
|
||||||
}
|
}
|
||||||
@ -254,6 +258,6 @@ trait KitsuTrait {
|
|||||||
protected function deleteRequest(...$args): bool
|
protected function deleteRequest(...$args): bool
|
||||||
{
|
{
|
||||||
$response = $this->getResponse('DELETE', ...$args);
|
$response = $this->getResponse('DELETE', ...$args);
|
||||||
return ((int) $response->getStatus() === 204);
|
return ($response->getStatus() === 204);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -582,7 +582,7 @@ final class Model {
|
|||||||
{
|
{
|
||||||
$defaultOptions = [
|
$defaultOptions = [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
'user_id' => $this->getUserId(),
|
||||||
'kind' => 'anime'
|
'kind' => 'anime'
|
||||||
],
|
],
|
||||||
'page' => [
|
'page' => [
|
||||||
@ -608,7 +608,7 @@ final class Model {
|
|||||||
{
|
{
|
||||||
$options = [
|
$options = [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
'user_id' => $this->getUserId(),
|
||||||
'kind' => 'anime',
|
'kind' => 'anime',
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
],
|
],
|
||||||
@ -683,7 +683,7 @@ final class Model {
|
|||||||
$options = [
|
$options = [
|
||||||
'query' => [
|
'query' => [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
'user_id' => $this->getUserId(),
|
||||||
'kind' => 'manga',
|
'kind' => 'manga',
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
],
|
],
|
||||||
@ -811,7 +811,7 @@ final class Model {
|
|||||||
{
|
{
|
||||||
$defaultOptions = [
|
$defaultOptions = [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
'user_id' => $this->getUserId(),
|
||||||
'kind' => 'manga'
|
'kind' => 'manga'
|
||||||
],
|
],
|
||||||
'page' => [
|
'page' => [
|
||||||
@ -866,7 +866,7 @@ final class Model {
|
|||||||
*/
|
*/
|
||||||
public function createListItem(array $data): ?Request
|
public function createListItem(array $data): ?Request
|
||||||
{
|
{
|
||||||
$data['user_id'] = $this->getUserIdByUsername($this->getUsername());
|
$data['user_id'] = $this->getUserId();
|
||||||
if ($data['id'] === NULL)
|
if ($data['id'] === NULL)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -941,7 +941,7 @@ final class Model {
|
|||||||
{
|
{
|
||||||
$options = [
|
$options = [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
'user_id' => $this->getUserId(),
|
||||||
'kind' => $type,
|
'kind' => $type,
|
||||||
],
|
],
|
||||||
'include' => "{$type},{$type}.mappings",
|
'include' => "{$type},{$type}.mappings",
|
||||||
@ -1001,7 +1001,7 @@ final class Model {
|
|||||||
'query' => [
|
'query' => [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'kind' => 'progressed,updated',
|
'kind' => 'progressed,updated',
|
||||||
'userId' => $this->getUserIdByUsername($this->getUsername()),
|
'userId' => $this->getUserId(),
|
||||||
],
|
],
|
||||||
'page' => [
|
'page' => [
|
||||||
'offset' => $offset,
|
'offset' => $offset,
|
||||||
@ -1018,6 +1018,18 @@ final class Model {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getUserId(): string
|
||||||
|
{
|
||||||
|
static $userId = NULL;
|
||||||
|
|
||||||
|
if ($userId === NULL)
|
||||||
|
{
|
||||||
|
$userId = $this->getUserIdByUsername($this->getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $userId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the kitsu username from config
|
* Get the kitsu username from config
|
||||||
*
|
*
|
||||||
@ -1105,7 +1117,7 @@ final class Model {
|
|||||||
$options = [
|
$options = [
|
||||||
'query' => [
|
'query' => [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'user_id' => $this->getUserIdByUsername(),
|
'user_id' => $this->getUserId(),
|
||||||
'kind' => $type,
|
'kind' => $type,
|
||||||
],
|
],
|
||||||
'page' => [
|
'page' => [
|
||||||
@ -1175,7 +1187,7 @@ final class Model {
|
|||||||
{
|
{
|
||||||
$defaultOptions = [
|
$defaultOptions = [
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
'user_id' => $this->getUserId(),
|
||||||
'kind' => $type,
|
'kind' => $type,
|
||||||
],
|
],
|
||||||
'page' => [
|
'page' => [
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient;
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Enum\EventType;
|
||||||
use function Aviat\Ion\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
use Aura\Router\Generator;
|
use Aura\Router\Generator;
|
||||||
@ -32,6 +33,7 @@ use Aviat\Ion\Di\{
|
|||||||
Exception\ContainerException,
|
Exception\ContainerException,
|
||||||
Exception\NotFoundException
|
Exception\NotFoundException
|
||||||
};
|
};
|
||||||
|
use Aviat\Ion\Event;
|
||||||
use Aviat\Ion\Exception\DoubleRenderException;
|
use Aviat\Ion\Exception\DoubleRenderException;
|
||||||
use Aviat\Ion\View\{HtmlView, HttpView, JsonView};
|
use Aviat\Ion\View\{HtmlView, HttpView, JsonView};
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
@ -131,6 +133,9 @@ class Controller {
|
|||||||
'url_type' => 'anime',
|
'url_type' => 'anime',
|
||||||
'urlGenerator' => $urlGenerator,
|
'urlGenerator' => $urlGenerator,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Event::on(EventType::CLEAR_CACHE, fn () => $this->emptyCache());
|
||||||
|
Event::on(EventType::RESET_CACHE_KEY, fn (string $key) => $this->removeCacheItem($key));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -430,5 +435,15 @@ class Controller {
|
|||||||
(new HttpView($this->container))->redirect($url, $code);
|
(new HttpView($this->container))->redirect($url, $code);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function emptyCache(): void
|
||||||
|
{
|
||||||
|
$this->cache->emptyCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeCacheItem(string $key): void
|
||||||
|
{
|
||||||
|
$this->cache->deleteItem($key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// End of BaseController.php
|
// End of BaseController.php
|
@ -17,6 +17,8 @@
|
|||||||
namespace Aviat\AnimeClient\Controller;
|
namespace Aviat\AnimeClient\Controller;
|
||||||
|
|
||||||
use Aviat\AnimeClient\Controller as BaseController;
|
use Aviat\AnimeClient\Controller as BaseController;
|
||||||
|
use Aviat\AnimeClient\Enum\EventType;
|
||||||
|
use Aviat\Ion\Event;
|
||||||
use Aviat\Ion\View\HtmlView;
|
use Aviat\Ion\View\HtmlView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,7 +32,10 @@ final class Misc extends BaseController {
|
|||||||
*/
|
*/
|
||||||
public function clearCache(): void
|
public function clearCache(): void
|
||||||
{
|
{
|
||||||
$this->cache->clear();
|
$this->checkAuth();
|
||||||
|
|
||||||
|
Event::emit(EventType::CLEAR_CACHE);
|
||||||
|
|
||||||
$this->outputHTML('blank', [
|
$this->outputHTML('blank', [
|
||||||
'title' => 'Cache cleared'
|
'title' => 'Cache cleared'
|
||||||
]);
|
]);
|
||||||
@ -89,8 +94,6 @@ final class Misc extends BaseController {
|
|||||||
*/
|
*/
|
||||||
public function logout(): void
|
public function logout(): void
|
||||||
{
|
{
|
||||||
$this->checkAuth();
|
|
||||||
|
|
||||||
$auth = $this->container->get('auth');
|
$auth = $this->container->get('auth');
|
||||||
$auth->logout();
|
$auth->logout();
|
||||||
|
|
||||||
|
@ -16,12 +16,14 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient;
|
namespace Aviat\AnimeClient;
|
||||||
|
|
||||||
|
use Aviat\AnimeClient\Enum\EventType;
|
||||||
use function Aviat\Ion\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
use Aura\Router\{Map, Matcher, Route, Rule};
|
use Aura\Router\{Map, Matcher, Route, Rule};
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\FailedResponseException;
|
use Aviat\AnimeClient\API\FailedResponseException;
|
||||||
use Aviat\Ion\Di\ContainerInterface;
|
use Aviat\Ion\Di\ContainerInterface;
|
||||||
|
use Aviat\Ion\Event;
|
||||||
use Aviat\Ion\Friend;
|
use Aviat\Ion\Friend;
|
||||||
use Aviat\Ion\Type\StringType;
|
use Aviat\Ion\Type\StringType;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
@ -161,10 +163,7 @@ final class Dispatcher extends RoutingBase {
|
|||||||
throw new LogicException('Missing controller');
|
throw new LogicException('Missing controller');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (array_key_exists('controller', $route->attributes))
|
|
||||||
{
|
|
||||||
$controllerName = $route->attributes['controller'];
|
$controllerName = $route->attributes['controller'];
|
||||||
}
|
|
||||||
|
|
||||||
// Get the full namespace for a controller if a short name is given
|
// Get the full namespace for a controller if a short name is given
|
||||||
if (strpos($controllerName, '\\') === FALSE)
|
if (strpos($controllerName, '\\') === FALSE)
|
||||||
@ -283,7 +282,7 @@ final class Dispatcher extends RoutingBase {
|
|||||||
$logger->debug('Dispatcher - controller arguments', $params);
|
$logger->debug('Dispatcher - controller arguments', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
\call_user_func_array([$controller, $method], $params);
|
call_user_func_array([$controller, $method], $params);
|
||||||
}
|
}
|
||||||
catch (FailedResponseException $e)
|
catch (FailedResponseException $e)
|
||||||
{
|
{
|
||||||
@ -293,7 +292,14 @@ final class Dispatcher extends RoutingBase {
|
|||||||
'API request timed out',
|
'API request timed out',
|
||||||
'Failed to retrieve data from API (╯°□°)╯︵ ┻━┻');
|
'Failed to retrieve data from API (╯°□°)╯︵ ┻━┻');
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Log out on session/api token expiration
|
||||||
|
Event::on(EventType::UNAUTHORIZED, static function () {
|
||||||
|
$controllerName = DEFAULT_CONTROLLER;
|
||||||
|
(new $controllerName($this->container))->logout();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
25
src/AnimeClient/Enum/EventType.php
Normal file
25
src/AnimeClient/Enum/EventType.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7.4
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2020 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\AnimeClient\Enum;
|
||||||
|
|
||||||
|
use Aviat\Ion\Enum as BaseEnum;
|
||||||
|
|
||||||
|
final class EventType extends BaseEnum {
|
||||||
|
public const CLEAR_CACHE = '::clear-cache::';
|
||||||
|
public const RESET_CACHE_KEY = '::reset-cache-key::';
|
||||||
|
public const UNAUTHORIZED = '::unauthorized::';
|
||||||
|
}
|
55
src/Ion/Event.php
Normal file
55
src/Ion/Event.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Hummingbird Anime List Client
|
||||||
|
*
|
||||||
|
* An API client for Kitsu to manage anime and manga watch lists
|
||||||
|
*
|
||||||
|
* PHP version 7.4
|
||||||
|
*
|
||||||
|
* @package HummingbirdAnimeClient
|
||||||
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||||
|
* @copyright 2015 - 2020 Timothy J. Warren
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||||
|
* @version 5
|
||||||
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Aviat\Ion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic event handler
|
||||||
|
*/
|
||||||
|
class Event {
|
||||||
|
private static array $eventMap = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to an event
|
||||||
|
*
|
||||||
|
* @param string $eventName
|
||||||
|
* @param callable $handler
|
||||||
|
*/
|
||||||
|
public static function on(string $eventName, callable $handler): void
|
||||||
|
{
|
||||||
|
if ( ! array_key_exists($eventName, static::$eventMap))
|
||||||
|
{
|
||||||
|
static::$eventMap[$eventName] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static::$eventMap[$eventName][] = $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire off an event
|
||||||
|
*
|
||||||
|
* @param string $eventName
|
||||||
|
* @param array $args
|
||||||
|
*/
|
||||||
|
public static function emit(string $eventName, array $args = []): void
|
||||||
|
{
|
||||||
|
// Call each subscriber with the provided arguments
|
||||||
|
if (array_key_exists($eventName, static::$eventMap))
|
||||||
|
{
|
||||||
|
array_walk(static::$eventMap[$eventName], fn ($fn) => $fn(...$args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user