Skip to content

[DoctrineBridge] Cannot remove Lazy-loaded listeners by object #51057

Closed
@VincentLanglet

Description

@VincentLanglet

Symfony version(s) affected

6.3.x, but maybe lower

Description

Let's say I the following code

foreach ($this->em->getEventManager()->getAllListeners() as $event => $listeners) {
     foreach ($listeners as $listener) {
         $this->em->getEventManager()->removeEventListener($event, $listener);
    }
}

To me this is suppose to remove all the existing event listener.
Seems like with the ContainerAwareEventManager it does nothing.

If I dump the listeners, I get:

[
    'postPersist' => [
        '_service_App\EventListener\MyListener' => the object(App\EventListener\MyListener),
    ],
]

Which means that, when I call removeEventListener($event, $listener), I pass the event and the object but in the remove method, the hash computed is the spl_object_hash and not the one with _service prefix.

public function removeEventListener($events, $listener): void
{
if (!$this->initializedSubscribers) {
$this->initializeSubscribers();
}
$hash = $this->getHash($listener);
foreach ((array) $events as $event) {
// Check if we actually have this listener associated
if (isset($this->listeners[$event][$hash])) {
unset($this->listeners[$event][$hash]);
}
if (isset($this->methods[$event][$hash])) {
unset($this->methods[$event][$hash]);
}
}
}

I would say something is wrong with the initializeListeners method

private function initializeListeners(string $eventName): void
{
$this->initialized[$eventName] = true;
foreach ($this->listeners[$eventName] as $hash => $listener) {
if (\is_string($listener)) {
$this->listeners[$eventName][$hash] = $listener = $this->container->get($listener);
$this->methods[$eventName][$hash] = $this->getMethod($listener, $eventName);
}
}
}

because it transform the $listener as string to the object instance, but without changing the hash.

the $lister property is modified from

[
    'postPersist' => [
        '_service_App\EventListener\MyListener' => "App\EventListener\MyListener",
    ],
]

to

[
    'postPersist' => [
        '_service_App\EventListener\MyListener' => the object(App\EventListener\MyListener),
    ],
]

when it should be

[
    'postPersist' => [
        'someHash' => the object(App\EventListener\MyListener),
    ],
]

Possible Solution

First I thought of something like
Maybe the method should be

    private function initializeListeners(string $eventName): void
    {
        $this->initialized[$eventName] = true;
        foreach ($this->listeners[$eventName] as $hash => $listener) {
            if (\is_string($listener)) {
                $listener = $this->container->get($listener);
                $newHash = $this->getHash($listener);
                unset($this->listeners[$eventName][$hash]);
                
                $this->listeners[$eventName][$newHash] = $listener;
                $this->methods[$eventName][$newHash] = $this->getMethod($listener, $eventName);
            }
        }
    }

But it require listeners to be initialized then.
Which means that:

$listener = new MyListener();
$this->container->set('lazy', $listener);
$this->evm->addEventListener('foo', 'lazy');
$this->evm->removeEventListener('foo', $listener);

won't do nothing when

$listener = new MyListener();
$this->container->set('lazy', $listener);
$this->evm->addEventListener('foo', 'lazy');
$this->evm->getAllListeners(); // Just to initialized
$this->evm->removeEventListener('foo', $listener);

will remove the lazy (now loaded) listener. Which would be weird.

Reproducer

This is a reproducer in the Symfony Tests:

public function testRemoveAllEventListener()
    {
        $this->container->set('lazy', new MyListener());
        $this->evm->addEventListener('foo', 'lazy');
        $this->evm->addEventListener('foo', new MyListener());

        foreach ($this->evm->getAllListeners() as $event => $listeners) {
            foreach ($listeners as $listener) {
                $this->evm->removeEventListener($event, $listener);
            }
        }

        $this->assertSame([], $this->evm->getListeners('foo'));
    }

Additional context

This seems cause by the PR #31335 and reported here #31335 (comment)
@Koc @nicolas-grekas @dmaicher

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions