A framework-agnostic PHP library for dispatching commands to handlers, with support for a configurable middleware pipeline.
This library provides a framework-agnostic command bus for PHP applications, dispatching commands to their registered handlers through a configurable middleware pipeline, with a PSR-3 compatible logging middleware included out of the box.
composer require andrewdyer/command-busCreate a command as a plain class carrying the data required to perform an operation:
class CreateUserCommand
{
public function __construct(
public readonly string $firstName,
public readonly string $lastName,
public readonly string $email,
) {}
}Create a handler with a handle() method that processes the command:
class CreateUserHandler
{
public function handle(CreateUserCommand $command): mixed
{
// Handle the command...
return $user;
}
}Create a CommandBus instance and register the handler against the command class it should handle:
use AndrewDyer\CommandBus\CommandBus;
$bus = new CommandBus();
$bus->register(CreateUserCommand::class, new CreateUserHandler());Once the command bus is configured, commands can be dispatched to their registered handlers. If no handler is registered for a given command, a HandlerNotFoundException is thrown.
$user = $bus->dispatch(new CreateUserCommand(
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
));Middleware intercepts commands before they reach the handler, allowing cross-cutting concerns such as logging, transactions, or validation to be applied consistently across all commands.
A LoggingMiddleware is included out of the box. It accepts any PSR-3 compatible logger and logs the command class name before and after dispatch:
use AndrewDyer\CommandBus\Middleware\LoggingMiddleware;
$bus->addMiddleware(new LoggingMiddleware($logger));Custom middleware defines an execute() method that receives the command and a $next closure to pass control down the pipeline:
class TransactionMiddleware
{
public function execute(object $command, \Closure $next): mixed
{
// Begin transaction...
try {
$result = $next($command);
} catch (\Throwable $e) {
// Rollback transaction...
throw $e;
}
// Commit transaction...
return $result;
}
}Middleware is executed in the order it is registered, so the first middleware added is the first to intercept the command:
$bus->addMiddleware(new TransactionMiddleware());
$bus->addMiddleware(new LoggingMiddleware($logger));Licensed under the MIT license and is free for private or commercial projects.
