New in Symfony 4.3: Simpler event dispatching

Contributed by
Nicolas Grekas
in #28920.

In Symfony 3.3, we made the class optional for named services and started recommending to use the fully-qualified class name (FQCN) as the service ID. This makes config more intuitive and helps you avoid thinking about arbitrary strings to name services.

Following the same idea, in Symfony 4.3 we've changed the signature of the EventDispatcherInterface::dispatch() method:

1
2
3
4
5
6
7
8
9
// ...
$order = new Order();
$newOrderEvent = new OrderPlacedEvent($order);

// Before
$dispatcher->dispatch(OrderEvents::NEW_ORDER, $newOrderEvent);

// After
$dispatcher->dispatch($newOrderEvent, OrderEvents::NEW_ORDER);

Although this change looks minor, it helps simplify the rest of the app code. For starters, you can subscribe to events using the FQCN of the event:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class StoreSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        // Before
        return [
            OrderEvents::NEW_ORDER => 'onStoreOrder',
        ];

        // After
        return [
            OrderPlacedEvent::class => 'onStoreOrder',
        ];
    }

    // ...
}

Moreover, the event name is now optional in the dispatch() method, so you can pass just the event object:

1
2
3
4
5
// Before
$dispatcher->dispatch(OrderEvents::NEW_ORDER, $newOrderEvent);

// After
$dispatcher->dispatch($newOrderEvent);

In summary, the new dispatch() signature allows you to develop code based on pure PHP classes instead of inventing arbitrary strings to name events.

Updated HttpKernel event classes

In addition to the previous change, we've also updated the classes of the events that Symfony passes for its own events. The new names are much more intuitive and concise:

  • Renamed FilterControllerArgumentsEvent to ControllerArgumentsEvent
  • Renamed FilterControllerEvent to ControllerEvent
  • Renamed FilterResponseEvent to ResponseEvent
  • Renamed GetResponseEvent to RequestEvent
  • Renamed GetResponseForControllerResultEvent to ViewEvent
  • Renamed GetResponseForExceptionEvent to ExceptionEvent
  • Renamed PostResponseEvent to TerminateEvent

Supporting both dispatchers

Bundles and packages that want to provide forward and/or backward compatibility with the new and old dispatch() methods can use the new LegacyEventDispatcherProxy class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;

class SomeService
{
    public function __construct(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
    }

    public function someMethod()
    {
        // ...

        // use the new way of dispatching events (with or without the event name)
        // even if the provided dispatcher still implements the legacy way
        $this->dispatcher->dispatch($newOrderEvent);
        $this->dispatcher->dispatch($newOrderEvent, OrderEvents::NEW_ORDER);
    }
}

Comments

Awesome feature! Thank you !
So good! I can't wait for this!
$this->dispatcher->dispatch() no ? in the SomeService::someMethod()
@Antoine yes! I've updated the blog post. Thanks.
Breaking changes? Order of dispatch arguments changes, what about BC compatibility ?
@Ksaveras this change won't be a "breaking change". Symfony 4.3 will make the needed changes internally so your code keeps working without changes and it will trigger deprecation notices (to upgrade to Symfony 5.0, released on November 2019, you'll need to change the argument order).

And if you develop some package that needs to support both methods at the same time, you can use the LegacyEventDispatcherProxy class as explained in the article.
👍
Does that implicitly mean that we have to make one class by event ?

I usually create one event class by entity with multiple constants for different events (e.g. MyEntityEvent::CREATE, MyEntityEvent::EDIT)
Will it become a bad practice in favor of having one class by event (e.g. MyEntityCreatedEvent, MyEntityEdited) ?
I don't hope so, because that would be a lot of code to duplicate...
In what version of Symfony will LegacyEventDispatcherProxy be removed? 5.0 or 6.0?
@Mickaël you can keep using arbitrary strings for event names. The only change is that the argument order has changed (in 4.x it will keep working, but when upgrading to 5.0 you'll need to make the change).
@Javier thanks, I just wanted to be sure this optional string is not temporary and will not be removed in future release ;-)
> In what version of Symfony will LegacyEventDispatcherProxy be removed? 5.0 or 6.0?

The plan is to deprecate it in 5.1 and remove it in 6.0.

> I just wanted to be sure this optional string is not temporary and will not be removed in future release

legit question, I confirm what Javier wrote: named events will remain 1st class supported
At last!!!
What would be the recommendation for cases where you have multiple events of the same type? Split them into multiple classes?
@Diego, if the new way of using PHP classes doesn't work for your case, you can keep using the same exact code as today, with the same strings to name your events.

the only change you'll need to do is to swap the order of the arguments in the dispatch() method (but only in Symfony 5.0, because in 4.3 and 4.4 will keep working) .
One step closer to Laravel! Nice ;-)
The version 4.3 is going to be incredibly good(The best), I'm looking forward to start working with 4.3 since it will bring things I needed since first minut.

Thanks team.
I was wrong with this post, I thought that with this update you did not have to create two classes, one to name the event and another to obtain it. Well I hope that someday you can see this.
Another great news!

We use this approach for the last 3 years, so I'm very happy it got into Symfony core:
https://pehapkari.cz/blog/2017/07/12/the-bulletproof-event-naming-for-symfony-event-dispatcher/
Will there be sth like hasListenersForThisKindOfEvent() ?

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.