Note
This is a pedagogical package. Do not use it in production environments.
Guagua is a PHP implementation of a command bus, a query bus and an event bus.
Create a command by implementing Guagua's command interface.
use Guagua\Command\Definition\CommandInterface;
class DeleteUserCommand implements CommandInterface
{
public function __construct(
public readonly string $id
) {
//
}
}
Remember to define your commands as immutable data carriers without behaviour.
See: CommandInterface.php.
Create a command handler by implementing Guagua's command handler interface and implementing the corresponding behaviour in an invoke method, which must receive the command as its argument.
use Guagua\Command\Definition\CommandHandlerInterface;
class DeleteUserCommandHandler implements CommandHandlerInterface
{
public function __construct(
private UserRepository $repository
) {
//
}
/** @param DeleteUserCommand $argument */
public function __invoke($argument): void
{
$this->repository->delete($argument->id);
}
}
Generally speaking, this will be the unique public method of your command handlers, along with the constructor which you are encouraged to use in order to inject its dependencies.
Maintain a list of all the relationships between your commands and their handlers by extending Guagua's command mapper.
use Guagua\Command\CommandMapper;
class MyCommandMapper extends CommandMapper
{
public function __construct()
{
$maps = [
DeleteUserCommand::class => DeleteUserCommandHandler::class,
];
parent::__construct($maps);
}
}
See: CommandMapper.php
Create a query by implementing Guagua's query interface.
use Guagua\Query\Definition\QueryInterface;
class GetUserQuery implements QueryInterface
{
public function __construct(
public readonly string $id
) {
//
}
}
As with commands, remember to define your queries as data carriers without behaviour.
See: QueryInterface.php
Create a query response by implementing Guagua's query response interface.
use Guagua\Query\Definition\QueryResponseInterface;
class GetUserQueryResponse implements QueryResponseInterface
{
public function __construct(
public readonly ?User $user = null
) {
//
}
}
Create a query handler by implementing Guagua's query handler interface and implementing the corresponding behaviour in an invoke method, which must receive the query as its argument.
use Guagua\Query\Definition\QueryHandlerInterface;
class GetUserQueryHandler implements QueryHandlerInterface
{
public function __construct(
private UserRepository $repository
) {
//
}
/** @param GetUserQuery $argument */
public function __invoke($argument): QueryResponseInterface;
{
$user = $this->repository->find($argument->id);
return new QueryResponse($user);
}
}
Pay attention to the return value of the method, as the main difference with the command bus is that in this case a response is mandatory and it has to implement query response interface.
Maintain a list of all the relationships between your queries and their handlers by extending Guagua's query mapper.
use Guagua\Query\QueryMapper;
class MyQueryMapper extends QueryMapper
{
public function __construct()
{
$maps = [
GetUserQuery::class => GetUserQueryHandler::class,
];
parent::__construct($maps);
}
}
See: QueryMapper.php
Create an event by implementing Guagua's event interface.
use Guagua\Event\Definition\EventInterface;
class UserCreatedEvent implements EventInterface
{
public function __construct(
public readonly string $userId
) {
//
}
}
As with commands and queries, remember to define your events as data carriers without behaviour.
See: EventInterface.php
Create an event listener by implementing Guagua's event listener interface and implementing the corresponding behaviour in an invoke method, which must receive the event as its argument.
use Guagua\Event\Definition\EventListenerInterface;
class WriteLogOnUserCreatedEvent implements EventListenerInterface
{
public function __construct(
private LogWriter $logger
) {
//
}
/** @param UserCreatedEvent $event */
public function __invoke(EventInterface $event): void
{
$this->logger->write('A new user has just been created: '.$event->userId);
}
}
Maintain a list of all the relationships between your events and their listeners by extending Guagua's event mapper.
use Guagua\Event\EventMapper;
class MyEventMapper extends EventMapper
{
public function __construct()
{
$maps = [
UserCreatedEvent::class => [
WriteLogOnUserCreatedEvent::class,
OtherActionOnUserCreatedEvent::class,
],
];
parent::__construct($maps);
}
}
See: EventMapper.php
Make the instancer use your mappers every time a service needs a command mapper, a query mapper or an event mapper by initializing it with your own implementation solver.
use Guagua\Instancer\ImplementationSolver;
use Guagua\Command\Definition\CommandMapperInterface;
use Guagua\Query\Definition\QueryMapperInterface;
use Guagua\Event\Definition\EventMapperInterface;
$solver = new ImplementationSolver([
CommandMapperInterface::class => MyCommandMapper::class,
QueryMapperInterface::class => MyQueryMapper::class,
EventMapperInterface::class => MyEventMapper::class,
]);
$instancer = new Guagua\Instancer\Instancer($solver);
Now you can use the instancer to get a command bus that knows about your commands and their handlers.
use Guagua\Command\Definition\CommandBusInterface;
$command = new DeleteUserCommand('1915d46b-e8a9-4da3-99cd-4a313c2b7b6f');
$commandBus = $instancer->get(CommandBusInterface::class);
$commandBus->dispatch($command);
Similarly, you can also use it to get a query bus that knows about your queries and their handlers.
use Guagua\Query\Definition\QueryBusInterface;
$query = new GetUserQuery('1915d46b-e8a9-4da3-99cd-4a313c2b7b6f');
$queryBus = $instancer->get(QueryBusInterface::class);
$user = $queryBus->ask($query);
Finally, you can use it to get an event bus that knows about your events and their listeners.
use Guagua\Event\Definition\EventBusInterface;
$event = new NewUserCreatedEvent('1915d46b-e8a9-4da3-99cd-4a313c2b7b6f');
$eventBus = $instancer->get(EventBusInterface::class);
$eventBus->publish($event);
See: Instancer.php
If instead you want to modify this package itself (for instance, to send a pull request), you are encouraged to clone this repository using Git.
git clone https://github.com/hawara-es/guagua.git
composer install
# optionally, you can install the package in production mode
# but you won't be able to run the test suite
composer install --no-dev
The PsySH debugger is installed as a Composer dependency, what means that you can quickly open an interactive PHP session to test a bus by running:
vendor/bin/psysh
The session will autoload the project namespaces, so you can very rapidly start having fun:
$instancer = new Guagua\Instancer\Instancer;
$instancer->get(Guagua\Command\CommandBus::class);
The Pest test engine helps tests being beautiful, readable, quick and are integrated into a GitHub workflow, so every pull request will run them.
vendor/bin/pest
The Pint code style fixer has been set up to facilitate following Laravel's suggested coding styles.
vendor/bin/pint