Skip to content

[EventDispatcher] Fix removing listeners when using first-class callable syntax #46262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function removeListener($eventName, $listener)
{
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
$listener = $wrappedListener;
unset($this->wrappedListeners[$eventName][$index]);
break;
Expand Down Expand Up @@ -110,8 +110,8 @@ public function getListenerPriority($eventName, $listener)
// we might have wrapped listeners for the event (if called while dispatching)
// in that case get the priority by wrapper
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
foreach ($this->wrappedListeners[$eventName] as $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Component/EventDispatcher/EventDispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public function getListenerPriority($eventName, $listener)
$v[0] = $v[0]();
$v[1] = $v[1] ?? '__invoke';
}
if ($v === $listener) {
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
return $priority;
}
}
Expand Down Expand Up @@ -178,7 +178,7 @@ public function removeListener($eventName, $listener)
$v[0] = $v[0]();
$v[1] = $v[1] ?? '__invoke';
}
if ($v === $listener) {
if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,35 @@ public function testMutatingWhilePropagationIsStopped()
$this->assertTrue($testLoaded);
}

/**
* @requires PHP 8.1
*/
public function testNamedClosures()
{
$listener = new TestEventListener();

$callback1 = \Closure::fromCallable($listener);
$callback2 = \Closure::fromCallable($listener);
$callback3 = \Closure::fromCallable(new TestEventListener());

$this->assertNotSame($callback1, $callback2);
$this->assertNotSame($callback1, $callback3);
$this->assertNotSame($callback2, $callback3);
$this->assertTrue($callback1 == $callback2);
$this->assertFalse($callback1 == $callback3);

$this->dispatcher->addListener('foo', $callback1, 3);
$this->dispatcher->addListener('foo', $callback2, 2);
$this->dispatcher->addListener('foo', $callback3, 1);

$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', $callback1));
$this->assertSame(3, $this->dispatcher->getListenerPriority('foo', $callback2));

$this->dispatcher->removeListener('foo', $callback1);

$this->assertSame(['foo' => [$callback3]], $this->dispatcher->getListeners());
}

/**
* @group legacy
* @expectedDeprecation Calling the "Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,30 @@ public function testWithPreviousNotStartedSession()
$this->assertSame($usageIndex, $session->getUsageIndex());
}

public function testOnKernelResponseRemoveListener()
{
$tokenStorage = new TokenStorage();
$tokenStorage->setToken(new UsernamePasswordToken('test1', 'pass1', 'phpunit', ['ROLE_USER']));

$request = new Request();
$request->attributes->set('_security_firewall_run', '_security_session');

$session = new Session(new MockArraySessionStorage());
$request->setSession($session);

$dispatcher = new EventDispatcher();
$httpKernel = $this->createMock(HttpKernelInterface::class);

$listener = new ContextListener($tokenStorage, [], 'session', null, $dispatcher, null, \Closure::fromCallable([$tokenStorage, 'getToken']));
$this->assertEmpty($dispatcher->getListeners());

$listener(new RequestEvent($httpKernel, $request, HttpKernelInterface::MASTER_REQUEST));
$this->assertNotEmpty($dispatcher->getListeners());

$listener->onKernelResponse(new ResponseEvent($httpKernel, $request, HttpKernelInterface::MASTER_REQUEST, new Response()));
$this->assertEmpty($dispatcher->getListeners());
}

protected function runSessionOnKernelResponse($newToken, $original = null)
{
$session = new Session(new MockArraySessionStorage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Security\Http\Tests\Firewall;

use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
Expand Down Expand Up @@ -170,6 +171,18 @@ public function testLogoutException()
$this->assertEquals(403, $event->getThrowable()->getStatusCode());
}

public function testUnregister()
{
$listener = $this->createExceptionListener();
$dispatcher = new EventDispatcher();

$listener->register($dispatcher);
$this->assertNotEmpty($dispatcher->getListeners());

$listener->unregister($dispatcher);
$this->assertEmpty($dispatcher->getListeners());
}

public function getAccessDeniedExceptionProvider()
{
return [
Expand Down