Don't Ever use Symfony Listeners

Another anti-pattern that deserves more attention than it has. I often see this in Symfony projects I consult and when I ask the dev why did he or she choose listener over subscriber, they don't really know - "it was in the Symfony documentation, you can read it there".

Not good enough. So why you should never use a listener?

When we look into Symfony EventDispatcher documentation, this is the first YAML code we see:

# config/services.yaml
services:
    App\EventListener\ExceptionListener:
        tags:
            - { name: kernel.event_listener, event: kernel.exception }

If I'd be in the process of learning Symfony, this would be my thoughts:

Btw, there is not even "Subscriber" in the main headline!

That's how you write a manipulative text if you wanted people to never use subscribers :).

What's Wrong With Listeners?

All these problems will shoot you or your colleague in the back in the future. You've just opened doors for 6 more possible bugs and problems to come to your project #carpeyolodiem.

Most of these problems are a result of config programming - that just sucks.

Why You Should Always use Event Subscriber?

Listeners have only one valid use case - it's a 3rd party code located in your /vendor and someone else wants you to use it with event of your choice in config, e.g.:

# config/services.yaml
services:
    Vendor\ThirdPartyProject\Listener\UseMeListener:
        tags:
            - { name: kernel.event_listener, event: kernel.exception }
            - { name: kernel.event_listener, event: kernel.view }

If it would be a subscriber, it would be very similar to this:

<?php

namespace App;

use Vendor\ThirdPartyProject\Listener\UseMeListener;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class YourListener extends UseMeListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::EXCEPTION => ['onKernelException'],
            KernelEvents::VIEW => ['onKernelView'],
        ];
    }
}

What's wrong with this code? First, UseMeListener should be final, so you cannot break SOLID like this.

Let's take part by part.

1. Validated PHP over Config Typos

$myEvents = [
    KernelEvents::EXCEPTION => ['onKernelException'],
    KernelEvents::VIEW => ['onKernelView'],
];

2. Explicit Services instead of Circular-Coupled Subscriber/Listener

If someone has created a listener that you can re-use, it's an anti-pattern already.

People actually do that, the StackOverflow has dozens of questions like "how to call command in a controller" or "how to controller in a command".

3. Don't Rape! Delegate

Command, Controller, EventSubscriber, Listener - they all should be only delegating code to a model layer service. If you need to mutually call or inherit one in another, you're creating a code smell. That's a sign that you should decouple common logic to a service and pass it via constructor to both.

So instead of giving people the option to use your code wrong way, give them a service, they can call in e.g. the EventSubscriber.

4. Let Interface take the Responsibility

EventSubscriber has own interface, that guides you:

<?php

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class MyCustomEventSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
    }
}

There is still a bit of magic... what should be in getSubscribedEvents() method? Honestly, I have no idea. I don't want to remember what's written in code. So I'll use PHPStorm:

What's Better with Event Subscriber?

The trade-off worth the change

Final Unrelated Tip: Constants over Strings

If you use KernelEvents::VIEW constants within PHP code, you make the code also easier to debug.

Where is KernelEvents::VIEW event actually dispatched? Just search KernelEvents::VIEW (or better dispatch(KernelEvents::VIEW)) in /vendor and PHPStorm will show you the exact line. If you'd look for a string, it will lead to a false source of KernelEvents (just a reference list of all Kernel events).

Also, when the event name is changed in a constant to view_event, you don't mind. If you have view in the config, good luck!

This makes using constants so fun. My rule fo a thumb is:

When the same string is used at 2 different classes,
it's worth creating a constant list to make it typo-proof.

E.g. imagine you have code like:

# in class A
$configuration->setOption('resource');

# in class B
$input->getOption('resource');

Now you need to get resource somewhere else. Was is "source", "sources", "resource" or "directory"? You don't care, constant autocomplete in PHPStorm tells you:

# in class A
$configuration->setOption('resource');

# in class B
$input->getOption('resource');

Don't remember what you don't need to.


Happy coding!