PHP Logosymfony/event-dispatcher

The `symfony/event-dispatcher` component is a powerful and standalone PHP library that provides a way to implement the Observer (or Publish-Subscribe) design pattern in your applications. It allows different parts of your application to communicate with each another without tight coupling, promoting a highly extensible and maintainable codebase.

Core Concepts:

1. Event: An event is an object that represents something that has happened in your application. It usually carries data related to that occurrence. In Symfony, events typically extend `Symfony\Contracts\EventDispatcher\Event` or implement `Psr\EventDispatcher\StoppableEventInterface` if you want to allow listeners to stop propagation.
2. Event Dispatcher: This is the central object responsible for dispatching events and notifying all registered listeners. It implements `Symfony\Contracts\EventDispatcher\EventDispatcherInterface`.
3. Listener (or Observer): A listener is a callable (a function, a static method, an object method) that executes when a specific event is dispatched. Listeners are registered with the dispatcher for one or more event names.
4. Subscriber: An event subscriber is a class that implements `Symfony\Component\EventDispatcher\EventSubscriberInterface`. Instead of registering multiple listeners individually, a subscriber defines which events it listens to and what methods should be called for each event within the class itself. This can help organize event handling logic.

How it Works:

* Defining Events: You define an event by creating a class, often extending `Symfony\Contracts\EventDispatcher\Event`. This class can hold any data relevant to the event.
* Registering Listeners/Subscribers: You register a listener (a callable) or a subscriber object with the event dispatcher, associating it with one or more specific event names. You can also specify a priority for listeners, determining the order in which they are executed.
* Dispatching Events: When something interesting happens in your application (e.g., a user registers, an order is placed, a file is uploaded), you create an instance of the relevant event class, populate it with data, and pass it to the `dispatch()` method of the event dispatcher along with an event name (a unique string identifier).
* Execution: The event dispatcher iterates through all registered listeners and subscribers for that specific event name and invokes their associated callables/methods. Listeners can modify the event object, potentially changing the behavior for subsequent listeners or the original dispatcher.

Benefits:

* Decoupling: Components don't need to know about each other. A component simply dispatches an event, and other components listen without direct dependencies.
* Extensibility: New features can be added by simply adding new listeners without modifying existing code. This is crucial for plugins and modules.
* Maintainability: Code becomes easier to understand and maintain as responsibilities are clearly separated.
* Reusability: Event listeners can be reused across different parts of an application or even different applications.
* Testability: Individual components (dispatchers, events, listeners) can be tested in isolation.

Example Code

<?php

require 'vendor/autoload.php';

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Contracts\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

// 1. Define a custom Event class
class UserRegisteredEvent extends Event
{
    public const NAME = 'user.registered';

    private string $username;
    private string $email;

    public function __construct(string $username, string $email)
    {
        $this->username = $username;
        $this->email = $email;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function getEmail(): string
    {
        return $this->email;
    }
}

// 2. Create an Event Dispatcher instance
$dispatcher = new EventDispatcher();

// 3. Register Listeners

// Listener 1: Using a simple callable (function or static method)
$dispatcher->addListener(UserRegisteredEvent::NAME, function (UserRegisteredEvent $event) {
    echo "[Listener 1] New user '{$event->getUsername()}' with email '{$event->getEmail()}' registered! Sending welcome email.\n";
});

// Listener 2: Using an object method (more common for complex logic)
class NotificationService
{
    public function onUserRegistered(UserRegisteredEvent $event): void
    {
        echo "[Listener 2] NotificationService: Notifying admin about new user '{$event->getUsername()}'.\n";
    }
}

$notificationService = new NotificationService();
$dispatcher->addListener(UserRegisteredEvent::NAME, [$notificationService, 'onUserRegistered']);

// Listener 3: With a priority (higher number means executed earlier)
$dispatcher->addListener(UserRegisteredEvent::NAME, function (UserRegisteredEvent $event) {
    echo "[Listener 3] Performing some pre-registration check for '{$event->getUsername()}'.\n";
}, 100); // Priority 100

// 4. Register an Event Subscriber
class AnalyticsSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            UserRegisteredEvent::NAME => 'onUserRegistered',
            // You can listen to multiple events with different methods
            // 'another.event' => ['handleAnotherEvent', 50], // Method name and priority
        ];
    }

    public function onUserRegistered(UserRegisteredEvent $event): void
    {
        echo "[Subscriber] AnalyticsService: Recording user registration for '{$event->getUsername()}'.\n";
    }
}

$analyticsSubscriber = new AnalyticsSubscriber();
$dispatcher->addSubscriber($analyticsSubscriber);

// 5. Dispatch the Event
echo "\n--- Dispatching UserRegisteredEvent ---\n";
$userRegisteredEvent = new UserRegisteredEvent('john_doe', 'john.doe@example.com');
$dispatcher->dispatch($userRegisteredEvent, UserRegisteredEvent::NAME);

echo "\n--- End of Dispatch ---\n";


// Example of another event (optional, to show extensibility)
class ProductViewedEvent extends Event
{
    public const NAME = 'product.viewed';
    private int $productId;

    public function __construct(int $productId)
    {
        $this->productId = $productId;
    }

    public function getProductId(): int
    {
        return $this->productId;
    }
}

class ProductLogListener
{
    public function onProductViewed(ProductViewedEvent $event): void
    {
        echo "[Listener for ProductViewed] Logging product view for product ID: {$event->getProductId()}.\n";
    }
}

$productLogListener = new ProductLogListener();
$dispatcher->addListener(ProductViewedEvent::NAME, [$productLogListener, 'onProductViewed']);

echo "\n--- Dispatching ProductViewedEvent ---\n";
$productViewedEvent = new ProductViewedEvent(123);
$dispatcher->dispatch($productViewedEvent, ProductViewedEvent::NAME);


/*
To run this code:
1. Make sure you have Composer installed.
2. Create a new directory, e.g., `event_dispatcher_example`.
3. Navigate into the directory and run `composer require symfony/event-dispatcher`.
4. Save the above code as `index.php` (or any other filename).
5. Run `php index.php` in your terminal.

Expected Output:

--- Dispatching UserRegisteredEvent ---
[Listener 3] Performing some pre-registration check for 'john_doe'.
[Listener 1] New user 'john_doe' with email 'john.doe@example.com' registered! Sending welcome email.
[Listener 2] NotificationService: Notifying admin about new user 'john_doe'.
[Subscriber] AnalyticsService: Recording user registration for 'john_doe'.

--- End of Dispatch ---

--- Dispatching ProductViewedEvent ---
[Listener for ProductViewed] Logging product view for product ID: 123.
*/