Dispatching Commands#
Commands represent intentions to change the system’s state. Unlike events that describe what happened, commands express what should happen. They carry the information needed to make a business decision and are the entry points for all write operations.
Commands are dispatched through a Dispatcher that routes them to their corresponding handlers. Command handlers
coordinate whatever operations are necessary to fulfill the command’s intent.
Defining commands#
Commands are simple data transfer objects with no behavior. They don’t need to implement any interface; they’re just plain PHP classes that carry data:
| |
Mark commands as readonly to emphasize their immutability. Commands should never change once created.
Naming commands#
Command names should clearly express intent using imperative verbs:
| |
Creating command handlers#
Command handlers implement HandlerInterface:
| |
Use HandleCommandTrait to automatically route commands to methods based on class names:
| |
The trait looks for methods named by prefixing handle to the command’s short class name.
The command handler workflow#
A common pattern for command handlers working with models follows these steps:
- Build the query to load relevant events
- Load the model from the Repository
- Execute business logic by calling decision methods
- Persist changes back to the Repository
| |
The Repository handles event replay, concurrency checks, event persistence, and event publishing. Command handlers coordinate operations.
However, command handlers are not limited to working with models. They can perform any operation needed to fulfill the command’s intent. For example, a handler might call external services, send notifications, or perform system operations:
| |
The key is that handlers encapsulate the logic needed to execute a command, whether that involves models, external services, or any other operations.
Registering handlers#
Register command handlers with the Dispatcher during application bootstrap:
| |
Adding middleware#
Middleware wraps command dispatch to add cross-cutting concerns like logging, validation, or transactions:
| |
Middleware executes in LIFO order. The last registered middleware wraps all previous ones.
Best practices#
Keep commands simple. Commands should be data containers with no logic. All validation and business rules belong in models.
Use descriptive command names. Command names should clearly express intent using action verbs.
Group related commands. Commands that share dependencies or operate on related concepts can share a handler.
Handle each command once. Each command class should map to exactly one handler method.
Let exceptions propagate. Domain exceptions should propagate to the dispatcher where middleware can handle them consistently.
Keep handlers focused. Command handlers should coordinate operations, not implement business logic. Business rules belong in models.
Make commands serializable. Commands should only contain scalar types and arrays so they can be serialized for queuing or logging.