Event bus bundle

Using the building blocks supplied by the SimpleBus/MessageBus library you can create an event bus, which is basically a message bus, with some middlewares and a collection of message subscribers. This is described in the documentation of EventBus.

Using the event bus

This bundle provides the event_bus service which is an instance of SimpleBus\SymfonyBridge\Bus\MessageBus. Wherever you like, you can let it handle events, e.g. by fetching it inside a container-aware controller:

1
2
3
4
// $event is an arbitrary object that will be passed to the event subscriber
$event = ...;

$this->get('event_bus')->handle($event);

However, you are encouraged to properly inject the event_bus service as a dependency whenever you need it:

1
2
3
4
5
services:
    some_service:
        class: Acme\Foobar
        arguments:
            - "@event_bus"

This bundle can be used with Symfony’s Autowiring out of the box.

Simply inject SimpleBus\SymfonyBridge\Bus\EventBus in your controller or service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
namespace App\Service;

use SimpleBus\SymfonyBridge\Bus\EventBus;

class SomeService
{
    private $eventBus;

    public function __construct(EventBus $eventBus)
    {
        $this->eventBus = $eventBus;
    }

    public function __invoke()
    {
        $this->eventBus->handle(new SomethingHappenedEvent());
    }
}

Registering event subscribers

As described in the EventBus documentation you can notify event subscribers about the occurrence of a particular event. This bundle allows you to register your own event subscribers by adding the event_subscriber tag to the event subscriber’s service definition:

1
2
3
4
5
services:
    user_registered_event_subscriber:
        class: Fully\Qualified\Class\Name\Of\UserRegisteredEventSubscriber
        tags:
            - { name: event_subscriber, subscribes_to: Fully\Qualified\Class\Name\Of\UserRegistered }

Note

Event subscribers are lazy-loaded

Since only some of the event subscribers are going to handle any particular event, event subscribers are lazy-loaded. This means that their services should be defined as public services (i.e. you can’t use public: false for them).

Event subscribers are callables

Any service that is a PHP callable itself can be used as an event subscriber. If a service itself is not callable, SimpleBus looks for a __invoke or notify method and calls it. If you want to use a custom method, just add a method attribute to the event_subscriber tag:

1
2
3
4
5
services:
    user_registered_event_subscriber:
        ...
        tags:
            - { name: event_subscriber, subscribes_to: ..., method: userRegistered }

If you are using Autowiring you can use the following configuration:

1
2
3
4
5
6
7
8
9
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\Subscriber\:
        resource: '%kernel.project_dir%/src/Subscriber'
        public: true
        tags: [{ name: 'event_subscriber' }]

This will search for all subscribers in the src/Subscriber directory and automatically detects the event that the subscriber is subscribing to.

One subscriber listening to multiple events

When you have 1 subscriber that is listening to multiple events you might want to set the register_public_methods attribute to true:

1
2
3
4
5
6
7
8
9
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\Subscriber\:
        resource: '%kernel.project_dir%/src/Subscriber'
        public: true
        tags: [{ name: 'event_subscriber', register_public_methods: true }]

With the following code for the subscriber:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
namespace App\Subscriber;

use App\Event\EventAddedEvent;
use App\Event\VenueAddedEvent;

class ElasticSearchSubscriber
{
    public function onEventAdded(EventAddedEvent $event)
    {
        // Add the event to ElasticSearch
    }

    public function onVenueAdded(VenueAddedEvent $event)
    {
        // Add the venue to ElasticSearch
    }
}

SimpleBus automatically detects that ElasticSearchSubscriber wants to subscribe to both EventAddedEvent and VenueAddedEvent.

Setting the event name resolving strategy

To find the correct event subscribers for a given event, the name of the event is used. This can be either 1) its fully- qualified class name (FQCN) or, 2) if the event implements the SimpleBus\Message\Name\NamedMessage interface, the value returned by its static messageName() method. By default, the first strategy is used, but you can configure it in your application configuration:

1
2
3
event_bus:
    # default value for this key is "class_based"
    event_name_resolver_strategy: named_message

When you change the strategy, you also have to change the value of the subscribes_to attribute of your event subscriber service definitions:

1
2
3
4
5
services:
    user_registered_event_subscriber:
        class: Fully\Qualified\Class\Name\Of\UserRegisteredEventSubscriber
        tags:
            - { name: event_subscriber, subscribes_to: user_registered }

Make sure that the value of subscribes_to matches the return value of UserRegistered::messageName().

Adding event bus middlewares

As described in the MessageBus documentation you can extend the behavior of the event bus by adding middlewares to it. This bundle allows you to register your own middlewares by adding the event_bus_middleware tag to middleware service definitions:

1
2
3
4
5
6
services:
    specialized_event_bus_middleware:
        class: YourSpecializedEventBusMiddleware
        public: false
        tags:
            - { name: event_bus_middleware, priority: 100 }

By providing a value for the priority tag attribute you can influence the order in which middlewares are added to the event bus.

Note

Middlewares are not lazy-loaded

Whenever you use the event bus, you also use all of its middlewares, so event bus middlewares are not lazy-loaded. This means that their services should be defined as private services (i.e. you should use public: false). See also: Marking Services as public / private

Event recorders

Recording events

As explained in the documentation of MessageBus you can collect events while a command is being handled. If you want to record new events you can inject the event_recorder service as a constructor argument of a command handler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use SimpleBus\Message\Recorder\RecordsMessages;

class SomeInterestingCommandHandler
{
    private $eventRecorder;

    public function __construct(RecordsMessages $eventRecorder)
    {
        $this->eventRecorder = $eventRecorder;
    }

    public function handle($command)
    {
        ...

        // create an event
        $event = new SomethingInterestingHappened();

        // record the event
        $this->eventRecorder->record($event);
    }
}

The corresponding service definition looks like this:

1
2
3
4
5
6
services:
    some_interesting_command_handler:
    arguments:
        - @event_recorder
    tags:
        - { name: command_handler, handles: Fully\Qualified\Name\Of\SomeInterestingCommand

Recorded events will be handled after the command has been completely handled.

Registering your own message recorders

In case you have another source for recorded message (for instance a class that collects domain events like the DoctrineORMBridge does), you can register it as a message recorder:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use SimpleBus\Message\Recorder\ContainsRecordedMessages;

class PropelDomainEvents implements ContainsRecordedMessages
{
    public function recordedMessages()
    {
        // return an array of Message instances
    }

    public function eraseMessages()
    {
        // clear the internal array containing the recorded messages
    }
}

The corresponding service definition looks like this:

1
2
3
4
5
6
services:
    propel_domain_events:
        class: Fully\Qualified\Class\Name\Of\PropelDomainEvents
        public: false
        tags:
            - { name: event_recorder }

Note

Logging

If you want to log every event that is being handled, enable logging in config.yml:

1
2
event_bus:
    logging: ~

Messages will be logged to the event_bus channel.