Skip to content

Conversation

@fre5h
Copy link
Contributor

@fre5h fre5h commented Jan 22, 2020

Q A
Branch? master
Bug fix? no
New feature? yes
Deprecations? no
Tickets no
License MIT
Doc PR Will add if pull request be approved

Thanks to the autowiring, we can easily add new services without a need to register them in configs.
We can create an EventListener class that has a method __invoke that receives the event of some class and thanks to the autowiring we don't need to register this listener as service in configs.

services:
    _defaults:
        autowire: true

App\EventListener\:
    resource: '../../src/EventListener/'
    tags:
        - 'kernel.event_listener'
namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\RequestEvent;

final class FooListener
{
    public function __invoke(RequestEvent $event): void
    {
    }
}

By default all listeners have priority 0. But when we need to set a custom priority, we have to add a config for listener and set the priority there. (If listener implements EventSubscriberInterface then it is possible to set priority in getSubscribedEvents method. But I talk about listeners as closures)

services:
    _defaults:
        autowire: true

App\EventListener\:
    resource: '../../src/EventListener/'
    tags:
        - 'kernel.event_listener'

App\EventListener\FooListener:
    class: App\EventListener\FooListener
    tags:
        - { name: kernel.event_listener, priority: 100 }

If you have a lot of listeners with custom priority you have to write configs for all of them. So you cannot use easily autowiring in this case.

I propose to add a DefaultPriorityInterface with a static method getDefaultPriority and set the default priority inside the class, so you don't have to put priority into config files. And your config files will be smaller and cleaner.

My request doesn't break BC, because you can still add priority as config parameter for listener.

So as a result config file with autowiring:

services:
    _defaults:
        autowire: true

App\EventListener\:
    resource: '../../src/EventListener/'
    tags:
        - 'kernel.event_listener'

Listener with a custom priority:

namespace App\EventListener;

use Symfony\Component\EventDispatcher\DefaultPriorityInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;

final class FooListener implements DefaultPriorityInterface
{
    public function __invoke(RequestEvent $event): void
    {
    }

    public static function getDefaultPriority(): int
    {
        return 100;
    }
}

In the case when listener implements DefaultPriorityInterface and has priority argument in config, the priority from config will be used. In this case it is 99

services:
    _defaults:
        autowire: true

App\EventListener\:
    resource: '../../src/EventListener/'
    tags:
        - 'kernel.event_listener'

App\EventListener\FooListener:
    class: App\EventListener\FooListener
    tags:
        - { name: kernel.event_listener, priority: 99 }
namespace App\EventListener;

use Symfony\Component\EventDispatcher\DefaultPriorityInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;

final class FooListener implements DefaultPriorityInterface
{
    public function __invoke(RequestEvent $event): void
    {
    }

    public static function getDefaultPriority(): int
    {
        return 100;
    }
}

…ority for autowired listener, without a need to register it as a service.
@fre5h fre5h changed the title [EventDispatcher] Add DefaultPriorityInterface to allow setting priority for autowired listener, without a need to register it as a service. [EventDispatcher] Add DefaultPriorityInterface to allow setting priority for autowired listener, without a need to register it as a service in configs.. Jan 22, 2020
@fre5h fre5h changed the title [EventDispatcher] Add DefaultPriorityInterface to allow setting priority for autowired listener, without a need to register it as a service in configs.. [EventDispatcher] Add DefaultPriorityInterface to allow setting priority for autowired listener, without a need to register it as a service in configs. Jan 22, 2020
@lyrixx
Copy link
Member

lyrixx commented Jan 22, 2020

Hello'

Thanks for this proposal. But I fail to see why we should support another way to register a listener with priority where everything is already supporter with subscribers

@fre5h
Copy link
Contributor Author

fre5h commented Jan 23, 2020

You mean, that if listener implemented with __invoke method and needs custom priority, it should be rewritten as subscriber? You are right, it is the way. But then there is no benefit from autowiring for listeners with __invoke method and custom priority.

In my opinion EventSubscriberInterface is useful, when you want to group listeners for the family of events in one class and there are not so many dependencies.

On my project I was faced with a situation when each listener for the family of events (lets say events related to order entity) has its own unique dependencies and priority. Grouping it all together in subsriber will broke the single responsibility principle, because a lot of different business logic and dependencies in one class. I decided to divide code into small independent listeners with a new cool feature of __invoke method and event as a class name. I set up the autowiring config and all new listeners where registered automatically.

services:
    _defaults:
        autowire: true

App\EventListener\:
    resource: '../../src/EventListener/'
    tags:
        - 'kernel.event_listener'

And everything was great until I needed to set custom priority. Right now I have configs for all listeners with custom priority in my services.yaml and the autowiring feature for this case became useless :( And I tried to find a way how to improve this behaviour.

I agree, that adding the DefaultPriorityInterface will increase efforts for supporting EventDispatcher component in future. But it is only a proposition and my view how to solve this problem, that I showed for community to be discussed.

@fre5h fre5h requested a review from nicolas-grekas January 23, 2020 08:04
@fre5h
Copy link
Contributor Author

fre5h commented Jan 23, 2020

@ro0NL Could you please take a look at this. Because you implemented the support for __invoke in #25275 Maube you can agree or disagree 😉

@ro0NL
Copy link
Contributor

ro0NL commented Jan 23, 2020

IMHO listeners are pure (domain) callables. Adding DefaultPriorityInterface adds coupling to the framework. For these cases (framework listeners) using EventSubscriberInterface is fine, and more easy: https://www.tomasvotruba.com/blog/2019/07/22/how-to-convert-listeners-to-subscribers-and-reduce-your-configs/

At this point, for listeners, i think

App\EventListener\:
    resource: '../../src/EventListener/'
    tags: [kernel.event_listener]

is the final form, which cannot be further simplified.

@fre5h
Copy link
Contributor Author

fre5h commented Jan 23, 2020

@ro0NL Thank you for your answer 👍

@lyrixx
Copy link
Member

lyrixx commented Jan 23, 2020

And if you use autowiring and autoconfiguration you can have one small subscriber per file that respect SRP and also support for priority

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Jan 23, 2020

I agree with @lyrixx, EventSubscriberInterface is enough, even for simple listeners.

@nicolas-grekas nicolas-grekas added this to the next milestone Jan 23, 2020
@fre5h
Copy link
Contributor Author

fre5h commented Jan 23, 2020

OK, then I close it

@fre5h fre5h closed this Jan 23, 2020
@fre5h fre5h deleted the event-dispatcher-default-priority branch January 24, 2020 13:43
@nicolas-grekas nicolas-grekas modified the milestones: next, 5.1 May 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants