DEV Community

loading...
Cover image for PHP - Create your own PHP PSR-14 event dispatcher

PHP - Create your own PHP PSR-14 event dispatcher

fadymr profile image F.R Michel ・2 min read

PHP Event Dispatcher PSR-14

Create an Event dispatcher is very easy

Install the standard interfaces for event handling.:

composer require psr/event-dispatcher
Enter fullscreen mode Exit fullscreen mode

Create EventDispatcher class to disptach event

<?php

namespace DevCoder\Listener;

use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Psr\EventDispatcher\StoppableEventInterface;

/**
 * Class EventDispatcher
 */
class EventDispatcher implements EventDispatcherInterface
{
    /**
     * @var ListenerProviderInterface
     */
    private $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);
        }
        return $event;
    }
}
Enter fullscreen mode Exit fullscreen mode

Create ListenerProvider class to sorage all listener

<?php


namespace DevCoder\Listener;


use Psr\EventDispatcher\ListenerProviderInterface;

/**
 * Class ListenerProvider
 * @package DevCoder\Listener
 */
class ListenerProvider implements ListenerProviderInterface
{

    /**
     * @var array
     */
    private $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]);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Create Event class

<?php

namespace DevCoder\Listener;

use Psr\EventDispatcher\StoppableEventInterface;

/**
 * Class Event
 * @package DevCoder\Listener
 */
class Event implements StoppableEventInterface
{
    /**
     * @var bool Whether no further event listeners should be triggered
     */
    private $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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Create an Event

<?php


namespace App\Event;

use App\Entity\User;
use DevCoder\Listener\Event;

/**
 * Class PreCreateEvent
 * @package App\Event
 */
class PreCreateEvent extends Event
{

    /**
     * @var object
     */
    private $object;

    /**
     * PreCreateEvent constructor.
     * @param object $object
     */
    public function __construct(object $object)
    {
        $this->object = $object;
    }

    /**
     * @return object
     */
    public function getObject(): object
    {
        return $this->object;
    }
}
Enter fullscreen mode Exit fullscreen mode

Create listener

<?php

namespace App\Listener;

use App\Entity\User;
use App\Event\PreCreateEvent;

/**
 * Class SecurityListener
 * @package App\Listener
 */
class UserListener
{
    /**
     * @param PreCreateEvent $event
     */
    public function __invoke(PreCreateEvent $event): void
    {
        $object = $event->getObject();

        if ($object instanceof User) {
            // do something
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

How to use ?

$listenerProvider = (new ListenerProvider())
        ->addListener(PreCreateEvent::class, new UserListener());

$dispatcher = new EventDispatcher($listenerProvider);

// After flush user in database send event

$dispatcher = new EventDispatcher($listenerProvider);
$dispatcher->dispatch(new PreCreateEvent($user));
Enter fullscreen mode Exit fullscreen mode

UserListener::class will be automatically call

Ideal for small project
Simple and easy!
https://github.com/devcoder-xyz/php-event-dispatcher

Discussion (5)

pic
Editor guide
Collapse
dopitz profile image
Daniel O.

For which use-cases could this event dispatcher be used?
Do you have some real-world examples?

Collapse
fadymr profile image
F.R Michel Author

Consider the real-world example where you want dispatch event when a user login
The event listener will be executed to set last connexion date :

$user->setLastLogin(new \DateTime());
$user->save();

Collapse
dopitz profile image
Daniel O.

Thanks for your answer. Why should I store the user's last login date with an event dispatcher when I could do the same without events?

Thread Thread
fadymr profile image
F.R Michel Author • Edited

What if there are two ways to authenticate on your application, Login and password | JWT ? you would have to duplicate the code. With a single event you fixes the problem.
In addition, give you the possibility of more flexibility without modifying the core of your code.

Thread Thread

Some comments have been hidden by the post's author - find out more