diff --git a/CHANGELOG.md b/CHANGELOG.md index a1f72379..2954b213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this #### Changed * [#450](https://github.com/shlinkio/shlink/issues/450) Added PHP 7.4 to the build matrix, as an allowed-to-fail env. -* [#443](https://github.com/shlinkio/shlink/issues/443) Split some logic into independent modules. +* [#441](https://github.com/shlinkio/shlink/issues/441) and [#443](https://github.com/shlinkio/shlink/issues/443) Split some logic into independent modules. * [#451](https://github.com/shlinkio/shlink/issues/451) Updated to infection 0.13. #### Deprecated diff --git a/composer.json b/composer.json index c1ed913e..994febcc 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,7 @@ "phly/phly-event-dispatcher": "^1.0", "predis/predis": "^1.1", "shlinkio/shlink-common": "^1.0", + "shlinkio/shlink-event-dispatcher": "^1.0", "shlinkio/shlink-installer": "^1.2.1", "shlinkio/shlink-ip-geolocation": "^1.0", "symfony/console": "^4.3", @@ -76,12 +77,8 @@ "Shlinkio\\Shlink\\CLI\\": "module/CLI/src", "Shlinkio\\Shlink\\Rest\\": "module/Rest/src", "Shlinkio\\Shlink\\Core\\": "module/Core/src", - "Shlinkio\\Shlink\\EventDispatcher\\": "module/EventDispatcher/src", "Shlinkio\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/src/" - }, - "files": [ - "module/EventDispatcher/functions/functions.php" - ] + } }, "autoload-dev": { "psr-4": { @@ -92,7 +89,6 @@ "module/Core/test", "module/Core/test-db" ], - "ShlinkioTest\\Shlink\\EventDispatcher\\": "module/EventDispatcher/test", "ShlinkioTest\\Shlink\\PreviewGenerator\\": "module/PreviewGenerator/test" } }, diff --git a/module/EventDispatcher/LICENSE b/module/EventDispatcher/LICENSE deleted file mode 100644 index 31778387..00000000 --- a/module/EventDispatcher/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2019 Alejandro Celaya - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/module/EventDispatcher/README.md b/module/EventDispatcher/README.md deleted file mode 100644 index 47938c12..00000000 --- a/module/EventDispatcher/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Shlink Event Dispatcher - -This library provides a PSR-14 EventDispatcher which is capable of dispatching both regular listeners and async listeners which are run using [swoole]'s task system. - -Most of the elements it provides require a [PSR-11] container, and it's easy to integrate on [expressive] applications thanks to the `ConfigProvider` it includes. - -## Install - -Install this library using composer: - - composer require shlinkio/shlink-event-dispatcher - -> This library is also an expressive module which provides its own `ConfigProvider`. Add it to your configuration to get everything automatically set up. diff --git a/module/EventDispatcher/config/event_dispatcher.config.php b/module/EventDispatcher/config/event_dispatcher.config.php deleted file mode 100644 index 8941443f..00000000 --- a/module/EventDispatcher/config/event_dispatcher.config.php +++ /dev/null @@ -1,39 +0,0 @@ - [ - 'regular' => [], - 'async' => [], - ], - - 'dependencies' => [ - 'factories' => [ - Phly\EventDispatcher::class => Phly\EventDispatcherFactory::class, - Psr\ListenerProviderInterface::class => Listener\ListenerProviderFactory::class, - ], - 'aliases' => [ - Psr\EventDispatcherInterface::class => Phly\EventDispatcher::class, - ], - 'delegators' => [ - // The listener provider has to be lazy, because it uses the Swoole server to generate AsyncEventListeners - // Without making this lazy, CLI commands which depend on the EventDispatcher fail - Psr\ListenerProviderInterface::class => [ - LazyServiceFactory::class, - ], - ], - 'lazy_services' => [ - 'class_map' => [ - Psr\ListenerProviderInterface::class => Psr\ListenerProviderInterface::class, - ], - ], - ], - -]; diff --git a/module/EventDispatcher/config/task_runner.config.php b/module/EventDispatcher/config/task_runner.config.php deleted file mode 100644 index a0a23db5..00000000 --- a/module/EventDispatcher/config/task_runner.config.php +++ /dev/null @@ -1,21 +0,0 @@ - [ - 'factories' => [ - Async\TaskRunner::class => Async\TaskRunnerFactory::class, - ], - 'delegators' => [ - HttpServer::class => [ - Async\TaskRunnerDelegator::class, - ], - ], - ], - -]; diff --git a/module/EventDispatcher/functions/functions.php b/module/EventDispatcher/functions/functions.php deleted file mode 100644 index a1c93231..00000000 --- a/module/EventDispatcher/functions/functions.php +++ /dev/null @@ -1,11 +0,0 @@ -logger = $logger; - $this->container = $container; - } - - public function __invoke(HttpServer $server, int $taskId, int $fromId, $task): void - { - if (! $task instanceof TaskInterface) { - $this->logger->warning('Invalid task provided to task worker: {type}. Task ignored', [ - 'type' => is_object($task) ? get_class($task) : gettype($task), - ]); - $server->finish(''); - return; - } - - $this->logger->notice('Starting work on task {taskId}: {task}', [ - 'taskId' => $taskId, - 'task' => $task->toString(), - ]); - - try { - $task->run($this->container); - } catch (Throwable $e) { - $this->logger->error('Error processing task {taskId}: {e}', [ - 'taskId' => $taskId, - 'e' => $e, - ]); - } finally { - $server->finish(''); - } - } -} diff --git a/module/EventDispatcher/src/Async/TaskRunnerDelegator.php b/module/EventDispatcher/src/Async/TaskRunnerDelegator.php deleted file mode 100644 index 7c87826b..00000000 --- a/module/EventDispatcher/src/Async/TaskRunnerDelegator.php +++ /dev/null @@ -1,25 +0,0 @@ -get(LoggerInterface::class); - - $server->on('task', $container->get(TaskRunner::class)); - $server->on('finish', function (HttpServer $server, int $taskId) use ($logger) { - $logger->notice('Task #{taskId} has finished processing', ['taskId' => $taskId]); - }); - - return $server; - } -} diff --git a/module/EventDispatcher/src/Async/TaskRunnerFactory.php b/module/EventDispatcher/src/Async/TaskRunnerFactory.php deleted file mode 100644 index cacfefc5..00000000 --- a/module/EventDispatcher/src/Async/TaskRunnerFactory.php +++ /dev/null @@ -1,16 +0,0 @@ -get(LoggerInterface::class); - return new TaskRunner($logger, $container); - } -} diff --git a/module/EventDispatcher/src/ConfigProvider.php b/module/EventDispatcher/src/ConfigProvider.php deleted file mode 100644 index 88f2045e..00000000 --- a/module/EventDispatcher/src/ConfigProvider.php +++ /dev/null @@ -1,14 +0,0 @@ -regularListenerName = $regularListenerName; - $this->server = $server; - } - - public function __invoke(object $event): void - { - $this->server->task(new EventListenerTask($this->regularListenerName, $event)); - } -} diff --git a/module/EventDispatcher/src/Listener/EventListenerTask.php b/module/EventDispatcher/src/Listener/EventListenerTask.php deleted file mode 100644 index bc1e3689..00000000 --- a/module/EventDispatcher/src/Listener/EventListenerTask.php +++ /dev/null @@ -1,34 +0,0 @@ -listenerName = $listenerName; - $this->event = $event; - } - - public function run(ContainerInterface $container): void - { - ($container->get($this->listenerName))($this->event); - } - - public function toString(): string - { - return sprintf('Listener -> "%s", Event -> "%s"', $this->listenerName, get_class($this->event)); - } -} diff --git a/module/EventDispatcher/src/Listener/ListenerProviderFactory.php b/module/EventDispatcher/src/Listener/ListenerProviderFactory.php deleted file mode 100644 index 7e9198a8..00000000 --- a/module/EventDispatcher/src/Listener/ListenerProviderFactory.php +++ /dev/null @@ -1,52 +0,0 @@ -has('config') ? $container->get('config') : []; - $events = $config['events'] ?? []; - $provider = new AttachableListenerProvider(); - - $this->registerListeners($events['regular'] ?? [], $container, $provider); - $this->registerListeners($events['async'] ?? [], $container, $provider, true); - - return $provider; - } - - private function registerListeners( - array $events, - ContainerInterface $container, - AttachableListenerProvider $provider, - bool $isAsync = false - ): void { - if (empty($events)) { - return; - } - - // Avoid registering async event listeners when the swoole server is not registered - if ($isAsync && ! $container->has(HttpServer::class)) { - return; - } - - foreach ($events as $eventName => $listeners) { - foreach ($listeners as $listenerName) { - $eventListener = $isAsync - ? asyncListener($container->get(HttpServer::class), $listenerName) - : lazyListener($container, $listenerName); - - $provider->listen($eventName, $eventListener); - } - } - } -} diff --git a/module/EventDispatcher/test/Async/TaskRunnerDelegatorTest.php b/module/EventDispatcher/test/Async/TaskRunnerDelegatorTest.php deleted file mode 100644 index ad0978e7..00000000 --- a/module/EventDispatcher/test/Async/TaskRunnerDelegatorTest.php +++ /dev/null @@ -1,46 +0,0 @@ -delegator = new TaskRunnerDelegator(); - } - - /** @test */ - public function serverIsFetchedFromCallbackAndDecorated(): void - { - $server = $this->createMock(HttpServer::class); - $server - ->expects($this->exactly(2)) - ->method('on'); - $callback = function () use ($server) { - return $server; - }; - - $container = $this->prophesize(ContainerInterface::class); - $getTaskRunner = $container->get(TaskRunner::class)->willReturn($this->prophesize(TaskRunner::class)->reveal()); - $getLogger = $container->get(LoggerInterface::class)->willReturn( - $this->prophesize(LoggerInterface::class)->reveal() - ); - - $result = ($this->delegator)($container->reveal(), '', $callback); - - $this->assertSame($server, $result); - $getTaskRunner->shouldHaveBeenCalledOnce(); - $getLogger->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/EventDispatcher/test/Async/TaskRunnerFactoryTest.php b/module/EventDispatcher/test/Async/TaskRunnerFactoryTest.php deleted file mode 100644 index cf139b62..00000000 --- a/module/EventDispatcher/test/Async/TaskRunnerFactoryTest.php +++ /dev/null @@ -1,48 +0,0 @@ -factory = new TaskRunnerFactory(); - } - - /** @test */ - public function properlyCreatesService(): void - { - $loggerMock = $this->prophesize(LoggerInterface::class); - $logger = $loggerMock->reveal(); - $containerMock = $this->prophesize(ContainerInterface::class); - $getLogger = $containerMock->get(LoggerInterface::class)->willReturn($logger); - $container = $containerMock->reveal(); - - $taskRunner = ($this->factory)($container, ''); - $loggerProp = $this->getPropertyFromTaskRunner($taskRunner, 'logger'); - $containerProp = $this->getPropertyFromTaskRunner($taskRunner, 'container'); - - $this->assertSame($container, $containerProp); - $this->assertSame($logger, $loggerProp); - $getLogger->shouldHaveBeenCalledOnce(); - } - - private function getPropertyFromTaskRunner(TaskRunner $taskRunner, string $propertyName) - { - $ref = new ReflectionObject($taskRunner); - $prop = $ref->getProperty($propertyName); - $prop->setAccessible(true); - return $prop->getValue($taskRunner); - } -} diff --git a/module/EventDispatcher/test/Async/TaskRunnerTest.php b/module/EventDispatcher/test/Async/TaskRunnerTest.php deleted file mode 100644 index 0bf56d7c..00000000 --- a/module/EventDispatcher/test/Async/TaskRunnerTest.php +++ /dev/null @@ -1,107 +0,0 @@ -logger = $this->prophesize(LoggerInterface::class); - $this->container = $this->prophesize(ContainerInterface::class); - $this->task = $this->prophesize(TaskInterface::class); - - $this->server = $this->createMock(HttpServer::class); - $this->server - ->expects($this->once()) - ->method('finish') - ->with(''); - - $this->taskRunner = new TaskRunner($this->logger->reveal(), $this->container->reveal()); - } - - /** @test */ - public function warningIsLoggedWhenProvidedTaskIsInvalid(): void - { - $logWarning = $this->logger->warning('Invalid task provided to task worker: {type}. Task ignored', [ - 'type' => 'string', - ]); - $logInfo = $this->logger->info(Argument::cetera()); - $logError = $this->logger->error(Argument::cetera()); - - ($this->taskRunner)($this->server, 1, 1, 'invalid_task'); - - $logWarning->shouldHaveBeenCalledOnce(); - $logInfo->shouldNotHaveBeenCalled(); - $logError->shouldNotHaveBeenCalled(); - } - - /** @test */ - public function properTasksAreRun(): void - { - $logWarning = $this->logger->warning(Argument::cetera()); - $logInfo = $this->logger->notice('Starting work on task {taskId}: {task}', [ - 'taskId' => 1, - 'task' => 'The task', - ]); - $logError = $this->logger->error(Argument::cetera()); - $taskToString = $this->task->toString()->willReturn('The task'); - $taskRun = $this->task->run($this->container->reveal())->will(function () { - }); - - ($this->taskRunner)($this->server, 1, 1, $this->task->reveal()); - - $logWarning->shouldNotHaveBeenCalled(); - $logInfo->shouldHaveBeenCalledOnce(); - $logError->shouldNotHaveBeenCalled(); - $taskToString->shouldHaveBeenCalledOnce(); - $taskRun->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function errorIsLoggedWhenTasksFail(): void - { - $e = new Exception('Error'); - - $logWarning = $this->logger->warning(Argument::cetera()); - $logInfo = $this->logger->notice('Starting work on task {taskId}: {task}', [ - 'taskId' => 1, - 'task' => 'The task', - ]); - $logError = $this->logger->error('Error processing task {taskId}: {e}', [ - 'taskId' => 1, - 'e' => $e, - ]); - $taskToString = $this->task->toString()->willReturn('The task'); - $taskRun = $this->task->run($this->container->reveal())->willThrow($e); - - ($this->taskRunner)($this->server, 1, 1, $this->task->reveal()); - - $logWarning->shouldNotHaveBeenCalled(); - $logInfo->shouldHaveBeenCalledOnce(); - $logError->shouldHaveBeenCalledOnce(); - $taskToString->shouldHaveBeenCalledOnce(); - $taskRun->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/EventDispatcher/test/ConfigProviderTest.php b/module/EventDispatcher/test/ConfigProviderTest.php deleted file mode 100644 index 8c344467..00000000 --- a/module/EventDispatcher/test/ConfigProviderTest.php +++ /dev/null @@ -1,27 +0,0 @@ -configProvider = new ConfigProvider(); - } - - /** @test */ - public function configIsReturned(): void - { - $config = $this->configProvider->__invoke(); - - $this->assertArrayHasKey('dependencies', $config); - $this->assertArrayHasKey('events', $config); - } -} diff --git a/module/EventDispatcher/test/Listener/AsyncEventListenerTest.php b/module/EventDispatcher/test/Listener/AsyncEventListenerTest.php deleted file mode 100644 index 554528cd..00000000 --- a/module/EventDispatcher/test/Listener/AsyncEventListenerTest.php +++ /dev/null @@ -1,41 +0,0 @@ -regularListenerName = 'the_regular_listener'; - $this->server = $this->createMock(HttpServer::class); - - $this->eventListener = new AsyncEventListener($this->server, $this->regularListenerName); - } - - /** @test */ - public function enqueuesTaskWhenInvoked(): void - { - $event = new stdClass(); - - $this->server - ->expects($this->once()) - ->method('task') - ->with(new EventListenerTask($this->regularListenerName, $event)); - - ($this->eventListener)($event); - } -} diff --git a/module/EventDispatcher/test/Listener/EventListenerTaskTest.php b/module/EventDispatcher/test/Listener/EventListenerTaskTest.php deleted file mode 100644 index 5cc6d5a9..00000000 --- a/module/EventDispatcher/test/Listener/EventListenerTaskTest.php +++ /dev/null @@ -1,58 +0,0 @@ -event = new stdClass(); - $this->listenerName = 'the_listener'; - - $this->task = new EventListenerTask($this->listenerName, $this->event); - } - - /** @test */ - public function toStringReturnsTheStringRepresentation(): void - { - $this->assertEquals( - sprintf('Listener -> "%s", Event -> "%s"', $this->listenerName, get_class($this->event)), - $this->task->toString() - ); - } - - /** @test */ - public function runInvokesContainerAndListenerWithEvent(): void - { - $invoked = false; - $container = $this->prophesize(ContainerInterface::class); - $listener = function (object $event) use (&$invoked) { - $invoked = true; - Assert::assertSame($event, $this->event); - }; - - $getListener = $container->get($this->listenerName)->willReturn($listener); - - $this->task->run($container->reveal()); - - $this->assertTrue($invoked); - $getListener->shouldHaveBeenCalledOnce(); - } -} diff --git a/module/EventDispatcher/test/Listener/ListenerProviderFactoryTest.php b/module/EventDispatcher/test/Listener/ListenerProviderFactoryTest.php deleted file mode 100644 index 493940a3..00000000 --- a/module/EventDispatcher/test/Listener/ListenerProviderFactoryTest.php +++ /dev/null @@ -1,176 +0,0 @@ -factory = new ListenerProviderFactory(); - } - - /** - * @test - * @dataProvider provideContainersWithoutEvents - */ - public function noListenersAreAttachedWhenNoConfigOrEventsAreRegistered(ContainerInterface $container): void - { - $provider = ($this->factory)($container, ''); - $listeners = $this->getListenersFromProvider($provider); - - $this->assertInstanceOf(AttachableListenerProvider::class, $provider); - $this->assertEmpty($listeners); - } - - public function provideContainersWithoutEvents(): iterable - { - yield 'no config' => [(function () { - $container = $this->prophesize(ContainerInterface::class); - $container->has('config')->willReturn(false); - - return $container->reveal(); - })()]; - yield 'no events' => [(function () { - $container = $this->prophesize(ContainerInterface::class); - $container->has('config')->willReturn(true); - $container->get('config')->willReturn([]); - - return $container->reveal(); - })()]; - } - - /** @test */ - public function configuredRegularEventsAreProperlyAttached(): void - { - $containerMock = $this->prophesize(ContainerInterface::class); - $containerMock->has('config')->willReturn(true); - $containerMock->get('config')->willReturn([ - 'events' => [ - 'regular' => [ - 'foo' => [ - 'bar', - 'baz', - ], - 'something' => [ - 'some_listener', - 'another_listener', - 'foobar', - ], - ], - ], - ]); - $container = $containerMock->reveal(); - - $provider = ($this->factory)($container, ''); - $listeners = $this->getListenersFromProvider($provider); - - $this->assertInstanceOf(AttachableListenerProvider::class, $provider); - $this->assertEquals([ - 'foo' => [ - lazyListener($container, 'bar'), - lazyListener($container, 'baz'), - ], - 'something' => [ - lazyListener($container, 'some_listener'), - lazyListener($container, 'another_listener'), - lazyListener($container, 'foobar'), - ], - ], $listeners); - } - - /** @test */ - public function configuredAsyncEventsAreProperlyAttached(): void - { - $server = $this->createMock(HttpServer::class); // Some weird errors are thrown if prophesize is used - - $containerMock = $this->prophesize(ContainerInterface::class); - $containerMock->has('config')->willReturn(true); - $containerMock->get('config')->willReturn([ - 'events' => [ - 'async' => [ - 'foo' => [ - 'bar', - 'baz', - ], - 'something' => [ - 'some_listener', - 'another_listener', - 'foobar', - ], - ], - ], - ]); - $containerMock->has(HttpServer::class)->willReturn(true); - $containerMock->get(HttpServer::class)->willReturn($server); - $container = $containerMock->reveal(); - - $provider = ($this->factory)($container, ''); - $listeners = $this->getListenersFromProvider($provider); - - $this->assertInstanceOf(AttachableListenerProvider::class, $provider); - $this->assertEquals([ - 'foo' => [ - asyncListener($server, 'bar'), - asyncListener($server, 'baz'), - ], - 'something' => [ - asyncListener($server, 'some_listener'), - asyncListener($server, 'another_listener'), - asyncListener($server, 'foobar'), - ], - ], $listeners); - } - - /** @test */ - public function ignoresAsyncEventsWhenServerIsNotRegistered(): void - { - $containerMock = $this->prophesize(ContainerInterface::class); - $containerMock->has('config')->willReturn(true); - $containerMock->get('config')->willReturn([ - 'events' => [ - 'async' => [ - 'foo' => [ - 'bar', - 'baz', - ], - 'something' => [ - 'some_listener', - 'another_listener', - 'foobar', - ], - ], - ], - ]); - $containerMock->has(HttpServer::class)->willReturn(false); - $container = $containerMock->reveal(); - - $provider = ($this->factory)($container, ''); - $listeners = $this->getListenersFromProvider($provider); - - $this->assertInstanceOf(AttachableListenerProvider::class, $provider); - $this->assertEmpty($listeners); - } - - private function getListenersFromProvider($provider): array - { - $ref = new ReflectionObject($provider); - $prop = $ref->getProperty('listeners'); - $prop->setAccessible(true); - - return $prop->getValue($provider); - } -} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bb78e85b..1ae25124 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -15,9 +15,6 @@ ./module/CLI/test - - ./module/EventDispatcher/test - ./module/PreviewGenerator/test