Updated Rector YAML to PHP configuration, as current standard.
I wrote Don't Ever use Symfony Listeners 2 months ago (if you missed it, be sure to read it to better understand this 2nd part). It got many constructive comments, mostly focused on particular standalone sentences without context.
To my surprise, none of the comments shown that listener beats subscriber.
But what can you do, if you'd like to try subscribers, but currently have over 100 listeners in your application?
Just a reminder, how hurtful is to teach people 2 very similar ways to do one thing.
Google shows that people are confused since 2012, wow!
And this is not related only to big patterns as subscriber or listener. We do such decisions every day - while we code a new feature when we add a new package to composer.json
when we integrate 4th API to verify payments.
Next time you'll be standing before 2 options, remember the least common denominator and make your code more durable in time.
If the readable and clear code is not good enough reason for you and you still think you should stick with listeners at all cost, maybe the following steps will convince you.
Symfony 3.3 introduced PSR-4 Autodiscovery of services. In short, it means we don't have register services manually, if they respect PSR-4 (class name ~= file location):
services:
- App\Controller\CoffeeController: ~
- App\Controller\WifiController: ~
- App\Controller\PlaneController: ~
- # thousands more...
+ App\Controller\:
+ resource: '../src/Controller'
It doesn't apply only to controllers, but to all services, like Event Subscribers:
services:
_defaults:
# this helps load event subscribers to EventDistpatcher
autoconfigure: true
- App\EventSubscriber\CoffeeEventSubscriber: ~
- App\EventSubscriber\WifiEventSubscriber: ~
- App\EventSubscriber\PlaneEventSubscriber: ~
- # thousands more...
+ App\EventSubscriber\:
+ resource: '../src/EventSubscriber'
Next time we create an event subscriber class, we don't have to code in config anymore ✅
We've reduced cognitive load → code is easier to work with → hiring is faster → we can focus on business and feature value.
services:
App\EventListener\WifiEventListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
- { name: kernel.event_listener, event: kernel.view }
Well, what about...
services:
App\EventListener\:
resource: '../src/EventListener'
Hm, how can be the listener called when it has now an information event?
That's one of legacy code smells of tags.
We can't reduce configs. We have to grow about configs together with code till the end of times ❌
If you're paid or motivated by productivity like me and not by produced lines of code or wasted time with no output, you care about this.
It's very nice use case for pattern refactoring, from A - Listener to B - Event Subscriber.
A. Listener
B. Event Subscriber
Symfony\Component\EventDispatcher\EventSubscriberInterface
getSubscribedEvents
inside the class<?php
class SomeListener
{
public function methodToBeCalled()
{
}
}
# in config.yaml
services:
SomeListener:
tags:
- { name: kernel.event_listener, event: 'some_event', method: 'methodToBeCalled' }
↓
<?php
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class SomeEventSubscriber implements EventSubscriberInterface
{
/**
* @return mixed[]
*/
public static function getSubscribedEvents(): array
{
return ['some_event' => 'methodToBeCalled'];
}
public function methodToBeCalled()
{
}
}
Without any config.
The latest Rector v0.5.8 is shipped with rule exactly for this kind of migration.
Just register the rule in your rector.php
config to start migration:
use Rector\SymfonyCodeQuality\Rector\Class_\EventListenerToEventSubscriberRector;
use Rector\Config\RectorConfig;
return function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(EventListenerToEventSubscriberRector::class);
// optional, when something fails
$parameters = $containerConfigurator->parameters();
// use explicit Kernel, if not discovered by Rector
$parameters->set('kernel_class', 'App\Kernel');
// use explicit environment, if not found by Rector
$parameters->set('kernel_environment', 'test');
};
Run it:
```bash
vendor/bin/rector process app src
And now all the listeners were migrated to event subscribers ✅
In the end, we have to remove all listeners + metadata from configs and add single autodiscovery for our EventSubscribers:
services:
- App\EventListener\WifiEventListener:
- tags:
- - { name: kernel.event_listener, event: kernel.exception }
- - { name: kernel.event_listener, event: kernel.view }
+ _defaults:
+ autoconfigure: true
+
+ App\EventSubscriber\:
+ resource: '../src/EventSubscriber'
That's it!
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!