Event Dispatching

FrameworkServiceProvider also aggregates FastForward\EventDispatcher\ServiceProvider\EventDispatcherServiceProvider . That means the framework container can resolve a PSR-14 dispatcher immediately, even before you configure any listeners.

Resolve dispatcher services

use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface;

$dispatcher = $container->get(EventDispatcherInterface::class);
$symfonyDispatcher = $container->get(SymfonyEventDispatcherInterface::class);
$listenerProvider = $container->get(ListenerProviderInterface::class);

The PSR-14 and Symfony contracts interfaces resolve to the same dispatcher implementation.

Register listeners through configuration

The most practical way to add listeners is to provide them under the Psr\EventDispatcher\ListenerProviderInterface configuration key.

use FastForward\Config\ArrayConfig;
use FastForward\Container\ContainerInterface;
use FastForward\Framework\ServiceProvider\FrameworkServiceProvider;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;

use function FastForward\Container\container;

final readonly class UserRegistered
{
    public function __construct(public string $email) {}
}

final class SendWelcomeEmailListener
{
    public function __invoke(UserRegistered $event): void
    {
        echo 'Sending welcome email to ' . $event->email . PHP_EOL;
    }
}

$config = new ArrayConfig([
    ContainerInterface::class => [
        FrameworkServiceProvider::class,
    ],
    ListenerProviderInterface::class => [
        SendWelcomeEmailListener::class,
    ],
]);

$container = container($config);
$dispatcher = $container->get(EventDispatcherInterface::class);

$dispatcher->dispatch(new UserRegistered('demo@example.com'));

Named events and Symfony subscribers

If your project uses Symfony subscribers or #[AsEventListener] attributes, add symfony/event-dispatcher to your application dependencies.

use FastForward\Config\ArrayConfig;
use FastForward\Container\ContainerInterface;
use FastForward\Framework\ServiceProvider\FrameworkServiceProvider;
use Psr\EventDispatcher\ListenerProviderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

use function FastForward\Container\container;

final readonly class PaymentReceived
{
    public function __construct(public string $invoiceId) {}
}

final class BillingSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            'billing.payment_received' => 'onPaymentReceived',
        ];
    }

    public function onPaymentReceived(PaymentReceived $event): void
    {
        echo 'Payment confirmed for invoice ' . $event->invoiceId . PHP_EOL;
    }
}

$config = new ArrayConfig([
    ContainerInterface::class => [
        FrameworkServiceProvider::class,
    ],
    ListenerProviderInterface::class => [
        BillingSubscriber::class,
    ],
]);

$container = container($config);
$dispatcher = $container->get(EventDispatcherInterface::class);

$dispatcher->dispatch(
    new PaymentReceived('INV-2026-0001'),
    'billing.payment_received',
);

Listener classification rules

The event-dispatcher integration classifies configured entries automatically:

  • invokable listeners and plain callables are routed to the reflection-based listener provider
  • Symfony subscribers are routed to the event-subscriber listener provider
  • listeners using #[AsEventListener] are routed to the prioritized listener provider
  • custom listener-provider classes are added to the aggregate provider directly