From 5e68646894dad428753eb6bc19fd763ff4bad83e Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Fri, 17 Feb 2023 10:34:48 +0100 Subject: [PATCH 1/2] Allow to remove lazy listeners --- src/Event/EventDispatcher.php | 5 ++ src/Factory/EventDispatcherFactory.php | 17 ++-- src/Listener/LazyContainerListener.php | 48 +++++++++++ test/Event/EventDispatcherTest.php | 62 +++++++++++++- test/Listener/LazyContainerListenerTest.php | 94 +++++++++++++++++++++ 5 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 src/Listener/LazyContainerListener.php create mode 100644 test/Listener/LazyContainerListenerTest.php diff --git a/src/Event/EventDispatcher.php b/src/Event/EventDispatcher.php index d20353b5..ecc7f2ef 100644 --- a/src/Event/EventDispatcher.php +++ b/src/Event/EventDispatcher.php @@ -4,6 +4,7 @@ namespace PhpSchool\PhpWorkshop\Event; +use PhpSchool\PhpWorkshop\Listener\LazyContainerListener; use PhpSchool\PhpWorkshop\Result\ResultInterface; use PhpSchool\PhpWorkshop\ResultAggregator; @@ -81,6 +82,10 @@ private function attachListener(string $eventName, callable $callback): void public function removeListener(string $eventName, callable $callback): void { foreach ($this->listeners[$eventName] ?? [] as $key => $listener) { + if ($listener instanceof LazyContainerListener) { + $listener = $listener->getWrapped(); + } + if ($listener === $callback) { unset($this->listeners[$eventName][$key]); $this->listeners[$eventName] = array_values($this->listeners[$eventName]); diff --git a/src/Factory/EventDispatcherFactory.php b/src/Factory/EventDispatcherFactory.php index d3bb0a13..a1d3638a 100644 --- a/src/Factory/EventDispatcherFactory.php +++ b/src/Factory/EventDispatcherFactory.php @@ -4,6 +4,7 @@ namespace PhpSchool\PhpWorkshop\Factory; +use PhpSchool\PhpWorkshop\Listener\LazyContainerListener; use Psr\Container\ContainerInterface; use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Event\ContainerListenerHelper; @@ -106,18 +107,10 @@ private function attachListeners( ); } - $dispatcher->listen($eventName, function (...$args) use ($container, $listener) { - /** @var object $service */ - $service = $container->get($listener->getService()); - - if (!method_exists($service, $listener->getMethod())) { - throw new InvalidArgumentException( - sprintf('Method "%s" does not exist on "%s"', $listener->getMethod(), get_class($service)) - ); - } - - $service->{$listener->getMethod()}(...$args); - }); + $dispatcher->listen( + $eventName, + new LazyContainerListener($container, $listener) + ); return; } diff --git a/src/Listener/LazyContainerListener.php b/src/Listener/LazyContainerListener.php new file mode 100644 index 00000000..983b4b19 --- /dev/null +++ b/src/Listener/LazyContainerListener.php @@ -0,0 +1,48 @@ +container = $container; + $this->listener = $listener; + } + + public function __invoke(...$args) + { + /** @var object $service */ + $service = $this->container->get($this->listener->getService()); + + if (!method_exists($service, $this->listener->getMethod())) { + throw new InvalidArgumentException( + sprintf('Method "%s" does not exist on "%s"', $this->listener->getMethod(), get_class($service)) + ); + } + + $service->{$this->listener->getMethod()}(...$args); + } + + public function getWrapped(): callable + { + return [ + $this->container->get($this->listener->getService()), + $this->listener->getMethod() + ]; + } +} diff --git a/test/Event/EventDispatcherTest.php b/test/Event/EventDispatcherTest.php index 804a911e..c517fb6a 100644 --- a/test/Event/EventDispatcherTest.php +++ b/test/Event/EventDispatcherTest.php @@ -2,12 +2,14 @@ namespace PhpSchool\PhpWorkshopTest\Event; +use PhpSchool\PhpWorkshop\Event\ContainerListenerHelper; use PhpSchool\PhpWorkshop\Event\Event; use PhpSchool\PhpWorkshop\Event\EventDispatcher; -use PhpSchool\PhpWorkshop\Event\EventInterface; +use PhpSchool\PhpWorkshop\Listener\LazyContainerListener; use PhpSchool\PhpWorkshop\Result\ResultInterface; use PhpSchool\PhpWorkshop\ResultAggregator; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; class EventDispatcherTest extends TestCase { @@ -161,4 +163,62 @@ public function testRemoveListener(): void $this->assertEquals([], $this->eventDispatcher->getListeners()); } + + public function testRemoveLazyListeners(): void + { + $container = $this->createMock(ContainerInterface::class); + + $myListener = new class { + public function __invoke() + { + } + }; + + $container->expects($this->any()) + ->method('get') + ->with('my-listener') + ->willReturn($myListener); + + $lazy = new LazyContainerListener( + $container, + new ContainerListenerHelper('my-listener') + ); + + $this->eventDispatcher->listen('some-event', $lazy); + + $this->assertEquals(['some-event' => [$lazy]], $this->eventDispatcher->getListeners()); + + $this->eventDispatcher->removeListener('some-event', [$myListener, '__invoke']); + + $this->assertEquals([], $this->eventDispatcher->getListeners()); + } + + public function testRemoveLazyListenersWithAlternateMethod(): void + { + $container = $this->createMock(ContainerInterface::class); + + $myListener = new class { + public function myMethod() + { + } + }; + + $container->expects($this->any()) + ->method('get') + ->with('my-listener') + ->willReturn($myListener); + + $lazy = new LazyContainerListener( + $container, + new ContainerListenerHelper('my-listener', 'myMethod') + ); + + $this->eventDispatcher->listen('some-event', $lazy); + + $this->assertEquals(['some-event' => [$lazy]], $this->eventDispatcher->getListeners()); + + $this->eventDispatcher->removeListener('some-event', [$myListener, 'myMethod']); + + $this->assertEquals([], $this->eventDispatcher->getListeners()); + } } diff --git a/test/Listener/LazyContainerListenerTest.php b/test/Listener/LazyContainerListenerTest.php new file mode 100644 index 00000000..beb9bb75 --- /dev/null +++ b/test/Listener/LazyContainerListenerTest.php @@ -0,0 +1,94 @@ +createMock(ContainerInterface::class); + + $container->expects($this->any()) + ->method('get') + ->with('my-listener') + ->willReturn($myListener); + + $lazy = new LazyContainerListener( + $container, + new ContainerListenerHelper('my-listener', 'myMethod') + ); + + $lazy->__invoke(new Event('some-event')); + } + + public function testThatUnderlyingListenerIsCalled(): void + { + $myListener = new class { + public $called = false; + public function __invoke() + { + $this->called = true; + } + }; + + $container = $this->createMock(ContainerInterface::class); + + $container->expects($this->any()) + ->method('get') + ->with('my-listener') + ->willReturn($myListener); + + $lazy = new LazyContainerListener( + $container, + new ContainerListenerHelper('my-listener') + ); + + $lazy->__invoke(new Event('some-event')); + + self::assertTrue($myListener->called); + } + + public function testWrappedReturnsUnderlyingListener(): void + { + $myListener = new class { + public function __invoke() + { + } + }; + + $container = $this->createMock(ContainerInterface::class); + + $container->expects($this->any()) + ->method('get') + ->with('my-listener') + ->willReturn($myListener); + + $lazy = new LazyContainerListener( + $container, + new ContainerListenerHelper('my-listener', '__invoke') + ); + + $wrapped = $lazy->getWrapped(); + self::assertIsArray($wrapped); + self::assertEquals($myListener, $wrapped[0]); + self::assertEquals('__invoke', $wrapped[1]); + } +} From b0d281be2f8e975bd97f55afaf22858cb19b56f5 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Fri, 17 Feb 2023 10:40:00 +0100 Subject: [PATCH 2/2] phpstan --- src/Listener/LazyContainerListener.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Listener/LazyContainerListener.php b/src/Listener/LazyContainerListener.php index 983b4b19..5a06207f 100644 --- a/src/Listener/LazyContainerListener.php +++ b/src/Listener/LazyContainerListener.php @@ -24,7 +24,10 @@ public function __construct(ContainerInterface $container, ContainerListenerHelp $this->listener = $listener; } - public function __invoke(...$args) + /** + * @param mixed ...$args + */ + public function __invoke(...$args): void { /** @var object $service */ $service = $this->container->get($this->listener->getService()); @@ -38,11 +41,17 @@ public function __invoke(...$args) $service->{$this->listener->getMethod()}(...$args); } + /** + * @return callable + */ public function getWrapped(): callable { - return [ + /** @var callable $listener */ + $listener = [ $this->container->get($this->listener->getService()), $this->listener->getMethod() ]; + + return $listener; } }