* @author Contributors https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors */ declare(strict_types=1); namespace Phpfastcache; use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface; use Phpfastcache\Event\EventManagerInterface; use Phpfastcache\Exceptions\PhpfastcacheEventManagerException; use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException; use Phpfastcache\Helper\UninstanciableObjectTrait; class EventManager implements EventManagerInterface { use UninstanciableObjectTrait; public const ON_EVERY_EVENT = '__every'; protected static EventManagerInterface $instance; protected bool $isScopedEventManager = false; protected ?ExtendedCacheItemPoolInterface $itemPoolContext = null; /** @var array> */ protected array $events = [ self::ON_EVERY_EVENT => [] ]; /** * @return EventManagerInterface */ public static function getInstance(): EventManagerInterface { return (self::$instance ?? self::$instance = new static()); } /** * @param EventManagerInterface $eventManagerInstance * @return void */ public static function setInstance(EventManagerInterface $eventManagerInstance): void { self::$instance = $eventManagerInstance; } public function dispatch(string $eventName, ...$args): void { /** * Replace array_key_exists by isset * due to performance issue on huge * loop dispatching operations */ if (isset($this->events[$eventName]) && $eventName !== self::ON_EVERY_EVENT) { $loopArgs = array_merge($args, [$eventName]); foreach ($this->events[$eventName] as $event) { /** * @todo V10: BC Break: Put eventName as first parameter (like self::ON_EVERY_EVENT) */ $event(...$loopArgs); } } foreach ($this->events[self::ON_EVERY_EVENT] as $event) { $event($eventName, ...$args); } } /** * @inheritDoc * @throws PhpfastcacheInvalidArgumentException * @throws PhpfastcacheEventManagerException */ public function __call(string $name, array $arguments): void { if (\str_starts_with($name, 'on')) { $name = \substr($name, 2); if (\is_callable($arguments[0])) { if (isset($arguments[1]) && \is_string($arguments[0])) { $this->events[$name][$arguments[1]] = $arguments[0]; } else { $this->events[$name][] = $arguments[0]; } } else { throw new PhpfastcacheInvalidArgumentException(\sprintf('Expected Callable, got "%s"', \gettype($arguments[0]))); } } else { throw new PhpfastcacheEventManagerException('An event must start with "on" such as "onCacheGetItem"'); } } /** * @param callable $callback * @param string $callbackName * @throws PhpfastcacheEventManagerException */ public function onEveryEvents(callable $callback, string $callbackName): void { if ($callbackName === '') { throw new PhpfastcacheEventManagerException('Callbackname cannot be empty'); } $this->events[self::ON_EVERY_EVENT][$callbackName] = $callback; } /** * @throws PhpfastcacheEventManagerException */ public function on(array|string $events, callable $callback): void { if (is_string($events)) { $events = [$events]; } foreach ($events as $event) { if (!\preg_match('#^([a-zA-Z])*$#', $event)) { throw new PhpfastcacheEventManagerException(\sprintf('Invalid event name "%s"', $event)); } $this->{'on' . \ucfirst($event)}($callback); } } /** * @param string $eventName * @param string $callbackName * @return bool */ public function unbindEventCallback(string $eventName, string $callbackName): bool { $return = isset($this->events[$eventName][$callbackName]); unset($this->events[$eventName][$callbackName]); return $return; } /** * @return bool */ public function unbindAllEventCallbacks(): bool { $this->events = [ self::ON_EVERY_EVENT => [] ]; return true; } public function __clone(): void { $this->isScopedEventManager = true; $this->unbindAllEventCallbacks(); } /** * @param ExtendedCacheItemPoolInterface $pool * @return EventManagerInterface * @throws PhpfastcacheEventManagerException */ public function getScopedEventManager(ExtendedCacheItemPoolInterface $pool): EventManagerInterface { return (clone $this)->setItemPoolContext($pool); } /** * @param ExtendedCacheItemPoolInterface $pool * @return EventManagerInterface * @throws PhpfastcacheEventManagerException */ public function setItemPoolContext(ExtendedCacheItemPoolInterface $pool): EventManagerInterface { if (!$this->isScopedEventManager) { throw new PhpfastcacheEventManagerException('Cannot set itemPool context on unscoped event manager instance.'); } $this->itemPoolContext = $pool; $this->onEveryEvents(function (string $eventName, ...$args) { EventManager::getInstance()->dispatch($eventName, ...$args); }, 'Scoped' . $pool->getDriverName() . spl_object_hash($this)); return $this; } } // phpcs:disable \class_alias(EventManager::class, PhpfastcacheEventManager::class); // @phpstan-ignore-line // phpcs:enable