[EntityListener] with multiple listeners per class only the first priority is used
Bug Report
| Q | A |
|---|---|
| Version | 2.14.0 |
Summary
Using the AsEntityListener attribute to listen to entity events one can set a priority for each event listener. The documentation suggests that then all listeners for an entity event are sorted by priority and strictly executed in that order.
But when having multiple event listeners in a single class with differing priorities this actually produces unexpected results. Because only the priority of the first defined listener is actually used and also applied to the other listeners.
This is due to EntityListenerPass::process() using PriorityTaggedServiceTrait::findAndSortTaggedServices() which effectively sorts all tagged services by the priority of the first attribute.
Current behavior
The defined entity listeners are not correctly executed in order of their individual priorities.
Expected behavior
Each defined entity listener should be executed according to its specific priority.
How to reproduce
Given these two classes with entity listeners
#[AsEntityListener(entity: User::class, event: Events::prePersist, priority: 0)]
#[AsEntityListener(entity: User::class, event: Events::postPersist, priority: 0)]
final class FirstListener {
public function prePersist(User $user, PrePersistEventArgs $event) {
dump('this is ' . __METHOD__);
}
public function postPersist(User $user, PostPersistEventArgs $event) {
dump('this is ' . __METHOD__);
}
}
#[AsEntityListener(entity: User::class, event: Events::prePersist, priority: 1)]
#[AsEntityListener(entity: User::class, event: Events::postPersist, priority: -1)]
final class SecondListener {
public function prePersist(User $user, PrePersistEventArgs $event) {
dump('this is ' . __METHOD__);
}
public function postPersist(User $user, PostPersistEventArgs $event) {
dump('this is ' . __METHOD__);
}
}
The expected output order of the dump() statements would be
"this is App\EventListener\SecondListener::prePersist" "this is App\EventListener\FirstListener::prePersist" "this is App\EventListener\FirstListener::postPersist" "this is App\EventListener\SecondListener::postPersist"
but actually is
"this is App\EventListener\SecondListener::prePersist" "this is App\EventListener\FirstListener::prePersist" "this is App\EventListener\SecondListener::postPersist" "this is App\EventListener\FirstListener::postPersist"
Switching just the order of the attributes on the second class
#[AsEntityListener(entity: User::class, event: Events::postPersist, priority: -1)]
#[AsEntityListener(entity: User::class, event: Events::prePersist, priority: 1)]
final class SecondListener {
public function prePersist(User $user, PrePersistEventArgs $event) {
dump('this is ' . __METHOD__);
}
public function postPersist(User $user, PostPersistEventArgs $event) {
dump('this is ' . __METHOD__);
}
}
the output becomes
"this is App\EventListener\FirstListener::prePersist" "this is App\EventListener\SecondListener::prePersist" "this is App\EventListener\FirstListener::postPersist" "this is App\EventListener\SecondListener::postPersist"
Fix at https://github.com/doctrine/DoctrineBundle/pull/1885, can you check if fix solves your problem?
Wow, that's a fast reaction, thank you!
can you check if fix solves your problem?
Not really unfortunately. Though it now correctly sorts the listeners within a single service, it does now not sort the service themselves at all. So the output of above example now becomes
"this is App\EventListener\FirstListener::prePersist" "this is App\EventListener\SecondListener::prePersist" "this is App\EventListener\FirstListener::postPersist" "this is App\EventListener\SecondListener::postPersist"
regardless of the used priorities.
Wow, that's a fast reaction, thank you!
can you check if fix solves your problem?
Not really unfortunately. Though it now correctly sorts the listeners within a single service, it does now not sort the service themselves at all. So the output of above example now becomes
"this is App\EventListener\FirstListener::prePersist" "this is App\EventListener\SecondListener::prePersist" "this is App\EventListener\FirstListener::postPersist" "this is App\EventListener\SecondListener::postPersist"
regardless of the used priorities.
Hello, I can confirm this behaviour as well too, it is not completely fixed yet (but thanks for the work done)