Przeglądaj źródła

Initial commit of PSR-14 event dispatcher library

michelphp 1 dzień temu
commit
763bf1228e

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/vendor/
+/.idea/
+composer.lock

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 F. Michel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 118 - 0
README.md

@@ -0,0 +1,118 @@
+# PSR-14 Event Dispatcher
+
+This library provides an easy-to-use implementation of a PSR-14 event dispatcher, allowing you to manage event-driven functionality in your PHP applications.
+
+## Installation
+
+Make sure to include the library in your `composer.json` or install it directly:
+
+```bash
+composer require michem/psr14-event-dispatcher
+```
+
+## Creating an Event
+
+To create a custom event, extend the `Event` class provided by the library:
+
+```php
+<?php
+
+namespace App\Event;
+
+use Michel\EventDispatcher\Event;
+
+/**
+ * Class PreCreateEvent
+ * @package App\Event
+ */
+class PreCreateEvent extends Event
+{
+    private object $object;
+
+    /**
+     * PreCreateEvent constructor.
+     * 
+     * @param object $object
+     */
+    public function __construct(object $object)
+    {
+        $this->object = $object;
+    }
+
+    /**
+     * Get the associated object.
+     *
+     * @return object
+     */
+    public function getObject(): object
+    {
+        return $this->object;
+    }
+}
+```
+
+## Creating a Listener
+
+A listener class handles the event logic. It should implement an `__invoke` method that accepts the event as a parameter:
+
+```php
+<?php
+
+namespace App\Listener;
+
+use App\Entity\User;
+use App\Event\PreCreateEvent;
+
+/**
+ * Class UserListener
+ * @package App\Listener
+ */
+class UserListener
+{
+    /**
+     * Handle the event.
+     * 
+     * @param PreCreateEvent $event
+     */
+    public function __invoke(PreCreateEvent $event): void
+    {
+        $object = $event->getObject();
+
+        if ($object instanceof User) {
+            // Perform actions with the User object
+        }
+    }
+}
+```
+
+## Usage
+
+To use the event dispatcher, register your listeners with a `ListenerProvider` and dispatch events as needed:
+
+```php
+<?php
+
+use App\Event\PreCreateEvent;
+use App\Listener\UserListener;
+
+// Create the listener provider and register the listener
+$listenerProvider = (new ListenerProvider())
+    ->addListener(PreCreateEvent::class, new UserListener());
+
+// Create the event dispatcher with the listener provider
+$dispatcher = new \Michel\EventDispatcher\EventDispatcher($listenerProvider);
+
+// Dispatch the event after saving a user to the database
+$dispatcher->dispatch(new PreCreateEvent($user));
+```
+
+**Note**: When the `PreCreateEvent` is dispatched, `UserListener` will be automatically invoked if the event matches its type.
+
+## Example Use Case
+
+Suppose you have a `User` entity that requires additional logic to be executed after being persisted to the database, such as sending a welcome email or logging activity. You can use the `PreCreateEvent` and `UserListener` to encapsulate this behavior, keeping your code clean and following the event-driven design pattern.
+
+## License
+
+This library is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
+

+ 24 - 0
composer.json

@@ -0,0 +1,24 @@
+{
+    "name": "michel/psr14-event-dispatcher",
+    "description": "This library provides an easy-to-use implementation of a PSR-14 event dispatcher, allowing you to manage event-driven functionality in your PHP applications.",
+    "type": "library",
+    "require": {
+        "php": ">=7.4",
+        "psr/event-dispatcher": "^1.0"
+    },
+    "require-dev": {
+        "michel/unitester": "^1.0.0"
+    },
+    "license": "MIT",
+    "autoload": {
+        "psr-4": {
+            "Michel\\EventDispatcher\\": "src",
+            "Test\\Michel\\EventDispatcher\\": "tests"
+        }
+    },
+    "authors": [
+        {
+            "name": "Michel.F"
+        }
+    ]
+}

+ 44 - 0
src/Event.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Michel\EventDispatcher;
+
+use Psr\EventDispatcher\StoppableEventInterface;
+
+/**
+ * Class Event
+ * @package Michel\EventDispatcher
+ */
+class Event implements StoppableEventInterface
+{
+    /**
+     * @var bool Whether no further event listeners should be triggered
+     */
+    private bool $propagationStopped = false;
+
+    /**
+     * Is propagation stopped?
+     *
+     * This will typically only be used by the Dispatcher to determine if the
+     * previous listener halted propagation.
+     *
+     * @return bool
+     *   True if the Event is complete and no further listeners should be called.
+     *   False to continue calling listeners.
+     */
+    public function isPropagationStopped(): bool
+    {
+        return $this->propagationStopped;
+    }
+
+    /**
+     * Stops the propagation of the event to further event listeners.
+     *
+     * If multiple event listeners are connected to the same event, no
+     * further event listener will be triggered once any trigger calls
+     * stopPropagation().
+     */
+    public function stopPropagation(): void
+    {
+        $this->propagationStopped = true;
+    }
+}

+ 43 - 0
src/EventDispatcher.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Michel\EventDispatcher;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\EventDispatcher\ListenerProviderInterface;
+use Psr\EventDispatcher\StoppableEventInterface;
+
+/**
+ * Class EventDispatcher
+ */
+class EventDispatcher implements EventDispatcherInterface
+{
+    private ListenerProviderInterface $listenerProvider;
+
+    /**
+     * EventDispatcher constructor.
+     * @param ListenerProviderInterface $listenerProvider
+     */
+    public function __construct(ListenerProviderInterface $listenerProvider)
+    {
+        $this->listenerProvider = $listenerProvider;
+    }
+
+    /**
+     * @param object $event
+     * @return object
+     */
+    public function dispatch(object $event): object
+    {
+
+        if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
+            return $event;
+        }
+        foreach ($this->listenerProvider->getListenersForEvent($event) as $listener) {
+            $listener($event);
+            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
+                break;
+            }
+        }
+        return $event;
+    }
+}

+ 55 - 0
src/ListenerProvider.php

@@ -0,0 +1,55 @@
+<?php
+
+
+namespace Michel\EventDispatcher;
+
+
+use Psr\EventDispatcher\ListenerProviderInterface;
+
+/**
+ * Class ListenerProvider
+ * @package Michel\EventDispatcher
+ */
+class ListenerProvider implements ListenerProviderInterface
+{
+
+    private array $listeners = [];
+
+    /**
+     * @param object $event
+     *   An event for which to return the relevant listeners.
+     * @return iterable[callable]
+     *   An iterable (array, iterator, or generator) of callables.  Each
+     *   callable MUST be type-compatible with $event.
+     */
+    public function getListenersForEvent(object $event): iterable
+    {
+        $eventType = get_class($event);
+        if (array_key_exists($eventType, $this->listeners)) {
+            return $this->listeners[$eventType];
+        }
+
+        return [];
+    }
+
+    /**
+     * @param string $eventType
+     * @param callable $callable
+     * @return $this
+     */
+    public function addListener(string $eventType, callable $callable): self
+    {
+        $this->listeners[$eventType][] = $callable;
+        return $this;
+    }
+
+    /**
+     * @param string $eventType
+     */
+    public function clearListeners(string $eventType): void
+    {
+        if (array_key_exists($eventType, $this->listeners)) {
+            unset($this->listeners[$eventType]);
+        }
+    }
+}

+ 31 - 0
tests/Event/PreCreateEventTest.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Test\Michel\EventDispatcher\Event;
+
+use Michel\EventDispatcher\Event;
+
+final class PreCreateEventTest extends Event
+{
+
+    private object $object;
+
+    /**
+     * PreCreateEvent constructor.
+     *
+     * @param object $object
+     */
+    public function __construct(object $object)
+    {
+        $this->object = $object;
+    }
+
+    /**
+     * Get the associated object.
+     *
+     * @return object
+     */
+    public function getObject(): object
+    {
+        return $this->object;
+    }
+}

+ 23 - 0
tests/Listener/UserListenerTest.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace Test\Michel\EventDispatcher\Listener;
+
+use Test\Michel\EventDispatcher\Event\PreCreateEventTest;
+
+class UserListenerTest
+{
+
+    /**
+     * Handle the event.
+     *
+     * @param PreCreateEventTest $event
+     */
+    public function __invoke(PreCreateEventTest $event): void
+    {
+        $object = $event->getObject();
+        if ($object instanceof \stdClass) {
+            $object->foo = 'bar';
+        }
+    }
+
+}

+ 35 - 0
tests/ListenerProviderTest.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Test\Michel\EventDispatcher;
+
+use Michel\EventDispatcher\EventDispatcher;
+use Michel\EventDispatcher\ListenerProvider;
+use Michel\UniTester\TestCase;
+use Test\Michel\EventDispatcher\Event\PreCreateEventTest;
+use Test\Michel\EventDispatcher\Listener\UserListenerTest;
+
+class ListenerProviderTest extends TestCase
+{
+
+    protected function setUp(): void
+    {
+        // TODO: Implement setUp() method.
+    }
+
+    protected function tearDown(): void
+    {
+        // TODO: Implement tearDown() method.
+    }
+
+    protected function execute(): void
+    {
+        $listenerProvider = new ListenerProvider();
+        $listenerProvider->addListener(PreCreateEventTest::class, new UserListenerTest());
+        $dispatcher = new EventDispatcher($listenerProvider);
+        $user = new \stdClass();
+        $user->foo = null;
+        $dispatcher->dispatch(new PreCreateEventTest($user));
+
+        $this->assertStrictEquals("bar", $user->foo);
+    }
+}