DEV Community

Cover image for PHP - Create your own PHP PSR-14 event dispatcher
F.R Michel
F.R Michel

Posted on

PHP - Create your own PHP PSR-14 event dispatcher

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

Top comments (5)

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

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 • 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
 
dopitz profile image
Info Comment hidden by post author - thread only accessible via permalink
Daniel O.

If I have two ways to authenticate I would separate the use-case specific logic into different (application) services and the shared logic into a domain service class.

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