remotelyliving / php-command-bus
A php query bus for abstracting querying, data loading, and graph building
Requires
- php: >=7.4
- ext-filter: *
- ext-json: *
- ext-mbstring: *
- myclabs/php-enum: ^1.7
- psr/container: ^1.0
- psr/event-dispatcher: ^1.0
- psr/log: ^1.0
Requires (Dev)
- maglnet/composer-require-checker: ^2.0
- php-coveralls/php-coveralls: ^2.2
- phpstan/phpstan: ^0.12.19
- phpunit/phpunit: ^9.0
- squizlabs/php_codesniffer: ^3.3
- vimeo/psalm: ^3.7
This package is auto-updated.
Last update: 2024-10-19 22:36:39 UTC
README
php-command-bus: 🚍 A Command Bus Implementation For PHP 🚍
Use Cases
If you want a light weight compliment to your Query Bus for CQRS, hopefully this library helps out.
Installation
composer require remotelyliving/php-command-bus
Usage
Create the Command Resolver
The Resolver can have Handlers added manually or locate them in a PSR-11 Service Container Commands are mapped 1:1 with a handler and are mapped by the Command class name as the lookup key.
$resolver = Resolver::create($serviceContainer) // can locate in service container ->pushHandler(Commands\ReserveRoom::class, new Handlers\ReserveRoom()) // can locate in a local map {command => handler} ->pushHandlerDeferred(Commands\Checkout::class, fn() => new Handlers\Checkout()); // can locate deferred to save un unnecessary object instantiation
Create the Command Bus
The Command Bus takes in a Command Resolver and optional PSR-14 Event Dispatcher and pushes whatever Middleware you want on the stack.
$resolver = Resolver::create($container); $commandBus = CommandBus::create($resolver, $psr14EventDispatcher | null) ->pushMiddleware($myMiddleware1); $command = new Commands\ReserveRoom(123); $commandBus->handle($command);
Middleware is any callable. Some base middleware is included: src/Middleware
That's really all there is to it!
Command
The Command for this library is left intentionally unimplemented. It's just an object. My suggestion for Command objects is to keep them as a DTO of what you need to perform a command.
An example command might look like this:
class ReserveRoom { private User $user; private Room $room; public function __construct(User $user, Room $room) { $this->user = $user; $this->room = $room; } public function getUser(): User { return $this->user; } public function getRoom(): Room { return $this->room; } }
As you can see, it's just a few getters
Handler
The Handlers are where the magic happens.
- Inject what ever Domain Services you need to perform your command in the contstuctor.
- Perform the logic for the command.
- Yield Domain Events that will be automatically dispatched by the PSR14 Dispatcher you pass in to the Bus (When calling a subsequent command from within the handler, make sure your events don't get out of order)
- Execute other commands from within the handler
Going with our ReserveRoom example, a Handler could look like:
class ReserveRoom implements Interfaces\Handler { public function handle(object $command, Interfaces\CommandBus $bus) { $bus->handle(new Commands\MarkRoomAsReserved($command->getRoom())); $bus->handle(new Commands\CompleteInvoiceForRoom($command->getUser(), $command->getRoom())); yield new Events\RoomWasReserved(); } }
Middleware
Middleware that this library ships with. The default execution order is LIFO and the signature very simple.
public function __invoke(Interfaces\Command $command, callable $next);
A Middleware must pass the command to the $next callable and execute it to continue the execution of the Command.
CommandLogger
Helpful for debugging, but best left for dev and stage environments.