From b18f25f38e9575acd7960ddf607a9890c46e1c13 Mon Sep 17 00:00:00 2001 From: Brent Roose Date: Wed, 6 Nov 2024 09:39:34 +0100 Subject: [PATCH 01/14] add async middleware and repository --- src/Tempest/CommandBus/src/AsyncCommand.php | 10 ++++ .../CommandBus/src/AsyncCommandMiddleware.php | 35 +++++++++++++ .../FileRepository.php | 39 +++++++++++++++ .../MemoryRepository.php | 30 ++++++++++++ .../CommandBus/src/AsyncCommandRepository.php | 15 ++++++ .../src/AsyncCommandRepositoryInitializer.php | 17 +++++++ .../CommandBus/src/HandleAsyncCommand.php | 45 +++++++++++++++++ .../CommandBus/src/stored-commands/.gitignore | 1 + .../Handlers/MyAsyncCommandHandler.php | 17 +++++++ .../CommandBus/AsyncCommandTest.php | 49 +++++++++++++++++++ .../CommandBus/Fixtures/MyAsyncCommand.php | 13 +++++ 11 files changed, 271 insertions(+) create mode 100644 src/Tempest/CommandBus/src/AsyncCommand.php create mode 100644 src/Tempest/CommandBus/src/AsyncCommandMiddleware.php create mode 100644 src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php create mode 100644 src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php create mode 100644 src/Tempest/CommandBus/src/AsyncCommandRepository.php create mode 100644 src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php create mode 100644 src/Tempest/CommandBus/src/HandleAsyncCommand.php create mode 100644 src/Tempest/CommandBus/src/stored-commands/.gitignore create mode 100644 tests/Fixtures/Handlers/MyAsyncCommandHandler.php create mode 100644 tests/Integration/CommandBus/AsyncCommandTest.php create mode 100644 tests/Integration/CommandBus/Fixtures/MyAsyncCommand.php diff --git a/src/Tempest/CommandBus/src/AsyncCommand.php b/src/Tempest/CommandBus/src/AsyncCommand.php new file mode 100644 index 000000000..01d75eca3 --- /dev/null +++ b/src/Tempest/CommandBus/src/AsyncCommand.php @@ -0,0 +1,10 @@ +commandBusConfig->addMiddleware(self::class); + } + + public function __invoke(object $command, CommandBusMiddlewareCallable $next): void + { + $reflector = new ClassReflector($command); + + if ($reflector->hasAttribute(AsyncCommand::class)) + { + $this->repository->store(Uuid::uuid7()->toString(), $command); + return; + } + + $next($command); + } +} \ No newline at end of file diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php new file mode 100644 index 000000000..ecb67d316 --- /dev/null +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php @@ -0,0 +1,39 @@ +map(fn (string $path) => pathinfo($path, PATHINFO_FILENAME)) + ->toArray(); + } +} \ No newline at end of file diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php new file mode 100644 index 000000000..32f87ad04 --- /dev/null +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php @@ -0,0 +1,30 @@ +commands[$uuid] = $command; + } + + public function find(string $uuid): object + { + return $this->commands[$uuid]; + } + + public function remove(string $uuid): void + { + unset($this->commands[$uuid]); + } + + public function all(): array + { + return array_keys($this->commands); + } +} \ No newline at end of file diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepository.php new file mode 100644 index 000000000..43ca7df5f --- /dev/null +++ b/src/Tempest/CommandBus/src/AsyncCommandRepository.php @@ -0,0 +1,15 @@ +repository->find($uuid); + + $commandHandler = $this->commandBusConfig->handlers[$command::class] ?? null; + + if (! $commandHandler) { + $commandClass = $command::class; + + $this->error("No handler found for command {$commandClass}"); + + return; + } + + $commandHandler->handler->invokeArgs( + $this->container->get($commandHandler->handler->getDeclaringClass()->getName()), + [$command], + ); + + $this->repository->remove($uuid); + + $this->success('Done'); + } +} \ No newline at end of file diff --git a/src/Tempest/CommandBus/src/stored-commands/.gitignore b/src/Tempest/CommandBus/src/stored-commands/.gitignore new file mode 100644 index 000000000..5fb03d009 --- /dev/null +++ b/src/Tempest/CommandBus/src/stored-commands/.gitignore @@ -0,0 +1 @@ +./* \ No newline at end of file diff --git a/tests/Fixtures/Handlers/MyAsyncCommandHandler.php b/tests/Fixtures/Handlers/MyAsyncCommandHandler.php new file mode 100644 index 000000000..7c167f39c --- /dev/null +++ b/tests/Fixtures/Handlers/MyAsyncCommandHandler.php @@ -0,0 +1,17 @@ +repository = new MemoryRepository(); + + $this->container->singleton( + AsyncCommandRepository::class, + fn () => $this->repository + ); + + MyAsyncCommandHandler::$isHandled = false; + } + + public function test_async_commands_are_stored_and_handled_afterwards(): void + { + command(new MyAsyncCommand('Brent')); + + $uuids = $this->repository->all(); + + $this->assertCount(1, $uuids); + $uuid = $uuids[0]; + $command = $this->repository->find($uuid); + $this->assertSame('Brent', $command->name); + $this->assertFalse(MyAsyncCommandHandler::$isHandled); + + $this->console + ->call("command:handle {$uuid}") + ->assertSee('Done'); + + $this->assertEmpty($this->repository->all()); + $this->assertTrue(MyAsyncCommandHandler::$isHandled); + } +} \ No newline at end of file diff --git a/tests/Integration/CommandBus/Fixtures/MyAsyncCommand.php b/tests/Integration/CommandBus/Fixtures/MyAsyncCommand.php new file mode 100644 index 000000000..70873cd92 --- /dev/null +++ b/tests/Integration/CommandBus/Fixtures/MyAsyncCommand.php @@ -0,0 +1,13 @@ + Date: Wed, 6 Nov 2024 10:25:45 +0100 Subject: [PATCH 02/14] wip --- composer.json | 3 +- .../CommandBus/src/MonitorAsyncCommands.php | 65 +++++++++++++++++++ .../Fixtures/Console/DispatchAsyncCommand.php | 23 +++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/Tempest/CommandBus/src/MonitorAsyncCommands.php create mode 100644 tests/Fixtures/Console/DispatchAsyncCommand.php diff --git a/composer.json b/composer.json index 131e3cfde..09f5108bc 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,8 @@ "symfony/var-dumper": "^7.1", "symfony/var-exporter": "^7.1", "tempest/highlight": "^2.0", - "vlucas/phpdotenv": "^5.6" + "vlucas/phpdotenv": "^5.6", + "symfony/process": "^7.1" }, "require-dev": { "aidan-casey/mock-client": "dev-master", diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php new file mode 100644 index 000000000..7d971d69e --- /dev/null +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -0,0 +1,65 @@ +success("Monitoring for new commands. Press ctrl+c to stop."); + + /** @var \Symfony\Component\Process\Process[] $processes */ + $processes = []; + + while (true) { + foreach ($processes as $key => $process) { + $errorOutput = trim($process->getErrorOutput()); + + if ($errorOutput) { + $this->error($errorOutput); + } + + if ($process->isTerminated()) { + $this->success("{$key} finished, {$process->getExitCode()}"); + unset($processes[$key]); + } + } + + $uuids = arr($this->repository->all()) + ->filter(fn (string $uuid) => ! in_array($uuid, array_keys($processes))); + + if (count($processes) === 5) { + sleep(1); + continue; + } + + if ($uuids->isEmpty()) { + sleep(1); + continue; + } + + // Start a task + $uuid = $uuids->first(); + $time = new DateTimeImmutable(); + $this->info("{$uuid} started at {$time->format('Y-m-d H:i:s')}"); + $process = new Process(['php', 'tempest', 'command:handle', $uuid], getcwd()); + $process->start(); + $processes[$uuid] = $process; + } + } +} \ No newline at end of file diff --git a/tests/Fixtures/Console/DispatchAsyncCommand.php b/tests/Fixtures/Console/DispatchAsyncCommand.php new file mode 100644 index 000000000..d796f934e --- /dev/null +++ b/tests/Fixtures/Console/DispatchAsyncCommand.php @@ -0,0 +1,23 @@ +info('Dispatched commands'); + } +} \ No newline at end of file From 8ba561b0abfb3ea3ce9174543723dadcb086cbf9 Mon Sep 17 00:00:00 2001 From: Brent Roose Date: Wed, 6 Nov 2024 10:54:25 +0100 Subject: [PATCH 03/14] wip --- .../FileRepository.php | 2 +- .../MemoryRepository.php | 2 +- .../CommandBus/src/AsyncCommandRepository.php | 2 +- .../CommandBus/src/MonitorAsyncCommands.php | 18 ++++++++++-------- .../CommandBus/AsyncCommandTest.php | 4 ++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php index ecb67d316..cc82e1bd8 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php @@ -30,7 +30,7 @@ public function remove(string $uuid): void unlink($path); } - public function all(): array + public function available(): array { return arr(glob(__DIR__ . "/../stored-commands/*.txt")) ->map(fn (string $path) => pathinfo($path, PATHINFO_FILENAME)) diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php index 32f87ad04..01993cfab 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php @@ -23,7 +23,7 @@ public function remove(string $uuid): void unset($this->commands[$uuid]); } - public function all(): array + public function available(): array { return array_keys($this->commands); } diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepository.php index 43ca7df5f..2357d7725 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepository.php @@ -11,5 +11,5 @@ public function find(string $uuid): object; public function remove(string $uuid): void; /** @return string[] */ - public function all(): array; + public function available(): array; } \ No newline at end of file diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php index 7d971d69e..889fbd100 100644 --- a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -30,17 +30,19 @@ public function __invoke(): void foreach ($processes as $key => $process) { $errorOutput = trim($process->getErrorOutput()); + $time = new DateTimeImmutable(); + if ($errorOutput) { $this->error($errorOutput); - } - - if ($process->isTerminated()) { - $this->success("{$key} finished, {$process->getExitCode()}"); + $this->writeln("{$key} failed at {$time->format('Y-m-d H:i:s')}"); + unset($processes[$key]); + } elseif ($process->isTerminated()) { + $this->writeln("{$key} finished at {$time->format('Y-m-d H:i:s')}"); unset($processes[$key]); } } - $uuids = arr($this->repository->all()) + $availableUuids = arr($this->repository->available()) ->filter(fn (string $uuid) => ! in_array($uuid, array_keys($processes))); if (count($processes) === 5) { @@ -48,15 +50,15 @@ public function __invoke(): void continue; } - if ($uuids->isEmpty()) { + if ($availableUuids->isEmpty()) { sleep(1); continue; } // Start a task - $uuid = $uuids->first(); + $uuid = $availableUuids->first(); $time = new DateTimeImmutable(); - $this->info("{$uuid} started at {$time->format('Y-m-d H:i:s')}"); + $this->writeln("

{$uuid}

started at {$time->format('Y-m-d H:i:s')}"); $process = new Process(['php', 'tempest', 'command:handle', $uuid], getcwd()); $process->start(); $processes[$uuid] = $process; diff --git a/tests/Integration/CommandBus/AsyncCommandTest.php b/tests/Integration/CommandBus/AsyncCommandTest.php index ea90c78b8..9464cc3b1 100644 --- a/tests/Integration/CommandBus/AsyncCommandTest.php +++ b/tests/Integration/CommandBus/AsyncCommandTest.php @@ -31,7 +31,7 @@ public function test_async_commands_are_stored_and_handled_afterwards(): void { command(new MyAsyncCommand('Brent')); - $uuids = $this->repository->all(); + $uuids = $this->repository->available(); $this->assertCount(1, $uuids); $uuid = $uuids[0]; @@ -43,7 +43,7 @@ public function test_async_commands_are_stored_and_handled_afterwards(): void ->call("command:handle {$uuid}") ->assertSee('Done'); - $this->assertEmpty($this->repository->all()); + $this->assertEmpty($this->repository->available()); $this->assertTrue(MyAsyncCommandHandler::$isHandled); } } \ No newline at end of file From bfe223ad83ee0b404b23153d6096209897824085 Mon Sep 17 00:00:00 2001 From: Brent Roose Date: Wed, 6 Nov 2024 11:21:08 +0100 Subject: [PATCH 04/14] wip --- composer.json | 4 ++-- src/Tempest/CommandBus/src/AsyncCommand.php | 4 +++- src/Tempest/CommandBus/src/AsyncCommandMiddleware.php | 11 +++++++---- .../src/AsyncCommandRepositories/FileRepository.php | 4 +++- .../src/AsyncCommandRepositories/MemoryRepository.php | 4 +++- src/Tempest/CommandBus/src/AsyncCommandRepository.php | 4 +++- .../src/AsyncCommandRepositoryInitializer.php | 4 +++- src/Tempest/CommandBus/src/HandleAsyncCommand.php | 7 +++++-- src/Tempest/CommandBus/src/MonitorAsyncCommands.php | 11 ++++++++--- tests/Fixtures/Console/DispatchAsyncCommand.php | 8 +++++--- tests/Fixtures/Handlers/MyAsyncCommandHandler.php | 4 +++- tests/Integration/CommandBus/AsyncCommandTest.php | 9 +++++++-- .../CommandBus/Fixtures/MyAsyncCommand.php | 7 +++++-- 13 files changed, 57 insertions(+), 24 deletions(-) diff --git a/composer.json b/composer.json index 09f5108bc..d973337bd 100644 --- a/composer.json +++ b/composer.json @@ -32,11 +32,11 @@ "psr/log": "^3.0.0", "ramsey/uuid": "^4.7", "symfony/cache": "^7.2", + "symfony/process": "^7.1", "symfony/var-dumper": "^7.1", "symfony/var-exporter": "^7.1", "tempest/highlight": "^2.0", - "vlucas/phpdotenv": "^5.6", - "symfony/process": "^7.1" + "vlucas/phpdotenv": "^5.6" }, "require-dev": { "aidan-casey/mock-client": "dev-master", diff --git a/src/Tempest/CommandBus/src/AsyncCommand.php b/src/Tempest/CommandBus/src/AsyncCommand.php index 01d75eca3..a3af36271 100644 --- a/src/Tempest/CommandBus/src/AsyncCommand.php +++ b/src/Tempest/CommandBus/src/AsyncCommand.php @@ -1,5 +1,7 @@ hasAttribute(AsyncCommand::class)) - { + if ($reflector->hasAttribute(AsyncCommand::class)) { $this->repository->store(Uuid::uuid7()->toString(), $command); + return; } $next($command); } -} \ No newline at end of file +} diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php index cc82e1bd8..e4a5a9fae 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php @@ -1,5 +1,7 @@ map(fn (string $path) => pathinfo($path, PATHINFO_FILENAME)) ->toArray(); } -} \ No newline at end of file +} diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php index 01993cfab..bfe598c3e 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php @@ -1,5 +1,7 @@ commands); } -} \ No newline at end of file +} diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepository.php index 2357d7725..ada1c7a47 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepository.php @@ -1,5 +1,7 @@ success('Done'); } -} \ No newline at end of file +} diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php index 889fbd100..ff2067004 100644 --- a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -1,5 +1,7 @@ $process) { $errorOutput = trim($process->getErrorOutput()); @@ -47,11 +50,13 @@ public function __invoke(): void if (count($processes) === 5) { sleep(1); + continue; } if ($availableUuids->isEmpty()) { sleep(1); + continue; } @@ -64,4 +69,4 @@ public function __invoke(): void $processes[$uuid] = $process; } } -} \ No newline at end of file +} diff --git a/tests/Fixtures/Console/DispatchAsyncCommand.php b/tests/Fixtures/Console/DispatchAsyncCommand.php index d796f934e..76d497f87 100644 --- a/tests/Fixtures/Console/DispatchAsyncCommand.php +++ b/tests/Fixtures/Console/DispatchAsyncCommand.php @@ -1,11 +1,13 @@ info('Dispatched commands'); } -} \ No newline at end of file +} diff --git a/tests/Fixtures/Handlers/MyAsyncCommandHandler.php b/tests/Fixtures/Handlers/MyAsyncCommandHandler.php index 7c167f39c..c9ee1d8ce 100644 --- a/tests/Fixtures/Handlers/MyAsyncCommandHandler.php +++ b/tests/Fixtures/Handlers/MyAsyncCommandHandler.php @@ -1,5 +1,7 @@ assertEmpty($this->repository->available()); $this->assertTrue(MyAsyncCommandHandler::$isHandled); } -} \ No newline at end of file +} diff --git a/tests/Integration/CommandBus/Fixtures/MyAsyncCommand.php b/tests/Integration/CommandBus/Fixtures/MyAsyncCommand.php index 70873cd92..02bb32895 100644 --- a/tests/Integration/CommandBus/Fixtures/MyAsyncCommand.php +++ b/tests/Integration/CommandBus/Fixtures/MyAsyncCommand.php @@ -1,5 +1,7 @@ Date: Fri, 8 Nov 2024 14:43:23 +0100 Subject: [PATCH 05/14] Add tests --- .../CommandBus/src/MonitorAsyncCommands.php | 9 +++- .../Fixtures/Console/DispatchAsyncCommand.php | 4 +- .../CommandBus/AsyncCommandTest.php | 47 ++++++++++++++----- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php index ff2067004..9c67e0930 100644 --- a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -49,13 +49,13 @@ public function __invoke(): void ->filter(fn (string $uuid) => ! in_array($uuid, array_keys($processes))); if (count($processes) === 5) { - sleep(1); + $this->sleep(0.5); continue; } if ($availableUuids->isEmpty()) { - sleep(1); + $this->sleep(0.5); continue; } @@ -69,4 +69,9 @@ public function __invoke(): void $processes[$uuid] = $process; } } + + private function sleep(float $seconds): void + { + usleep((int) ($seconds * 1_000_000)); + } } diff --git a/tests/Fixtures/Console/DispatchAsyncCommand.php b/tests/Fixtures/Console/DispatchAsyncCommand.php index 76d497f87..7d695d85a 100644 --- a/tests/Fixtures/Console/DispatchAsyncCommand.php +++ b/tests/Fixtures/Console/DispatchAsyncCommand.php @@ -14,9 +14,9 @@ use HasConsole; #[ConsoleCommand(name: 'command:dispatch')] - public function __invoke(): void + public function __invoke(int $times = 10): void { - foreach (range(1, 10) as $i) { + foreach (range(1, $times) as $i) { command(new MyAsyncCommand("{$i}")); } diff --git a/tests/Integration/CommandBus/AsyncCommandTest.php b/tests/Integration/CommandBus/AsyncCommandTest.php index ac1ee63a4..0a20417b3 100644 --- a/tests/Integration/CommandBus/AsyncCommandTest.php +++ b/tests/Integration/CommandBus/AsyncCommandTest.php @@ -4,9 +4,11 @@ namespace Tests\Tempest\Integration\CommandBus; +use Symfony\Component\Process\Process; use function Tempest\command; use Tempest\CommandBus\AsyncCommandRepositories\MemoryRepository; use Tempest\CommandBus\AsyncCommandRepository; +use Tempest\Highlight\Themes\TerminalStyle; use Tests\Tempest\Fixtures\Handlers\MyAsyncCommandHandler; use Tests\Tempest\Integration\CommandBus\Fixtures\MyAsyncCommand; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; @@ -16,31 +18,24 @@ */ final class AsyncCommandTest extends FrameworkIntegrationTestCase { - private MemoryRepository $repository; - - protected function setUp(): void + public function test_async_commands_are_stored_and_handled_afterwards(): void { - parent::setUp(); - - $this->repository = new MemoryRepository(); + $repository = new MemoryRepository(); $this->container->singleton( AsyncCommandRepository::class, - fn () => $this->repository + fn () => $repository ); MyAsyncCommandHandler::$isHandled = false; - } - public function test_async_commands_are_stored_and_handled_afterwards(): void - { command(new MyAsyncCommand('Brent')); - $uuids = $this->repository->available(); + $uuids = $repository->available(); $this->assertCount(1, $uuids); $uuid = $uuids[0]; - $command = $this->repository->find($uuid); + $command = $repository->find($uuid); $this->assertSame('Brent', $command->name); $this->assertFalse(MyAsyncCommandHandler::$isHandled); @@ -48,7 +43,33 @@ public function test_async_commands_are_stored_and_handled_afterwards(): void ->call("command:handle {$uuid}") ->assertSee('Done'); - $this->assertEmpty($this->repository->available()); + $this->assertEmpty($repository->available()); $this->assertTrue(MyAsyncCommandHandler::$isHandled); } + + public function test_async_command_monitor(): void + { + $process = new Process(['php', 'tempest', 'command:monitor']); + $process->start(); + + $this->console->call("command:dispatch 1"); + + sleep(1); + + $output = $this->getOutput($process); + + $this->assertStringContainsString('Monitoring for new commands', $output); + $this->assertStringContainsString('started at', $output); + $this->assertStringContainsString('finished at', $output); + } + + private function getOutput(Process $process): string + { + $pattern = array_map( + fn (TerminalStyle $consoleStyle) => TerminalStyle::ESC->value . $consoleStyle->value, + TerminalStyle::cases(), + ); + + return str_replace($pattern, '', $process->getOutput()); + } } From c01d4b11b7796c704db075aaedeb6d7235cc8389 Mon Sep 17 00:00:00 2001 From: Brent Roose Date: Fri, 8 Nov 2024 15:19:21 +0100 Subject: [PATCH 06/14] wip state management --- .../FileCommandRepository.php | 54 +++++++++++++++++++ .../FileRepository.php | 41 -------------- .../MemoryRepository.php | 7 ++- .../CommandBus/src/AsyncCommandRepository.php | 4 +- .../src/AsyncCommandRepositoryInitializer.php | 4 +- .../CommandBus/src/HandleAsyncCommand.php | 7 ++- .../CommandBus/src/MonitorAsyncCommands.php | 28 ++++++---- .../CommandBus/src/stored-commands/.gitignore | 2 +- .../src/Exceptions/ConsoleErrorHandler.php | 2 +- .../Fixtures/Console/DispatchAsyncCommand.php | 8 ++- .../Handlers/MyAsyncCommandHandler.php | 10 +++- .../Fixtures/MyFailingAsyncCommand.php | 16 ++++++ 12 files changed, 120 insertions(+), 63 deletions(-) create mode 100644 src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php delete mode 100644 src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php create mode 100644 tests/Integration/CommandBus/Fixtures/MyFailingAsyncCommand.php diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php new file mode 100644 index 000000000..3466a4c32 --- /dev/null +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php @@ -0,0 +1,54 @@ +markAsFailed($uuid); + } + } + + public function markAsDone(string $uuid): void + { + $path = __DIR__ . "/../stored-commands/{$uuid}.pending.txt"; + + unlink($path); + } + + public function markAsFailed(string $uuid): void + { + rename( + from: __DIR__ . "/../stored-commands/{$uuid}.pending.txt", + to: __DIR__ . "/../stored-commands/{$uuid}.failed.txt" + ); + } + + public function available(): array + { + return arr(glob(__DIR__ . "/../stored-commands/*.pending.txt")) + ->map(fn (string $path) => pathinfo($path, PATHINFO_FILENAME)) + ->toArray(); + } +} diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php deleted file mode 100644 index e4a5a9fae..000000000 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileRepository.php +++ /dev/null @@ -1,41 +0,0 @@ -map(fn (string $path) => pathinfo($path, PATHINFO_FILENAME)) - ->toArray(); - } -} diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php index bfe598c3e..589fd782f 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php @@ -20,7 +20,12 @@ public function find(string $uuid): object return $this->commands[$uuid]; } - public function remove(string $uuid): void + public function markAsDone(string $uuid): void + { + unset($this->commands[$uuid]); + } + + public function markAsFailed(string $uuid): void { unset($this->commands[$uuid]); } diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepository.php index ada1c7a47..da8989207 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepository.php @@ -10,7 +10,9 @@ public function store(string $uuid, object $command): void; public function find(string $uuid): object; - public function remove(string $uuid): void; + public function markAsDone(string $uuid): void; + + public function markAsFailed(string $uuid): void; /** @return string[] */ public function available(): array; diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php b/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php index 72d7d6694..fff7ffee6 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php @@ -4,7 +4,7 @@ namespace Tempest\CommandBus; -use Tempest\CommandBus\AsyncCommandRepositories\FileRepository; +use Tempest\CommandBus\AsyncCommandRepositories\FileCommandRepository; use Tempest\Container\Container; use Tempest\Container\Initializer; @@ -14,6 +14,6 @@ public function initialize(Container $container): AsyncCommandRepository { // TODO: refactor to make it configurable - return new FileRepository(); + return new FileCommandRepository(); } } diff --git a/src/Tempest/CommandBus/src/HandleAsyncCommand.php b/src/Tempest/CommandBus/src/HandleAsyncCommand.php index f31d27cd9..b697ff92a 100644 --- a/src/Tempest/CommandBus/src/HandleAsyncCommand.php +++ b/src/Tempest/CommandBus/src/HandleAsyncCommand.php @@ -6,8 +6,10 @@ use Tempest\Console\Console; use Tempest\Console\ConsoleCommand; +use Tempest\Console\ExitCode; use Tempest\Console\HasConsole; use Tempest\Container\Container; +use Throwable; final readonly class HandleAsyncCommand { @@ -18,8 +20,7 @@ public function __construct( private Container $container, private Console $console, private AsyncCommandRepository $repository, - ) { - } + ) {} #[ConsoleCommand(name: 'command:handle')] public function __invoke(string $uuid): void @@ -41,8 +42,6 @@ public function __invoke(string $uuid): void [$command], ); - $this->repository->remove($uuid); - $this->success('Done'); } } diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php index 9c67e0930..9bd98eae4 100644 --- a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -8,6 +8,7 @@ use Symfony\Component\Process\Process; use Tempest\Console\Console; use Tempest\Console\ConsoleCommand; +use Tempest\Console\ExitCode; use Tempest\Console\HasConsole; use function Tempest\Support\arr; @@ -30,18 +31,27 @@ public function __invoke(): void $processes = []; while (true) { // @phpstan-ignore-line - foreach ($processes as $key => $process) { - $errorOutput = trim($process->getErrorOutput()); - + foreach ($processes as $uuid => $process) { $time = new DateTimeImmutable(); - if ($errorOutput) { - $this->error($errorOutput); - $this->writeln("{$key} failed at {$time->format('Y-m-d H:i:s')}"); - unset($processes[$key]); + if ($process->getExitCode() !== ExitCode::SUCCESS) { + $errorOutput = trim($process->getErrorOutput()); + + if ($errorOutput) { + $this->error($errorOutput); + } + + $this->repository->markAsFailed($uuid); + + $this->writeln("{$uuid} failed at {$time->format('Y-m-d H:i:s')}"); + + unset($processes[$uuid]); } elseif ($process->isTerminated()) { - $this->writeln("{$key} finished at {$time->format('Y-m-d H:i:s')}"); - unset($processes[$key]); + $this->writeln("{$uuid} finished at {$time->format('Y-m-d H:i:s')}"); + + $this->repository->markAsDone($uuid); + + unset($processes[$uuid]); } } diff --git a/src/Tempest/CommandBus/src/stored-commands/.gitignore b/src/Tempest/CommandBus/src/stored-commands/.gitignore index 5fb03d009..314f02b1b 100644 --- a/src/Tempest/CommandBus/src/stored-commands/.gitignore +++ b/src/Tempest/CommandBus/src/stored-commands/.gitignore @@ -1 +1 @@ -./* \ No newline at end of file +*.txt \ No newline at end of file diff --git a/src/Tempest/Console/src/Exceptions/ConsoleErrorHandler.php b/src/Tempest/Console/src/Exceptions/ConsoleErrorHandler.php index 0fbd29eef..dab363c1b 100644 --- a/src/Tempest/Console/src/Exceptions/ConsoleErrorHandler.php +++ b/src/Tempest/Console/src/Exceptions/ConsoleErrorHandler.php @@ -57,7 +57,7 @@ public function handleException(Throwable $throwable): void public function handleError(int $errNo, string $errstr, string $errFile, int $errLine): void { - ll('error'); + ll(error: $errstr); $this->console ->writeln() diff --git a/tests/Fixtures/Console/DispatchAsyncCommand.php b/tests/Fixtures/Console/DispatchAsyncCommand.php index 7d695d85a..2c7aff8e7 100644 --- a/tests/Fixtures/Console/DispatchAsyncCommand.php +++ b/tests/Fixtures/Console/DispatchAsyncCommand.php @@ -4,6 +4,7 @@ namespace Tests\Tempest\Fixtures\Console; +use Tests\Tempest\Integration\CommandBus\Fixtures\MyFailingAsyncCommand; use function Tempest\command; use Tempest\Console\ConsoleCommand; use Tempest\Console\HasConsole; @@ -14,10 +15,13 @@ use HasConsole; #[ConsoleCommand(name: 'command:dispatch')] - public function __invoke(int $times = 10): void + public function __invoke(int $times = 10, bool $failing = false): void { foreach (range(1, $times) as $i) { - command(new MyAsyncCommand("{$i}")); + command($failing + ? new MyFailingAsyncCommand("{$i}") + : new MyAsyncCommand("{$i}"), + ); } $this->info('Dispatched commands'); diff --git a/tests/Fixtures/Handlers/MyAsyncCommandHandler.php b/tests/Fixtures/Handlers/MyAsyncCommandHandler.php index c9ee1d8ce..b9466e14d 100644 --- a/tests/Fixtures/Handlers/MyAsyncCommandHandler.php +++ b/tests/Fixtures/Handlers/MyAsyncCommandHandler.php @@ -4,16 +4,24 @@ namespace Tests\Tempest\Fixtures\Handlers; +use Exception; use Tempest\CommandBus\CommandHandler; use Tests\Tempest\Integration\CommandBus\Fixtures\MyAsyncCommand; +use Tests\Tempest\Integration\CommandBus\Fixtures\MyFailingAsyncCommand; final class MyAsyncCommandHandler { public static bool $isHandled = false; #[CommandHandler] - public function __invoke(MyAsyncCommand $command): void + public function onMyAsyncCommand(MyAsyncCommand $command): void { self::$isHandled = true; } + + #[CommandHandler] + public function onMyFailingAsyncCommand(MyFailingAsyncCommand $command): void + { + throw new Exception('Failed command'); + } } diff --git a/tests/Integration/CommandBus/Fixtures/MyFailingAsyncCommand.php b/tests/Integration/CommandBus/Fixtures/MyFailingAsyncCommand.php new file mode 100644 index 000000000..bb65aca41 --- /dev/null +++ b/tests/Integration/CommandBus/Fixtures/MyFailingAsyncCommand.php @@ -0,0 +1,16 @@ + Date: Fri, 8 Nov 2024 15:22:00 +0100 Subject: [PATCH 07/14] wip state management --- .../src/AsyncCommandRepositories/FileCommandRepository.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php index 3466a4c32..456807572 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php @@ -48,7 +48,9 @@ public function markAsFailed(string $uuid): void public function available(): array { return arr(glob(__DIR__ . "/../stored-commands/*.pending.txt")) - ->map(fn (string $path) => pathinfo($path, PATHINFO_FILENAME)) + ->map(function (string $path) { + return str_replace('.pending.txt', '', pathinfo($path, PATHINFO_BASENAME)); + }) ->toArray(); } } From 76bd9088ba007dbe57a413ff76c9d11879c2f14d Mon Sep 17 00:00:00 2001 From: brendt Date: Sat, 9 Nov 2024 09:16:54 +0100 Subject: [PATCH 08/14] wip --- .../src/AsyncCommandRepositories/FileCommandRepository.php | 4 ++-- src/Tempest/CommandBus/src/HandleAsyncCommand.php | 5 ++--- tests/Fixtures/Console/DispatchAsyncCommand.php | 5 +++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php index 456807572..18aadd824 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php @@ -5,8 +5,8 @@ namespace Tempest\CommandBus\AsyncCommandRepositories; use Tempest\CommandBus\AsyncCommandRepository; -use Throwable; use function Tempest\Support\arr; +use Throwable; final readonly class FileCommandRepository implements AsyncCommandRepository { @@ -25,7 +25,7 @@ public function find(string $uuid): object try { return unserialize($payload); - } catch (Throwable $e) { + } catch (Throwable) { $this->markAsFailed($uuid); } } diff --git a/src/Tempest/CommandBus/src/HandleAsyncCommand.php b/src/Tempest/CommandBus/src/HandleAsyncCommand.php index b697ff92a..13a584811 100644 --- a/src/Tempest/CommandBus/src/HandleAsyncCommand.php +++ b/src/Tempest/CommandBus/src/HandleAsyncCommand.php @@ -6,10 +6,8 @@ use Tempest\Console\Console; use Tempest\Console\ConsoleCommand; -use Tempest\Console\ExitCode; use Tempest\Console\HasConsole; use Tempest\Container\Container; -use Throwable; final readonly class HandleAsyncCommand { @@ -20,7 +18,8 @@ public function __construct( private Container $container, private Console $console, private AsyncCommandRepository $repository, - ) {} + ) { + } #[ConsoleCommand(name: 'command:handle')] public function __invoke(string $uuid): void diff --git a/tests/Fixtures/Console/DispatchAsyncCommand.php b/tests/Fixtures/Console/DispatchAsyncCommand.php index 2c7aff8e7..9b58151bb 100644 --- a/tests/Fixtures/Console/DispatchAsyncCommand.php +++ b/tests/Fixtures/Console/DispatchAsyncCommand.php @@ -4,11 +4,11 @@ namespace Tests\Tempest\Fixtures\Console; -use Tests\Tempest\Integration\CommandBus\Fixtures\MyFailingAsyncCommand; use function Tempest\command; use Tempest\Console\ConsoleCommand; use Tempest\Console\HasConsole; use Tests\Tempest\Integration\CommandBus\Fixtures\MyAsyncCommand; +use Tests\Tempest\Integration\CommandBus\Fixtures\MyFailingAsyncCommand; final readonly class DispatchAsyncCommand { @@ -18,7 +18,8 @@ public function __invoke(int $times = 10, bool $failing = false): void { foreach (range(1, $times) as $i) { - command($failing + command( + $failing ? new MyFailingAsyncCommand("{$i}") : new MyAsyncCommand("{$i}"), ); From de5127ee478d9926ed71889b56d6fbc0c4d6a4d8 Mon Sep 17 00:00:00 2001 From: brendt Date: Sat, 9 Nov 2024 13:01:48 +0100 Subject: [PATCH 09/14] wip --- .../FileCommandRepository.php | 16 +++---- .../MemoryRepository.php | 2 +- .../CommandBus/src/AsyncCommandRepository.php | 2 +- .../src/Exceptions/CouldNotResolveCommand.php | 11 +++++ .../CommandBus/src/HandleAsyncCommand.php | 48 ++++++++++++++----- .../CommandBus/src/MonitorAsyncCommands.php | 27 +++++------ .../src/Actions/ExecuteConsoleCommand.php | 4 +- .../Fixtures/Console/DispatchAsyncCommand.php | 10 ++-- .../CommandBus/AsyncCommandTest.php | 23 +++++++-- 9 files changed, 96 insertions(+), 47 deletions(-) create mode 100644 src/Tempest/CommandBus/src/Exceptions/CouldNotResolveCommand.php diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php index 18aadd824..593809ee5 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php @@ -5,8 +5,8 @@ namespace Tempest\CommandBus\AsyncCommandRepositories; use Tempest\CommandBus\AsyncCommandRepository; +use Tempest\CommandBus\Exceptions\CouldNotResolveCommand; use function Tempest\Support\arr; -use Throwable; final readonly class FileCommandRepository implements AsyncCommandRepository { @@ -21,13 +21,13 @@ public function find(string $uuid): object { $path = __DIR__ . "/../stored-commands/{$uuid}.pending.txt"; + if (! file_exists($path)) { + throw new CouldNotResolveCommand($uuid); + } + $payload = file_get_contents($path); - try { - return unserialize($payload); - } catch (Throwable) { - $this->markAsFailed($uuid); - } + return unserialize($payload); } public function markAsDone(string $uuid): void @@ -41,11 +41,11 @@ public function markAsFailed(string $uuid): void { rename( from: __DIR__ . "/../stored-commands/{$uuid}.pending.txt", - to: __DIR__ . "/../stored-commands/{$uuid}.failed.txt" + to: __DIR__ . "/../stored-commands/{$uuid}.failed.txt", ); } - public function available(): array + public function getPendingUuids(): array { return arr(glob(__DIR__ . "/../stored-commands/*.pending.txt")) ->map(function (string $path) { diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php index 589fd782f..4eb646180 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php @@ -30,7 +30,7 @@ public function markAsFailed(string $uuid): void unset($this->commands[$uuid]); } - public function available(): array + public function getPendingUuids(): array { return array_keys($this->commands); } diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepository.php index da8989207..03e3c5fa1 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepository.php @@ -15,5 +15,5 @@ public function markAsDone(string $uuid): void; public function markAsFailed(string $uuid): void; /** @return string[] */ - public function available(): array; + public function getPendingUuids(): array; } diff --git a/src/Tempest/CommandBus/src/Exceptions/CouldNotResolveCommand.php b/src/Tempest/CommandBus/src/Exceptions/CouldNotResolveCommand.php new file mode 100644 index 000000000..483b836e0 --- /dev/null +++ b/src/Tempest/CommandBus/src/Exceptions/CouldNotResolveCommand.php @@ -0,0 +1,11 @@ +repository->find($uuid); + $uuid ??= $this->repository->getPendingUuids()[0] ?? null; - $commandHandler = $this->commandBusConfig->handlers[$command::class] ?? null; + if (! $uuid) { + $this->error('No pending command found'); - if (! $commandHandler) { - $commandClass = $command::class; + return ExitCode::ERROR; + } - $this->error("No handler found for command {$commandClass}"); + try { + $command = $this->repository->find($uuid); - return; - } + $commandHandler = $this->commandBusConfig->handlers[$command::class] ?? null; + + if (! $commandHandler) { + $commandClass = $command::class; + + $this->error("No handler found for command {$commandClass}"); + + return ExitCode::ERROR; + } - $commandHandler->handler->invokeArgs( - $this->container->get($commandHandler->handler->getDeclaringClass()->getName()), - [$command], - ); + $commandHandler->handler->invokeArgs( + $this->container->get($commandHandler->handler->getDeclaringClass()->getName()), + [$command], + ); - $this->success('Done'); + $this->repository->markAsDone($uuid); + + $this->success('Done'); + + return ExitCode::SUCCESS; + } catch (Throwable $throwable) { + $this->repository->markAsFailed($uuid); + + $this->error($throwable->getMessage()); + + return ExitCode::ERROR; + } } } diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php index 9bd98eae4..6e823c07d 100644 --- a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -8,7 +8,6 @@ use Symfony\Component\Process\Process; use Tempest\Console\Console; use Tempest\Console\ConsoleCommand; -use Tempest\Console\ExitCode; use Tempest\Console\HasConsole; use function Tempest\Support\arr; @@ -34,28 +33,26 @@ public function __invoke(): void foreach ($processes as $uuid => $process) { $time = new DateTimeImmutable(); - if ($process->getExitCode() !== ExitCode::SUCCESS) { - $errorOutput = trim($process->getErrorOutput()); - - if ($errorOutput) { - $this->error($errorOutput); + if ($process->isTerminated()) { + if ($process->isSuccessful()) { + $this->writeln("{$uuid} finished at {$time->format('Y-m-d H:i:s')}"); + } else { + $this->writeln("{$uuid} failed at {$time->format('Y-m-d H:i:s')}"); } - $this->repository->markAsFailed($uuid); - - $this->writeln("{$uuid} failed at {$time->format('Y-m-d H:i:s')}"); - - unset($processes[$uuid]); - } elseif ($process->isTerminated()) { - $this->writeln("{$uuid} finished at {$time->format('Y-m-d H:i:s')}"); + if ($output = trim($process->getOutput())) { + $this->writeln($output); + } - $this->repository->markAsDone($uuid); + if ($errorOutput = trim($process->getErrorOutput())) { + $this->writeln($errorOutput); + } unset($processes[$uuid]); } } - $availableUuids = arr($this->repository->available()) + $availableUuids = arr($this->repository->getPendingUuids()) ->filter(fn (string $uuid) => ! in_array($uuid, array_keys($processes))); if (count($processes) === 5) { diff --git a/src/Tempest/Console/src/Actions/ExecuteConsoleCommand.php b/src/Tempest/Console/src/Actions/ExecuteConsoleCommand.php index 09f849b3c..a799c3105 100644 --- a/src/Tempest/Console/src/Actions/ExecuteConsoleCommand.php +++ b/src/Tempest/Console/src/Actions/ExecuteConsoleCommand.php @@ -43,12 +43,12 @@ private function getCallable(array $commandMiddleware): ConsoleMiddlewareCallabl $inputBuilder = new ConsoleInputBuilder($consoleCommand, $invocation->argumentBag); - $consoleCommand->handler->invokeArgs( + $exitCode = $consoleCommand->handler->invokeArgs( $consoleCommandClass, $inputBuilder->build(), ); - return ExitCode::SUCCESS; + return $exitCode ?? ExitCode::SUCCESS; }); $middlewareStack = [...$this->consoleConfig->middleware, ...$commandMiddleware]; diff --git a/tests/Fixtures/Console/DispatchAsyncCommand.php b/tests/Fixtures/Console/DispatchAsyncCommand.php index 9b58151bb..248313800 100644 --- a/tests/Fixtures/Console/DispatchAsyncCommand.php +++ b/tests/Fixtures/Console/DispatchAsyncCommand.php @@ -15,13 +15,15 @@ use HasConsole; #[ConsoleCommand(name: 'command:dispatch')] - public function __invoke(int $times = 10, bool $failing = false): void + public function __invoke(int $times = 10, bool $fail = false): void { foreach (range(1, $times) as $i) { - command( - $failing + $command = $fail ? new MyFailingAsyncCommand("{$i}") - : new MyAsyncCommand("{$i}"), + : new MyAsyncCommand("{$i}"); + + command( + $command, ); } diff --git a/tests/Integration/CommandBus/AsyncCommandTest.php b/tests/Integration/CommandBus/AsyncCommandTest.php index 0a20417b3..6d50342bc 100644 --- a/tests/Integration/CommandBus/AsyncCommandTest.php +++ b/tests/Integration/CommandBus/AsyncCommandTest.php @@ -31,7 +31,7 @@ public function test_async_commands_are_stored_and_handled_afterwards(): void command(new MyAsyncCommand('Brent')); - $uuids = $repository->available(); + $uuids = $repository->getPendingUuids(); $this->assertCount(1, $uuids); $uuid = $uuids[0]; @@ -43,7 +43,6 @@ public function test_async_commands_are_stored_and_handled_afterwards(): void ->call("command:handle {$uuid}") ->assertSee('Done'); - $this->assertEmpty($repository->available()); $this->assertTrue(MyAsyncCommandHandler::$isHandled); } @@ -57,10 +56,28 @@ public function test_async_command_monitor(): void sleep(1); $output = $this->getOutput($process); - $this->assertStringContainsString('Monitoring for new commands', $output); $this->assertStringContainsString('started at', $output); $this->assertStringContainsString('finished at', $output); + $this->assertStringContainsString('Done', $output); + $process->stop(); + } + + public function test_async_failed_command_monitor(): void + { + $process = new Process(['php', 'tempest', 'command:monitor']); + $process->start(); + + $this->console->call("command:dispatch 1 --fail"); + + sleep(1); + + $output = $this->getOutput($process); + $this->assertStringContainsString('Monitoring for new commands', $output); + $this->assertStringContainsString('started at', $output); + $this->assertStringContainsString('failed at', $output); + $this->assertStringContainsString('Failed command', $output); + $process->stop(); } private function getOutput(Process $process): string From 0ed5d9a7eb1126beef4bcaef22414f5e09ed8a75 Mon Sep 17 00:00:00 2001 From: brendt Date: Sat, 9 Nov 2024 13:39:43 +0100 Subject: [PATCH 10/14] wip --- src/Tempest/CommandBus/src/MonitorAsyncCommands.php | 13 ++++++++++++- .../Console/src/Input/ConsoleArgumentBag.php | 5 +++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php index 6e823c07d..efe8a4525 100644 --- a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -9,6 +9,7 @@ use Tempest\Console\Console; use Tempest\Console\ConsoleCommand; use Tempest\Console\HasConsole; +use Tempest\Console\Input\ConsoleArgumentBag; use function Tempest\Support\arr; final readonly class MonitorAsyncCommands @@ -17,6 +18,7 @@ public function __construct( private AsyncCommandRepository $repository, + private ConsoleArgumentBag $argumentBag, private Console $console, ) { } @@ -69,10 +71,19 @@ public function __invoke(): void // Start a task $uuid = $availableUuids->first(); + $time = new DateTimeImmutable(); $this->writeln("

{$uuid}

started at {$time->format('Y-m-d H:i:s')}"); - $process = new Process(['php', 'tempest', 'command:handle', $uuid], getcwd()); + + $process = new Process([ + $this->argumentBag->getBinaryPath(), + $this->argumentBag->getCliName(), + 'command:handle', + $uuid + ], getcwd()); + $process->start(); + $processes[$uuid] = $process; } } diff --git a/src/Tempest/Console/src/Input/ConsoleArgumentBag.php b/src/Tempest/Console/src/Input/ConsoleArgumentBag.php index 24331cd22..2a8aa0a91 100644 --- a/src/Tempest/Console/src/Input/ConsoleArgumentBag.php +++ b/src/Tempest/Console/src/Input/ConsoleArgumentBag.php @@ -132,6 +132,11 @@ public function add(ConsoleInputArgument $argument): self return $this; } + public function getBinaryPath(): string + { + return PHP_BINARY; + } + public function getCliName(): string { return $this->path[0] ?? ''; From 1a8a522d6e1e1fe2e1176d787b3d6bb5fbdd3f95 Mon Sep 17 00:00:00 2001 From: brendt Date: Sat, 9 Nov 2024 13:41:51 +0100 Subject: [PATCH 11/14] wip --- src/Tempest/CommandBus/src/AsyncCommandMiddleware.php | 2 +- .../src/AsyncCommandRepositories/FileCommandRepository.php | 4 ++-- .../src/AsyncCommandRepositories/MemoryRepository.php | 4 ++-- .../CommandBus/src/AsyncCommandRepositoryInitializer.php | 2 +- .../src/{AsyncCommandRepository.php => CommandRepository.php} | 2 +- src/Tempest/CommandBus/src/HandleAsyncCommand.php | 2 +- src/Tempest/CommandBus/src/MonitorAsyncCommands.php | 4 ++-- tests/Integration/CommandBus/AsyncCommandTest.php | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) rename src/Tempest/CommandBus/src/{AsyncCommandRepository.php => CommandRepository.php} (91%) diff --git a/src/Tempest/CommandBus/src/AsyncCommandMiddleware.php b/src/Tempest/CommandBus/src/AsyncCommandMiddleware.php index d08546e1a..0007cc8ba 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandMiddleware.php +++ b/src/Tempest/CommandBus/src/AsyncCommandMiddleware.php @@ -13,7 +13,7 @@ { public function __construct( private CommandBusConfig $commandBusConfig, - private AsyncCommandRepository $repository, + private CommandRepository $repository, ) { } diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php index 593809ee5..fe1e4d44b 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php @@ -4,11 +4,11 @@ namespace Tempest\CommandBus\AsyncCommandRepositories; -use Tempest\CommandBus\AsyncCommandRepository; +use Tempest\CommandBus\CommandRepository; use Tempest\CommandBus\Exceptions\CouldNotResolveCommand; use function Tempest\Support\arr; -final readonly class FileCommandRepository implements AsyncCommandRepository +final readonly class FileCommandRepository implements CommandRepository { public function store(string $uuid, object $command): void { diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php index 4eb646180..f984214af 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php @@ -4,9 +4,9 @@ namespace Tempest\CommandBus\AsyncCommandRepositories; -use Tempest\CommandBus\AsyncCommandRepository; +use Tempest\CommandBus\CommandRepository; -final class MemoryRepository implements AsyncCommandRepository +final class MemoryRepository implements CommandRepository { private array $commands = []; diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php b/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php index fff7ffee6..e1dfb6f9a 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php @@ -10,7 +10,7 @@ final readonly class AsyncCommandRepositoryInitializer implements Initializer { - public function initialize(Container $container): AsyncCommandRepository + public function initialize(Container $container): CommandRepository { // TODO: refactor to make it configurable diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepository.php b/src/Tempest/CommandBus/src/CommandRepository.php similarity index 91% rename from src/Tempest/CommandBus/src/AsyncCommandRepository.php rename to src/Tempest/CommandBus/src/CommandRepository.php index 03e3c5fa1..a15d1ecdd 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepository.php +++ b/src/Tempest/CommandBus/src/CommandRepository.php @@ -4,7 +4,7 @@ namespace Tempest\CommandBus; -interface AsyncCommandRepository +interface CommandRepository { public function store(string $uuid, object $command): void; diff --git a/src/Tempest/CommandBus/src/HandleAsyncCommand.php b/src/Tempest/CommandBus/src/HandleAsyncCommand.php index 8d430b509..b525d4ea3 100644 --- a/src/Tempest/CommandBus/src/HandleAsyncCommand.php +++ b/src/Tempest/CommandBus/src/HandleAsyncCommand.php @@ -19,7 +19,7 @@ public function __construct( private CommandBusConfig $commandBusConfig, private Container $container, private Console $console, - private AsyncCommandRepository $repository, + private CommandRepository $repository, ) { } diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php index efe8a4525..6fcb59c21 100644 --- a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -17,7 +17,7 @@ use HasConsole; public function __construct( - private AsyncCommandRepository $repository, + private CommandRepository $repository, private ConsoleArgumentBag $argumentBag, private Console $console, ) { @@ -79,7 +79,7 @@ public function __invoke(): void $this->argumentBag->getBinaryPath(), $this->argumentBag->getCliName(), 'command:handle', - $uuid + $uuid, ], getcwd()); $process->start(); diff --git a/tests/Integration/CommandBus/AsyncCommandTest.php b/tests/Integration/CommandBus/AsyncCommandTest.php index 6d50342bc..dd4467f44 100644 --- a/tests/Integration/CommandBus/AsyncCommandTest.php +++ b/tests/Integration/CommandBus/AsyncCommandTest.php @@ -7,7 +7,7 @@ use Symfony\Component\Process\Process; use function Tempest\command; use Tempest\CommandBus\AsyncCommandRepositories\MemoryRepository; -use Tempest\CommandBus\AsyncCommandRepository; +use Tempest\CommandBus\CommandRepository; use Tempest\Highlight\Themes\TerminalStyle; use Tests\Tempest\Fixtures\Handlers\MyAsyncCommandHandler; use Tests\Tempest\Integration\CommandBus\Fixtures\MyAsyncCommand; @@ -23,7 +23,7 @@ public function test_async_commands_are_stored_and_handled_afterwards(): void $repository = new MemoryRepository(); $this->container->singleton( - AsyncCommandRepository::class, + CommandRepository::class, fn () => $repository ); From 531bd4b7faf4eb4ca330aefafe35f4c5c9a4db4d Mon Sep 17 00:00:00 2001 From: brendt Date: Sat, 9 Nov 2024 20:41:55 +0100 Subject: [PATCH 12/14] wip --- .../src/AsyncCommandRepositoryInitializer.php | 19 ------------------ .../CommandBus/src/CommandBusConfig.php | 4 ++++ .../src/CommandRepositoryInitializer.php | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 19 deletions(-) delete mode 100644 src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php create mode 100644 src/Tempest/CommandBus/src/CommandRepositoryInitializer.php diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php b/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php deleted file mode 100644 index e1dfb6f9a..000000000 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositoryInitializer.php +++ /dev/null @@ -1,19 +0,0 @@ -> */ public array $middleware = [], + + /** @var class-string<\Tempest\CommandBus\CommandRepository> $commandRepositoryClass */ + public string $commandRepositoryClass = FileCommandRepository::class, ) { } diff --git a/src/Tempest/CommandBus/src/CommandRepositoryInitializer.php b/src/Tempest/CommandBus/src/CommandRepositoryInitializer.php new file mode 100644 index 000000000..b2afa16a2 --- /dev/null +++ b/src/Tempest/CommandBus/src/CommandRepositoryInitializer.php @@ -0,0 +1,20 @@ +get(CommandBusConfig::class)->commandRepositoryClass; + + return $container->get($commandRepositoryClass); + } +} From f5f732d5746fe7634d8d5e696be4322bd0c6ac52 Mon Sep 17 00:00:00 2001 From: brendt Date: Sat, 9 Nov 2024 21:01:07 +0100 Subject: [PATCH 13/14] wip --- .../FileCommandRepository.php | 12 +++++--- .../MemoryRepository.php | 12 ++++---- .../CommandBus/src/CommandRepository.php | 8 ++--- .../CommandBus/src/HandleAsyncCommand.php | 29 ++++++++----------- .../CommandBus/src/MonitorAsyncCommands.php | 8 ++--- .../CommandBus/AsyncCommandTest.php | 10 +++---- 6 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php index fe1e4d44b..3c6a2cb04 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/FileCommandRepository.php @@ -17,7 +17,7 @@ public function store(string $uuid, object $command): void file_put_contents(__DIR__ . "/../stored-commands/{$uuid}.pending.txt", $payload); } - public function find(string $uuid): object + public function findPendingCommand(string $uuid): object { $path = __DIR__ . "/../stored-commands/{$uuid}.pending.txt"; @@ -45,11 +45,15 @@ public function markAsFailed(string $uuid): void ); } - public function getPendingUuids(): array + public function getPendingCommands(): array { return arr(glob(__DIR__ . "/../stored-commands/*.pending.txt")) - ->map(function (string $path) { - return str_replace('.pending.txt', '', pathinfo($path, PATHINFO_BASENAME)); + ->mapWithKeys(function (string $path) { + $uuid = str_replace('.pending.txt', '', pathinfo($path, PATHINFO_BASENAME)); + + $payload = file_get_contents($path); + + yield $uuid => unserialize($payload); }) ->toArray(); } diff --git a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php index f984214af..23a178d73 100644 --- a/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php +++ b/src/Tempest/CommandBus/src/AsyncCommandRepositories/MemoryRepository.php @@ -15,7 +15,12 @@ public function store(string $uuid, object $command): void $this->commands[$uuid] = $command; } - public function find(string $uuid): object + public function getPendingCommands(): array + { + return $this->commands; + } + + public function findPendingCommand(string $uuid): object { return $this->commands[$uuid]; } @@ -29,9 +34,4 @@ public function markAsFailed(string $uuid): void { unset($this->commands[$uuid]); } - - public function getPendingUuids(): array - { - return array_keys($this->commands); - } } diff --git a/src/Tempest/CommandBus/src/CommandRepository.php b/src/Tempest/CommandBus/src/CommandRepository.php index a15d1ecdd..1a0b35d1b 100644 --- a/src/Tempest/CommandBus/src/CommandRepository.php +++ b/src/Tempest/CommandBus/src/CommandRepository.php @@ -8,12 +8,12 @@ interface CommandRepository { public function store(string $uuid, object $command): void; - public function find(string $uuid): object; + /** @return array */ + public function getPendingCommands(): array; + + public function findPendingCommand(string $uuid): object; public function markAsDone(string $uuid): void; public function markAsFailed(string $uuid): void; - - /** @return string[] */ - public function getPendingUuids(): array; } diff --git a/src/Tempest/CommandBus/src/HandleAsyncCommand.php b/src/Tempest/CommandBus/src/HandleAsyncCommand.php index b525d4ea3..90b9e7630 100644 --- a/src/Tempest/CommandBus/src/HandleAsyncCommand.php +++ b/src/Tempest/CommandBus/src/HandleAsyncCommand.php @@ -10,6 +10,7 @@ use Tempest\Console\HasConsole; use Tempest\Container\Container; use Throwable; +use function Tempest\Support\arr; final readonly class HandleAsyncCommand { @@ -20,30 +21,28 @@ public function __construct( private Container $container, private Console $console, private CommandRepository $repository, - ) { - } + ) {} #[ConsoleCommand(name: 'command:handle')] public function __invoke(?string $uuid = null): ExitCode { - $uuid ??= $this->repository->getPendingUuids()[0] ?? null; - - if (! $uuid) { - $this->error('No pending command found'); - - return ExitCode::ERROR; - } - try { - $command = $this->repository->find($uuid); + if ($uuid) { + $command = $this->repository->findPendingCommand($uuid); + } else { + $command = arr($this->repository->getPendingCommands())->first(); + } + + if (! $command) { + $this->error('No pending command found'); + return ExitCode::ERROR; + } $commandHandler = $this->commandBusConfig->handlers[$command::class] ?? null; if (! $commandHandler) { $commandClass = $command::class; - $this->error("No handler found for command {$commandClass}"); - return ExitCode::ERROR; } @@ -53,15 +52,11 @@ public function __invoke(?string $uuid = null): ExitCode ); $this->repository->markAsDone($uuid); - $this->success('Done'); - return ExitCode::SUCCESS; } catch (Throwable $throwable) { $this->repository->markAsFailed($uuid); - $this->error($throwable->getMessage()); - return ExitCode::ERROR; } } diff --git a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php index 6fcb59c21..3508ab027 100644 --- a/src/Tempest/CommandBus/src/MonitorAsyncCommands.php +++ b/src/Tempest/CommandBus/src/MonitorAsyncCommands.php @@ -54,8 +54,8 @@ public function __invoke(): void } } - $availableUuids = arr($this->repository->getPendingUuids()) - ->filter(fn (string $uuid) => ! in_array($uuid, array_keys($processes))); + $availableCommands = arr($this->repository->getPendingCommands()) + ->filter(fn (object $command, string $uuid) => ! in_array($uuid, array_keys($processes))); if (count($processes) === 5) { $this->sleep(0.5); @@ -63,14 +63,14 @@ public function __invoke(): void continue; } - if ($availableUuids->isEmpty()) { + if ($availableCommands->isEmpty()) { $this->sleep(0.5); continue; } // Start a task - $uuid = $availableUuids->first(); + $uuid = $availableCommands->keys()->first(); $time = new DateTimeImmutable(); $this->writeln("

{$uuid}

started at {$time->format('Y-m-d H:i:s')}"); diff --git a/tests/Integration/CommandBus/AsyncCommandTest.php b/tests/Integration/CommandBus/AsyncCommandTest.php index dd4467f44..73ad6fa5e 100644 --- a/tests/Integration/CommandBus/AsyncCommandTest.php +++ b/tests/Integration/CommandBus/AsyncCommandTest.php @@ -12,6 +12,7 @@ use Tests\Tempest\Fixtures\Handlers\MyAsyncCommandHandler; use Tests\Tempest\Integration\CommandBus\Fixtures\MyAsyncCommand; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; +use function Tempest\Support\arr; /** * @internal @@ -31,16 +32,15 @@ public function test_async_commands_are_stored_and_handled_afterwards(): void command(new MyAsyncCommand('Brent')); - $uuids = $repository->getPendingUuids(); + $pendingCommands = arr($repository->getPendingCommands()); - $this->assertCount(1, $uuids); - $uuid = $uuids[0]; - $command = $repository->find($uuid); + $this->assertCount(1, $pendingCommands); + $command = $pendingCommands->first(); $this->assertSame('Brent', $command->name); $this->assertFalse(MyAsyncCommandHandler::$isHandled); $this->console - ->call("command:handle {$uuid}") + ->call("command:handle " . $pendingCommands->keys()->first()) ->assertSee('Done'); $this->assertTrue(MyAsyncCommandHandler::$isHandled); From feaf4f93f2cbdcbcb1446ea91b53604f691a2c9d Mon Sep 17 00:00:00 2001 From: brendt Date: Sat, 9 Nov 2024 21:04:04 +0100 Subject: [PATCH 14/14] wip --- src/Tempest/CommandBus/src/HandleAsyncCommand.php | 9 +++++++-- tests/Integration/CommandBus/AsyncCommandTest.php | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Tempest/CommandBus/src/HandleAsyncCommand.php b/src/Tempest/CommandBus/src/HandleAsyncCommand.php index 90b9e7630..79ede0702 100644 --- a/src/Tempest/CommandBus/src/HandleAsyncCommand.php +++ b/src/Tempest/CommandBus/src/HandleAsyncCommand.php @@ -9,8 +9,8 @@ use Tempest\Console\ExitCode; use Tempest\Console\HasConsole; use Tempest\Container\Container; -use Throwable; use function Tempest\Support\arr; +use Throwable; final readonly class HandleAsyncCommand { @@ -21,7 +21,8 @@ public function __construct( private Container $container, private Console $console, private CommandRepository $repository, - ) {} + ) { + } #[ConsoleCommand(name: 'command:handle')] public function __invoke(?string $uuid = null): ExitCode @@ -35,6 +36,7 @@ public function __invoke(?string $uuid = null): ExitCode if (! $command) { $this->error('No pending command found'); + return ExitCode::ERROR; } @@ -43,6 +45,7 @@ public function __invoke(?string $uuid = null): ExitCode if (! $commandHandler) { $commandClass = $command::class; $this->error("No handler found for command {$commandClass}"); + return ExitCode::ERROR; } @@ -53,10 +56,12 @@ public function __invoke(?string $uuid = null): ExitCode $this->repository->markAsDone($uuid); $this->success('Done'); + return ExitCode::SUCCESS; } catch (Throwable $throwable) { $this->repository->markAsFailed($uuid); $this->error($throwable->getMessage()); + return ExitCode::ERROR; } } diff --git a/tests/Integration/CommandBus/AsyncCommandTest.php b/tests/Integration/CommandBus/AsyncCommandTest.php index 73ad6fa5e..da40850de 100644 --- a/tests/Integration/CommandBus/AsyncCommandTest.php +++ b/tests/Integration/CommandBus/AsyncCommandTest.php @@ -9,10 +9,10 @@ use Tempest\CommandBus\AsyncCommandRepositories\MemoryRepository; use Tempest\CommandBus\CommandRepository; use Tempest\Highlight\Themes\TerminalStyle; +use function Tempest\Support\arr; use Tests\Tempest\Fixtures\Handlers\MyAsyncCommandHandler; use Tests\Tempest\Integration\CommandBus\Fixtures\MyAsyncCommand; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; -use function Tempest\Support\arr; /** * @internal @@ -25,7 +25,7 @@ public function test_async_commands_are_stored_and_handled_afterwards(): void $this->container->singleton( CommandRepository::class, - fn () => $repository + fn () => $repository, ); MyAsyncCommandHandler::$isHandled = false; @@ -78,6 +78,11 @@ public function test_async_failed_command_monitor(): void $this->assertStringContainsString('failed at', $output); $this->assertStringContainsString('Failed command', $output); $process->stop(); + + arr(glob(__DIR__ . '/../../../src/Tempest/CommandBus/src/stored-commands/*.failed.txt')) + ->each(function (string $filename): void { + unlink($filename); + }); } private function getOutput(Process $process): string