PSR-14 w praktyce: Event Dispatcher w PHP | MasterPHP

28.08.2025

PSR-14 w praktyce: Event Dispatcher w PHP

Eventy to taki temat, który w PHP wraca jak bumerang. Każdy framework ma swoje rozwiązania, ale prędzej czy później pojawia się pytanie: „a czemu nie ma jednego standardu?”. No i w końcu jest. PSR-14 wprowadza wspólny interfejs dla systemów event dispatchingu, dzięki czemu aplikacje, biblioteki i frameworki mogą korzystać z tego samego modelu zdarzeń.

Na czym polega PSR-14?

Cała idea jest prosta: mamy event – obiekt, który coś się wydarzyło – i mamy listenerów, którzy na ten event reagują. Dispatcher odpala event, provider podpowiada kogo wołać, a słuchacze robią resztę.

Przykładowa implementacja

W kursie przygotałem minimalistyczną, ale w pełni zgodną z PSR-14 implementację. Oparta jest o trzy główne pliki:

EventDispatcher.php

Dispatcher to centrum całego systemu. Dostaje event i pyta providera: „kto chce na to zareagować?”. Następnie przechodzi przez listę listenerów i odpala ich w odpowiedniej kolejności. Żadnej magii – tylko prosty, przejrzysty kod.

<?php

declare(strict_types=1);

namespace DJWeb\Framework\Events;

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

final readonly class EventDispatcher implements EventDispatcherInterface
{
    public function __construct(
        private ListenerProviderInterface $listenerProvider
    ) {
    }

    public function dispatch(object $event): object
    {
        $listeners = (array) $this->listenerProvider->getListenersForEvent($event);
        $listeners = array_filter($listeners, 'is_callable');

        foreach ($listeners as $listener) {
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
                break;
            }

            $listener($event);
        }

        return $event;
    }
}

ListenerProvider.php

To serce rejestracji słuchaczy. Provider wie, które klasy nasłuchują na jakie eventy i zwraca tę listę, gdy poprosi o nią dispatcher. Dzięki temu eventy są całkowicie niezależne od konkretnych listenerów.

<?php

declare(strict_types=1);

namespace DJWeb\Framework\Events;

use Psr\EventDispatcher\ListenerProviderInterface;

class ListenerProvider implements ListenerProviderInterface
{
    /**
     * @var array<string, array<callable>>
     */
    private array $listeners = [];

    public function addListener(string $eventClass, callable $listener): void
    {
        $this->listeners[$eventClass] ??= [];
        $this->listeners[$eventClass][] = $listener;
    }

    public function getListenersForEvent(object $event): iterable
    {
        $eventClass = $event::class;
        return $this->listeners[$eventClass] ?? [];
    }
}

EventManager.php

Zamiast żonglować dispatcherem i providerem, mamy prostą fasadę – EventManager. To ona udostępnia wygodne API do rejestrowania listenerów i wywoływania eventów. Dla programisty oznacza to jedno: prostotę i czytelność.

<?php

declare(strict_types=1);

namespace DJWeb\Framework\Events;

use DJWeb\Framework\Config\Config;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;

class EventManager
{
    public EventDispatcherInterface $dispatcher{
        get => $this->_eventDispatcher ??= new EventDispatcher($this->listenerProvider);
    }
    public ListenerProviderInterface $listenerProvider{
        get => $this->_listenerProvider ??= $this->createListenerProvider();
    }
    private ?EventDispatcherInterface $_eventDispatcher = null;
    private ?ListenerProviderInterface $_listenerProvider = null;

    public function dispatch(object $event): object
    {
        return $this->dispatcher->dispatch($event);
    }

    private function createListenerProvider(): ListenerProviderInterface
    {
        $provider = new ListenerProvider();

        $eventConfig = Config::get('events.listeners');

        foreach ($eventConfig as $eventClass => $listeners) {
            foreach ((array) $listeners as $listenerClass) {
                $provider->addListener($eventClass, $listenerClass);
            }
        }

        return $provider;
    }
}

Dlaczego warto?

Dzięki PSR-14 możesz pisać kod oparty o eventy bez przywiązywania się do jednego frameworka. Taka implementacja jest lekka, zgodna z kontraktem i łatwa do rozszerzenia. A jak kiedyś będziesz chciał podmienić dispatcher na inny – żaden problem, bo interfejs pozostaje ten sam.

📖 Oficjalna specyfikacja PSR-14: https://www.php-fig.org/psr/psr-14/

👉 To tylko fragment pełnego kursu MasterPHP!

W kursie znajdziesz kompletną implementację PSR-14 wraz z przykładami:

  • jak pisać własne eventy i listenery,
  • jak używać EventManagera jako centralnego punktu,
  • jak testować eventy w praktyce.
Sprawdź pełny kurs