Updated with Symfony 4.3 syntax, that supports class-name events out of the box.
I wrote intro to Symfony\EventDispatcher and how to use it with simple event.
But when it comes to dispatching events, you can choose from 4 different ways. Which one to choose and why? Today I will show you pros and cons of them to make it easier for you.
You can start with simple string named event:
$postEvent = new PostEvent($post);
$this->eventDispatcher->dispatch('post_added', $postEvent);
Simple for start and easy to use for one place and one event.
One day I started to use in more places:
$postEvent = new PostEvent($post);
$this->eventDispatcher->dispatch('post_add', $postEvent);
All looked good, but the subscriber didn't work. Fun time with event subscribers debugging was about to come.
Hour has passed. Event subscriber was registered as a service, tagged, collected by dispatcher... but I still couldn't find the issue. So I showed it to my colleague:
Oh, you've got "post_add" there, but there should be "post_added".
YAY! I copied the previous subscriber with "post_added" but I made a typo while dispatching event.
There must be a cure for this, I wished.
Then I got inspired by Symfony ConsoleEvents
class that collects all events from one domain in constants.
final class PostEvents
{
/**
* This event is invoked when post is added.
* It is called here @see \App\Post\PostService::add().
* And @see \App\Events\PostAddedEvent class is passed.
*
* @var string
*/
public const ON_POST_ADDED = 'post_added';
/**
* This event is invoked when post is published.
* It is called here @see \App\Post\PostService::published().
* And @see \App\Events\PostPublishedEvent class is passed.
*
* @var string
*/
public const ON_POST_PUBLISHED = 'post_published';
}
Our first example will change from stringly to strongly typed:
$postAddedEvent = new PostAddedEvent($post);
$this->eventDispatcher(PostEvents::ON_POST_ADDED, $postAddedEvent)
Also subscriber becomes typo-proof:
final class TagPostSubscriber implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [PostEvents::ON_POST_ADDED => 'tagPost'];
}
public function tagPost(PostAddedEvent $postAddedEvent): void
{
// ...
}
}
EventClass
doesn't work in PHPStorm, but @see EventClass
doesThe more events you have the harder is this to maintain properly. With 5th event you might end up like this:
final class PostEvents
{
/**
* This event is invoked when post is published.
* It is called here @see \App\Post\PostService::published().
* And @see \App\Events\PostPublishedEvent class is passed.
*
* @var string
*/
public const ON_POST_PUBLISHED = 'post_published';
// 3 more nicely annotated events...
public const ON_POST_CHANGED = 'changed';
}
I wanted to respect open-closed principle, so global class was a no-go.
Maybe, I could put those...
Like this:
final class PostAddedEvent
{
/**
* @var string
*/
public const NAME = 'post_added';
/**
* @var Post
*/
private $post;
public function __construct(Post $post)
{
$this->post = $post;
}
}
Our example is now strongly typed and respects open-closed principle:
$postAddedEvent = new PostAddedEvent($post);
$this->eventDispatcher(PostAddedEvent::NAME, $postAddedEvent)
Like this!
All the above +
constant NAME = '...'
unique per-class.Take a step back: what is my goal?
I look for an identifier that is:
Can you see it? I think you do :)
$postAddedEvent = new PostAddedEvent($post);
$this->eventDispatcher(PostAddedEvent::class, $postAddedEvent)
It could not be simpler and meets all the conditions!
All 4 reasons above +
::class
support.This is my story for event naming evolution. But what is yours - which event naming system do you use? I'm curious and ready to be wrong, so please let me know in the comments if you like it or do it any different way.
Enumag suggested such different way by removing first argument:
public function dispatch(Event $event): void
{
$this->eventDispatcher->dispatch(get_class($event), $event);
}
And exactly this is possible since Symfony 4.3 (2019):
$postAddedEvent = new PostAddedEvent($post);
$this->eventDispatcher($postAddedEvent);
// or in case we don't need to get changed content from the event
$this->eventDispatcher->dispatch(new PostAddedEvent($post));
Happy coding!
Do you learn from my contents or use open-source packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!