diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..10863c0 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "infinityloop-dev/observer-component", + "description": "Event system for Nette framework's component model.", + "homepage": "https://www.infinityloop.dev/", + "type": "library", + "license": ["MIT"], + "authors": [ + { + "name": "Václav Pelíšek", + "homepage": "https://www.peldax.com" + } + ], + "require": { + "php": ">=7.4", + "nette/application": "^3.0", + "nette/caching": "^3.0", + "nette/utils": "^3.0" + }, + "autoload": { + "psr-4": { + "Infinityloop\\": "src/" + } + } +} \ No newline at end of file diff --git a/src/ObserverComponent/EventMapper.php b/src/ObserverComponent/EventMapper.php new file mode 100644 index 0000000..3a2b909 --- /dev/null +++ b/src/ObserverComponent/EventMapper.php @@ -0,0 +1,91 @@ +application = $application; + $this->storage = $storage; + $this->debugMode = $debugMode; + } + + public function registerObserver(IObserverComponent $component) : void + { + $componentPath = $component->lookupPath(\Nette\Application\IPresenter::class); + \assert(\is_string($componentPath)); + + if (!$this->debugMode && $this->isComponentRegistered($componentPath)) { + return; + } + + foreach ($component::getObservedEvents() as $eventName) { + \assert(\is_string($eventName) && \class_exists($eventName)); + + $observerList = $this->getObserverList($eventName); + + if (\in_array($componentPath, $observerList, true)) { + continue; + } + + $observerList[] = $componentPath; + $this->getEventMap()->save($eventName, $observerList); + + $registeredComponents = $this->getEventMap()->load('components') ?? []; + $registeredComponents[] = $componentPath; + $this->getEventMap()->save('components', $registeredComponents); + } + } + + public function dispatchEvent(IEvent $event) : void + { + $presenter = $this->application->getPresenter(); + \assert($presenter instanceof \Nette\Application\UI\Control); + + foreach ($this->getObserverList(\get_class($event)) as $observerPath) { + \assert(\is_string($observerPath)); + + $observer = $presenter->getComponent($observerPath); + \assert($observer instanceof IObserverComponent); + + $observer->observableUpdated($event); + } + } + + private function getObserverList(string $eventName) : array + { + return $this->getEventMap()->load($eventName) + ?? []; + } + + private function isComponentRegistered(string $componentPath) : bool + { + $registeredComponents = $this->getEventMap()->load('components') ?? []; + + return \in_array($componentPath, $registeredComponents, true); + } + + private function getEventMap() : \Nette\Caching\Cache + { + if (!$this->eventMap instanceof \Nette\Caching\Cache) { + $applicationPath = \ltrim($this->application->getPresenter()->getAction(true), ':'); + $this->eventMap = new \Nette\Caching\Cache($this->storage, 'eventMapper-' . $applicationPath); + } + + return $this->eventMap; + } +} diff --git a/src/ObserverComponent/IEvent.php b/src/ObserverComponent/IEvent.php new file mode 100644 index 0000000..c5802ad --- /dev/null +++ b/src/ObserverComponent/IEvent.php @@ -0,0 +1,9 @@ +eventMapper = $eventMapper; + } + + public function notifyObservers(IEvent $event) : void + { + $this->eventMapper->dispatchEvent($event); + } +} diff --git a/src/ObserverComponent/TObserverComponent.php b/src/ObserverComponent/TObserverComponent.php new file mode 100644 index 0000000..3f91dff --- /dev/null +++ b/src/ObserverComponent/TObserverComponent.php @@ -0,0 +1,22 @@ +eventMapper = $eventMapper; + $this->onAnchor[] = function (\Nette\ComponentModel\IComponent $parent) : void { + $this->eventMapper->registerObserver($this); + }; + } +}