Event Constants & @Event Docs
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeThere's one way we can make this better, and all high quality bundles do this: set the event name as a constant, instead of just having this random string. It's even a bit cooler than it sounds.
In the Event
directory, create a new class: KnpULoremIpsumEvents
. If your bundle dispatches events, you should typically have one class that has a constant for each event. It's a one-stop place to find all the event hook points.
// ... lines 1 - 4 | |
final class KnpULoremIpsumEvents | |
{ | |
// ... lines 7 - 14 | |
} |
Make this class final
... which isn't too important... but in general, you should considering making any class in a shareable library final
, unless you do want people to be able to sub-class it. Using final
is always a safe bet and can be removed later.
Anyways, add const FILTER_API = ''
, go copy the event name and paste it here.
// ... lines 1 - 13 | |
const FILTER_API = 'knpu_lorem_ipsum.filter_api'; | |
// ... lines 15 - 16 |
Now, of course, replace that string in the controller with KnpULoremIpsumEvents::FILTER_API
.
// ... lines 1 - 10 | |
class IpsumApiController extends AbstractController | |
{ | |
// ... lines 13 - 22 | |
public function index() | |
{ | |
// ... lines 25 - 30 | |
if ($this->eventDispatcher) { | |
$this->eventDispatcher->dispatch(KnpULoremIpsumEvents::FILTER_API, $event); | |
} | |
// ... lines 34 - 35 | |
} | |
} |
So, this is nice! Though, the reason I really like this is that it gives us a proper place to document the purpose of this event: why you would listen to it and the types of things you can do.
The Special @Event Documentation
But the coolest part is this: add @Event()
, and then inside double quotes, put the full class name of the event that listeners will receive. In other words, copy the namespace from the event class, paste it here and add \FilterApiResponseEvent
.
// ... lines 1 - 4 | |
final class KnpULoremIpsumEvents | |
{ | |
/** | |
* Called directly before the Lorem Ipsum API data is returned. | |
* | |
* Listeners have the opportunity to change that data. | |
* | |
* @Event("KnpU\LoremIpsumBundle\Event\FilterApiResponseEvent") | |
*/ | |
const FILTER_API = 'knpu_lorem_ipsum.filter_api'; | |
} |
What the heck does this do? On a technical level, absolutely nothing! This is purely documentation. But! Some systems - like PhpStorm - know to parse this and use it to help us when we're building event subscribers. We'll see exactly what I'm talking about in a minute. But, it's at least good documentation: if you listen to this event, this is the event object you should expect.
Creating an EventSubscriber
And... we're done! I'm not going to write a test for this, but I do at least want to make sure it works in my project. Move back over to the application code. Inside src/
, create a new directory called EventSubscriber
. Then, a new class called AddMessageToIpsumApiSubscriber
.
// ... lines 1 - 8 | |
class AddMessageToIpsumApiSubscriber implements EventSubscriberInterface | |
{ | |
// ... lines 11 - 23 | |
} |
Like all subscribers, this needs to implement EventSubscriberInterface
. Then I'll go to the Code -> Generate menu, or Command + N on a Mac, select Implement Methods, and add getSubscribedEvents
.
// ... lines 1 - 10 | |
public static function getSubscribedEvents() | |
{ | |
// ... lines 13 - 15 | |
} | |
// ... lines 17 - 25 |
Before we fill this in, I want to make sure that PhpStorm is fully synchronized with how our bundle looks - sometimes the symlink gets stale. Right click on the vendor/knpuniversity/lorem-ipsum-bundle
directory, and click "Synchronize".
Cool: now it will definitely see the new event classes. When it's done indexing, return an array with KnpULoremIpsumEvents::FILTER_API
set to, how about, onFilterApi
.
// ... lines 1 - 10 | |
public static function getSubscribedEvents() | |
{ | |
return [ | |
KnpULoremIpsumEvents::FILTER_API => 'onFilterApi', | |
]; | |
} | |
// ... lines 17 - 25 |
Ready for the magic? Thanks to the Symfony plugin, we can hover over the method name, press Alt + Enter and select "Create Method". Woh! It added the onFilterApi
method for me and type-hinted the first argument with FilterApiResponseEvent
! But, how did it know that this was the right event class?
// ... lines 1 - 17 | |
public function onFilterApi(FilterApiResponseEvent $event) | |
{ | |
// ... lines 20 - 22 | |
} | |
// ... lines 24 - 25 |
It knew that thanks to the @Event()
documentation we added earlier.
Inside the method, let's say $data = $event->getData()
and then add a new key called message
set to, the very important, "Have a magical day". Finally, set that data back on the event with $event->setData($data)
.
// ... lines 1 - 17 | |
public function onFilterApi(FilterApiResponseEvent $event) | |
{ | |
$data = $event->getData(); | |
$data['message'] = 'Have a magical day!'; | |
$event->setData($data); | |
} | |
// ... lines 24 - 25 |
That is it! Thanks to Symfony's service auto-configuration, this is already a service and it will already be an event subscriber. In other words, go refresh the API endpoint. It, just, works! Our controller is now extensible, without the user needing to override it. Dispatching events is most commonly done in controllers, but you could dispatch them in any service.
Next, let's improve our word provider setup by making it a true plugin system with dependency injection tags and compiler passes. Woh.
May I ask you why do you use a separate storage for event names? Why using Event::class, or at least, Event::getName() or Event::EVEN_NAME is not the preferred approach? I understand, it's seducing to keep the list of the Bundle Event Names in one, separate place, but, by doing this you
1) make the code less straight-forward, just compare this:
<blockquote>return [
];</blockquote>
and this
`return [
];`
2) actually double the code by adding Event's class namespace and description to @Event annotation. Like, if you alter the Event's functionality, besides the Event doc, you will also have to update the @Event annotation - one change, but double places.