diff --git a/composer.json b/composer.json index 53136ba..5abe47a 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "react/async": "^4", "guzzlehttp/guzzle": "^7.10", "zircote/swagger-php": "^5.7", - "spiral/roadrunner-grpc": "^3.5" + "spiral/roadrunner-grpc": "^3.5", + "league/tactician": "^1.1" }, "require-dev": { "phpunit/phpunit": "^12.4", diff --git a/composer.lock b/composer.lock index 9d3dad5..93797e9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "977f74570c671e4d59fd70d5e732c3d2", + "content-hash": "d027bee8e875c5542f7ff9612bfac4e2", "packages": [ { "name": "adhocore/cli", @@ -1360,6 +1360,61 @@ ], "time": "2024-11-25T08:10:15+00:00" }, + { + "name": "league/tactician", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/tactician.git", + "reference": "e79f763170f3d5922ec29e85cffca0bac5cd8975" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/tactician/zipball/e79f763170f3d5922ec29e85cffca0bac5cd8975", + "reference": "e79f763170f3d5922ec29e85cffca0bac5cd8975", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^9.3.8", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Tactician\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ross Tuck", + "homepage": "http://tactician.thephpleague.com" + } + ], + "description": "A small, flexible command bus. Handy for building service layers.", + "keywords": [ + "command", + "command bus", + "service layer" + ], + "support": { + "issues": "https://github.com/thephpleague/tactician/issues", + "source": "https://github.com/thephpleague/tactician/tree/v1.1.0" + }, + "time": "2021-02-14T15:29:04+00:00" + }, { "name": "monolog/monolog", "version": "3.9.0", diff --git a/src/Attributes/CommandBus/HandlesCommand.php b/src/Attributes/CommandBus/HandlesCommand.php new file mode 100644 index 0000000..573a625 --- /dev/null +++ b/src/Attributes/CommandBus/HandlesCommand.php @@ -0,0 +1,22 @@ +getAttributes(HandlesCommand::class); + + foreach ($attributes as $attribute) { + $instance = $attribute->newInstance(); + $commandClass = $instance->commandClass; + $this->handlers[$commandClass] = $fullClassName; + } + } + } + } + } + + public function getHandlerForCommand($commandName) + { + if (isset($this->handlers[$commandName])) { + $handlerClass = $this->handlers[$commandName]; + return new $handlerClass(); + } + + throw new CanNotInvokeHandlerException("No handler found for command: " . $commandName); + } +} diff --git a/src/CommandBus/Commands/Command.php b/src/CommandBus/Commands/Command.php new file mode 100644 index 0000000..7fc4ee3 --- /dev/null +++ b/src/CommandBus/Commands/Command.php @@ -0,0 +1,9 @@ +name; + } +} \ No newline at end of file diff --git a/src/CommandBus/Handlers/CommandHandler.php b/src/CommandBus/Handlers/CommandHandler.php new file mode 100644 index 0000000..77ce9cb --- /dev/null +++ b/src/CommandBus/Handlers/CommandHandler.php @@ -0,0 +1,9 @@ +getName(); + Logger::info('Handling ExampleCommand for name: ' . $name); + + return 'Hello, ' . $name . '!'; + } +} diff --git a/src/Controllers/HealthcheckController.php b/src/Controllers/HealthcheckController.php index 7b5bfda..66259ce 100644 --- a/src/Controllers/HealthcheckController.php +++ b/src/Controllers/HealthcheckController.php @@ -49,7 +49,7 @@ class HealthcheckController extends Controller 'Healthcheck failed: ' . $e->getMessage(), ['exception' => $e] ); - + return JsonResponseFactory::createJsonResponse( new ServerErrorResponse($e), CodesEnum::SERVICE_UNAVAILABLE diff --git a/src/Services/Facades/CommandBus.php b/src/Services/Facades/CommandBus.php new file mode 100644 index 0000000..3a2dea7 --- /dev/null +++ b/src/Services/Facades/CommandBus.php @@ -0,0 +1,27 @@ +app->singleton(CommandBus::class, function () { + return new CommandBus([ + new CommandHandlerMiddleware( + new ClassNameExtractor(), + new AttributeLocator(), + new InvokeInflector() + ), + ]); + }); + } +} diff --git a/tests/CommandBus/AttributeLocatorTest.php b/tests/CommandBus/AttributeLocatorTest.php new file mode 100644 index 0000000..2b71b30 --- /dev/null +++ b/tests/CommandBus/AttributeLocatorTest.php @@ -0,0 +1,36 @@ + ExampleHandler::class, + ]; + + public function testResolvesFiles(): void + { + $attributeLocator = new AttributeLocator(); + + foreach (self::HANDLERS as $command => $handler) { + $class = $attributeLocator->getHandlerForCommand($command); + $this->assertInstanceOf($handler, $class); + } + } + + public function testThrowsOnCannotResolve(): void + { + $attributeLocator = new AttributeLocator(); + + $this->expectException(CanNotInvokeHandlerException::class); + $attributeLocator->getHandlerForCommand('NonExistentCommand'); + } +} diff --git a/tests/CommandBus/Handlers/ExampleHandlerTest.php b/tests/CommandBus/Handlers/ExampleHandlerTest.php new file mode 100644 index 0000000..7d33bd6 --- /dev/null +++ b/tests/CommandBus/Handlers/ExampleHandlerTest.php @@ -0,0 +1,32 @@ +assertEquals('test payload', $command->getName()); + + $handler = new ExampleHandler(); + $result = $handler($command); + $this->assertEquals('Hello, test payload!', $result); + } + + public function testThrowsException(): void + { + $class = new readonly class extends Command + { + }; + + $this->expectException(\TypeError::class); + $handler = new ExampleHandler(); + $handler($class); + } +} diff --git a/tests/GrpcHandlers/GreeterHandlerTest.php b/tests/GrpcHandlers/GreeterHandlerTest.php new file mode 100644 index 0000000..f0d5a76 --- /dev/null +++ b/tests/GrpcHandlers/GreeterHandlerTest.php @@ -0,0 +1,23 @@ +setName('World'); + + $context = \Mockery::mock(ContextInterface::class); + + $handler = new \Siteworxpro\App\GrpcHandlers\GreeterHandler(); + $response = $handler->SayHello($context, $request); + $this->assertEquals('Hello World', $response->getMessage()); + } +}