It's 3 p.m. on a Wednesday, and you’re sipping your third coffee of the day when you notice what seems like a small bug to fix in the code. “This will be fine,” you tell yourself, finishing your coffee with confidence.
Every coder has these glorious moments of chaos and confusion, especially when something small spirals into a time-consuming rabbit hole of debugging despair. For me, there was one such occasion where what started as an "Oh, that’s pretty weird" moment quickly turned into a full-scale exploration of Symfony's inner workings.
In this post, I’ll take you along on my journey of debugging some unexpected behaviour with event listeners and how a slight misconfiguration in the way you define these as services could be the culprit.
There's no one-size-fits-all solution when it comes to debugging (wouldn’t that be nice?), but the lessons and problem-solving approaches shared here might give you some useful tools for your own debugging tool belt.
Let’s go!
🌞 In the beginning...
There was an innocent profile.linked
event.
An event subscriber, SendEmailWhenProfileLinkedEventSubscriber::onProfileLinked
, was behaving a little too enthusiastically, delivering the same email multiple times.
Something was clearly wrong! But what? And why?
It was time to grab my trusty rubber duck (we refer to him as Professor Quacks, and he has a PhDuck. in Debugging 🦆) and get to work.
Like any good detective, we started gathering evidence. The first step? Seeing how Symfony's event dispatcher was managing the listeners.
We ran the following command to list all registered event listeners in the service container:
php bin/console debug:event-dispatcher
Here’s a small snippet from the output (truncated for brevity):
"profile.linked" event
Custom\AuthBundle\Listener\SendEmailWhenProfileLinkedEventSubscriber::onProfileLinked()
Custom\AuthBundle\Listener\SendEmailWhenProfileLinkedEventSubscriber::onProfileLinked()
Hold up - why was the listener registered twice?!
Further investigation revealed this wasn't just limited to the profile.linked
event. Several other listeners were also registered multiple times, pointing to a broader misconfiguration issue within our service setup.
Professor Quacks and I exchanged knowing looks. Aha! That explained the mystery around the duplicate emails. Now, we just needed to figure out what was causing it.
🕵️♀️ Following the Trail
After some more digging, we identified the problem: Symfony's service tagging and autoconfiguration.
Tags in Symfony are an interesting concept. They're essentially metadata that tell Symfony how to handle different services. Think of them as labels that mark services for special treatment. For example, when you tag a service as an event listener, you’re telling Symfony:
"Hey, this service needs to listen for specific events!"
Symfony’s autoconfigure feature provides built-in rules to wire services automatically. If you name a class or use certain interfaces that Symfony expects, it knows how to tag and use these services without extra manual configuration.
In our case, we had enabled autoconfigure()
globally:
$services
->defaults()
->autoconfigure()
->autowire();
At the same time, we were also manually adding a custom tag to the service definition:
$services->set(SendEmailWhenProfileLinkedEventSubscriber::class)
->args([
service('email_verification_manager'),
service('translator'),
])
// Custom tag
->tag('kernel.event_listener', [
'event' => 'profile.linked',
'method' => 'onProfileLinked',
]);
This was causing two separate issues:
- Since we had globally enabled autoconfiguration, Symfony was already tagging our event listeners based on their class names and interfaces.
- We had also manually defined custom tags on these listeners.
This double-tagging meant that Symfony was registering the same listener twice, once from the autoconfiguration and once from our custom tags.
🔥 Bringing Order to the Chaos
We needed to be more intentional about how services were configured to prevent this duplication.
The Fix: Disabling autoconfiguration for duplicated isteners
By disabling autoconfiguration, we ensured Symfony wouldn't automatically tag services that we were already tagging manually.
Before (Problematic Code):
$services
->set('SendEmailWhenProfileLinkedEventSubscriber::class)
->args([
service('email_verification_manager'),
service('translator')
])
->tag('kernel.event_listener', [
'event' => 'profile.linked',
'method' => 'onProfileLinked',
]);
After (Fixed Code):
The configuration above explicitly tags the event listener just once and avoids any accidental re-tagging that autoconfiguration might have introduced by setting autoconfigure
to false
. It also allows for more precise control over which events the listener should be registered for.
Alternatively, we could have removed the custom tag entirely from the service definition (then we wouldn't have needed to set autoconfigure
to false
) and it would have had the same effect, but we opted for explicit configuration. Why? Because when debugging is involved, explicit is often better than implicit.
We also scanned our configuration files to check for any accidental duplicate imports - better safe than sorry!
💡 Moment of Truth
We ran the debug:event-dispatcher
command again and...
🚨 STOP THE BUS! 🚨
It worked!
The listener now appeared only once in the list:
🧑🎓 What Did We Learn?
🎯 Autoconfiguration is powerful but needs care. It can save a lot of time, but you have to be cautious about it. It's important to understand how and when services are being tagged to avoid unintended side effects.
🔍 Symfony has some pretty awesome debug tools! The debug:event-dispatcher
command was a lifesaver here and really helped get some visibility on what was happening under the hood.
📌 Be explicit when necessary. In many cases, being explicit in the service definitions can prevent headaches and while autoconfiguration is convenient, sometimes a little manual configuration goes a long way and is worth the clarity that it brings.
🌈 The Happy Ending
After all the quacks, tweaks and fixes, the event listener finally learned to send emails just once. Our users stopped getting spammed, and all was well in the land of profile.linked
events.
Remember, fellow developers: never underestimate the importance of a good rubber duck! (Professor Quacks made me say that.) 🦆
Member discussion