lmc/cqrs-types
Lightweight CQRS type definitions for Laravel/PHP applications, offering command, query, and handler contracts that help separate write and read concerns. Keeps business actions explicit, consistent, and easy to organize across projects.
lmc/cqrs-types is the contract layer of the LMC CQRS stack: interfaces for commands, queries and handlers, plus base value objects. It ships no bus, no container wiring, no middleware — those live in the companion lmc/cqrs package. Treat this as the package you put in your require (not require-dev) when your domain layer needs to speak CQRS without depending on an implementation.
Install both packages — the types alone give you nothing runnable:
composer require lmc/cqrs-types lmc/cqrs
Read composer.json first — check the PHP constraint and the autoload namespace (typically Lmc\Cqrs\Types\). The 2024-01 release means you should verify it matches your PHP version before adopting.
Where to look next — src/ is small; skim it in five minutes. Look for:
CommandInterface, QueryInterface)CommandHandlerInterface, QueryHandlerInterface)First use case — a query handler:
final class GetUserQuery implements QueryInterface
{
public function __construct(public readonly UserId $id) {}
}
final class GetUserHandler implements QueryHandlerInterface
{
public function __construct(private UserRepository $users) {}
public function __invoke(GetUserQuery $query): User
{
return $this->users->get($query->id);
}
}
Register the handler in your container, resolve QueryBus from lmc/cqrs, and dispatch new GetUserQuery($id). If nothing dispatches, re-check you installed lmc/cqrs — this is the single most common first-hour mistake.
Keep the dependency arrow pointing inward. Domain and application code should type-hint only cqrs-types interfaces. Only the composition root (Laravel service provider, Symfony services.yaml, a plain bootstrap.php) should import concrete bus classes from lmc/cqrs. This is the whole reason to split the packages: you can swap or fake the bus without touching business code.
One handler per message, plain classes. Handlers don't need to extend anything — implement the interface and let the constructor inject collaborators. Commands mutate, queries return; don't mix the two in one class.
Value objects for IDs and scalars. Extend the provided base classes for UserId, Email, Money, etc. Validate in the constructor and throw a domain exception — that keeps invalid values from ever reaching a handler. Add equals() for comparisons and __toString() for logging.
Middleware for cross-cutting concerns. Wrap the bus (or use the middleware pipeline from lmc/cqrs) for logging, transaction boundaries, validation and metrics. Order matters: validation → transaction → handler, and logging should be outermost so it captures failures.
Testing is trivial by design. Because handlers are plain classes, unit-test them directly with mocked repositories — no bus needed. For integration tests, build a tiny in-memory bus that maps message class → handler callable, or mock the bus interface in controllers.
Framework wiring. In Laravel, bind the handler interfaces in a service provider and tag handlers; in Symfony, use !tagged_iterator for the handler locator. In both cases the bus needs a handler-locator service — check the lmc/cqrs README for the exact container contract.
Async commands. If you push commands onto a queue, keep command objects serializable: no closures, resources, or un-serializable services. Value objects need JsonSerializable or a fromString() factory if you round-trip them.
"lmc/cqrs-types": "1.2.3" style) and be prepared to fork if a PHP upgrade breaks it.cqrs-types gives you interfaces and nothing else. Every "class not found" or "no handler" error traces back to this or to a forgotten handler registration.CommandInterface has a corresponding handler — this catches 90% of wiring bugs before deploy.User for GetUserQuery". Document the expected return type in the query's docblock and, ideally, add a static-analysis baseline (PHPStan/Psalm) to enforce it.== on two VOs compares properties; use the provided equals() method if it exists, or you'll get subtle bugs with whitespace, casing or lazy-loaded internals.final are load-bearing. The base classes are likely final; if you need custom behaviour, implement the interface rather than fighting the inheritance. Prefer composition.jsonSerialize()/fromString(). The package won't magically hydrate them.cqrs-types itself, you probably want a decorator around the bus instead.How can I help you explore Laravel packages today?