samir-hussein/laravel-cqrs
Composer 安装命令:
composer require samir-hussein/laravel-cqrs
包简介
A clean CQRS (Command Query Responsibility Segregation) pattern implementation for Laravel applications
README 文档
README
CQRS (Command Query Responsibility Segregation) for Laravel — separate writes (commands) from reads (queries), route both through a predictable pipeline (validation → middleware → handler), and resolve handlers automatically from naming conventions or explicit mappings.
This package gives you a small, opinionated surface area: two buses, one helper, convention-based handler resolution, optional Laravel validation on messages, and a middleware pipeline for cross-cutting behavior — without pulling in event sourcing or heavy infrastructure.
Table of contents
- Why use this package
- What you get
- Requirements
- Installation
- Concepts
- Quick start
- Handler resolution
- The
CQRShelper - Command and query base classes
- Validation
- Middleware pipeline
- Using the buses directly
- Automatic request data (controllers)
- Configuration reference
- Artisan commands
- Package architecture
- Exceptions
- License
- Changelog
Why use this package
- Clear boundaries: Commands change state; queries return data. That separation scales with team size and keeps controllers thin.
- One entry point:
CQRS::dispatch()works for both commands and queries — validate once, then run the pipeline. - Laravel-native: Uses the container, config, validator, and optional controller injection of
Command/Queryinstances. - Extensible without clutter: Global and per-message middleware for logging, DB transactions, authorization, caching reads, etc.
- Flexible routing to handlers: Convention by default; override specific handlers via config when names or folders do not match.
What you get
| Area | Details |
|---|---|
| Core types | Command, Query base classes with data access and validation hooks |
| Buses | CommandBus, QueryBus (singletons) resolving handlers and running the pipeline |
| Facade-style API | LaravelCQRS\CQRS static methods for dispatch with/without validation |
| Contracts | CommandHandlerInterface, QueryHandlerInterface, MiddlewareInterface |
| Pipeline | Pipeline executes middleware; middleware may be classes or container-resolved class names |
| Config | Namespaces, auto-resolve toggle, handler mappings, middleware stacks |
| Generators | cqrs:command, cqrs:query, cqrs:handler, cqrs:middleware |
| Exceptions | HandlerNotFoundException, InvalidHandlerException |
Requirements
- PHP ≥ 8.0
- Laravel ≥ 9 (
illuminate/*packages as declared incomposer.json)
Installation
composer require samir-hussein/laravel-cqrs
Laravel will auto-discover LaravelCQRS\CQRSServiceProvider.
Publish configuration (recommended for production apps):
php artisan vendor:publish --tag=cqrs-config
This creates config/cqrs.php.
Concepts
- Command: An intent to change application state (create user, place order). Typically handled once, side effects allowed.
- Query: A read model request (get user by id, list products). No state change in the CQRS sense; return a result.
- Handler: A class that implements
CommandHandlerInterfaceorQueryHandlerInterfaceand contains the use-case logic. - Flow:
Controller → CQRS::dispatch() → [validate] → CommandBus/QueryBus → middleware pipeline → handler.handle() → result
HTTP Request
→ Controller
→ CQRS::dispatch($message) // validates if rules() non-empty
→ Bus
→ Middleware (global, then per-message)
→ Handler::handle($message)
→ Domain / repositories / models
→ mixed result
Quick start
1. Command + handler
Generate files:
php artisan cqrs:command User/CreateUserCommand php artisan cqrs:handler User/CreateUserCommandHandler --type=command
Command (app/CQRS/Commands/User/CreateUserCommand.php):
<?php namespace App\CQRS\Commands\User; use LaravelCQRS\Command; class CreateUserCommand extends Command { public function rules(): array { return [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'email'], ]; } }
Handler (app/CQRS/Handlers/User/CreateUserCommandHandler.php):
<?php namespace App\CQRS\Handlers\User; use App\CQRS\Commands\User\CreateUserCommand; use LaravelCQRS\Command; use LaravelCQRS\Contracts\CommandHandlerInterface; class CreateUserCommandHandler implements CommandHandlerInterface { public function handle(Command $command): mixed { /** @var CreateUserCommand $command */ return $command->getData(); // replace with real persistence } }
Controller:
use App\CQRS\Commands\User\CreateUserCommand; use LaravelCQRS\CQRS; $user = CQRS::dispatch(new CreateUserCommand($request->all()));
2. Query + handler
php artisan cqrs:query User/GetUserQuery php artisan cqrs:handler User/GetUserQueryHandler --type=query
Dispatch the same way:
use App\CQRS\Queries\User\GetUserQuery; use LaravelCQRS\CQRS; $user = CQRS::dispatch(new GetUserQuery(['id' => $id]));
Handler resolution
Resolution order:
config('cqrs.handler_mappings')— if the command/query class is a key, that handler class is used.- If no mapping and
auto_resolve_handlersisfalse, aHandlerNotFoundExceptionis thrown. - Otherwise auto-resolution uses
command_namespace,query_namespace, andhandler_namespacefrom config:App\CQRS\Commands\User\CreateUserCommand→App\CQRS\Handlers\User\CreateUserCommandHandlerApp\CQRS\Queries\User\GetUserQuery→App\CQRS\Handlers\User\GetUserQueryHandler
- If namespaces do not match, the bus falls back to string replacement (
Commands→Handlers,Command→CommandHandler, etc.).
Mappings always win — useful for legacy handlers or one-off overrides.
The CQRS helper
All methods live on LaravelCQRS\CQRS.
| Method | Purpose |
|---|---|
dispatch(Command|Query $m, bool $validate = true) |
Validates (if $validate and rules() not empty), then routes to CommandBus or QueryBus. |
dispatchWithoutValidation(Command|Query $m) |
Same as dispatch($m, false). |
dispatchCommand(Command $c, bool $validate = true) |
Command only. |
dispatchCommandWithoutValidation(Command $c) |
Command, no validation. |
dispatchQuery(Query $q, bool $validate = true) |
Query only. |
dispatchQueryWithoutValidation(Query $q) |
Query, no validation. |
Validation uses Laravel’s validator; failures throw Illuminate\Validation\ValidationException.
Command and query base classes
LaravelCQRS\Command and LaravelCQRS\Query share the same data and validation API.
Data:
| Method | Description |
|---|---|
getData(): array |
Full payload. |
get(string $key, mixed $default = null): mixed |
Single value. |
set(string $key, mixed $value): self |
Mutable payload (e.g. from middleware). |
has(string $key): bool |
Key isset. |
toObject(): object |
Recursive array → object (indexed arrays preserved as arrays). |
Validation (override in subclasses):
| Method | Default |
|---|---|
rules(): array |
[] — no rules means validate() returns raw data unchanged. |
messages(): array |
Custom rule messages. |
attributes(): array |
Custom attribute names for errors. |
Execution:
| Method | Description |
|---|---|
validate(): array |
Runs validator; throws ValidationException on failure; returns validated data. |
isValid(): bool |
Non-throwing check. |
errors(): MessageBag |
Errors without throwing. |
Validation
Recommended: validate through CQRS::dispatch()
Define rules() (and optionally messages(), attributes()) on the command or query. Call:
CQRS::dispatch(new CreateUserCommand($request->all()));
Manual or layered checks
$command = new CreateUserCommand($request->all()); if (! $command->isValid()) { return response()->json(['errors' => $command->errors()], 422); } $validated = $command->validate(); // or dispatch after manual validate()
Validate inside a handler
public function handle(Command $command): mixed { $data = $command->validate(); // use $data }
With Laravel Form Requests
You can validate at the HTTP layer and pass only validated data into the command:
public function store(CreateUserRequest $request): JsonResponse { $user = CQRS::dispatch(new CreateUserCommand($request->validated())); return response()->json($user, 201); }
Use Form Requests for HTTP-specific rules; use command/query rules() for message invariants that belong with the use case.
Middleware pipeline
Middleware wraps handler execution: global middleware first (in order), then middleware listed for that command/query class (in order). Implementation: LaravelCQRS\Pipeline.
Interface
use LaravelCQRS\Command; use LaravelCQRS\Contracts\MiddlewareInterface; use LaravelCQRS\Query; use Closure; class LoggingMiddleware implements MiddlewareInterface { public function handle(Command|Query $commandOrQuery, Closure $next): mixed { logger()->info(get_class($commandOrQuery), $commandOrQuery->getData()); return $next($commandOrQuery); } }
Config accepts class names; the pipeline resolves them from the container. Instances may also be used if your stack provides them.
Example: database transaction (commands only)
use Illuminate\Support\Facades\DB; public function handle(Command|Query $commandOrQuery, Closure $next): mixed { if ($commandOrQuery instanceof Command) { return DB::transaction(fn () => $next($commandOrQuery)); } return $next($commandOrQuery); }
Example: cache key from payload
Use getData() for serialization (there is no toArray() on the base classes):
$key = 'cqrs:' . get_class($commandOrQuery) . ':' . md5(json_encode($commandOrQuery->getData()));
Configuration
// config/cqrs.php 'middleware' => [ 'global' => [ \App\CQRS\Middleware\LoggingMiddleware::class, ], \App\CQRS\Commands\User\CreateUserCommand::class => [ \App\CQRS\Middleware\TransactionMiddleware::class, ], ],
Execution order: global middleware → per-message middleware → handler.
Using the buses directly
Inject LaravelCQRS\Bus\CommandBus or LaravelCQRS\Bus\QueryBus. They do not run CQRS::dispatch() validation — validate yourself if needed:
use LaravelCQRS\Bus\CommandBus; public function __construct(private CommandBus $commandBus) {} public function store(Request $request): JsonResponse { $command = new CreateUserCommand($request->all()); $command->validate(); $result = $this->commandBus->dispatch($command); return response()->json($result, 201); }
Automatic request data (controllers)
When a Command or Query is type-hinted in a controller action and its internal data is still empty, the service provider merges the current request’s input and route parameters into the object. That lets you write:
public function store(CreateUserCommand $command): JsonResponse { return response()->json(CQRS::dispatch($command), 201); }
If you construct the message manually with an array, that behavior is skipped. In console or when no request exists, injection is safely ignored.
Configuration reference
Environment keys mirror config/cqrs.php:
| Key | Purpose |
|---|---|
handler_namespace |
Base namespace for handler classes (App\CQRS\Handlers). |
command_namespace |
Base namespace for commands (App\CQRS\Commands). |
query_namespace |
Base namespace for queries (App\CQRS\Queries). |
middleware_namespace |
Used by cqrs:middleware for default output namespace (App\CQRS\Middleware). |
auto_resolve_handlers |
true: resolve by convention/mapping; false: mappings required (or exception). |
handler_mappings |
['Full\\Command\\Class' => 'Full\\Handler\\Class', ...] — highest priority. |
middleware |
global array + optional keys per fully qualified command/query class name. |
Example .env overrides:
CQRS_HANDLER_NAMESPACE=App\CQRS\Handlers CQRS_COMMAND_NAMESPACE=App\CQRS\Commands CQRS_QUERY_NAMESPACE=App\CQRS\Queries CQRS_MIDDLEWARE_NAMESPACE=App\CQRS\Middleware CQRS_AUTO_RESOLVE=true
Artisan commands
| Command | Generates |
|---|---|
php artisan cqrs:command User/CreateUserCommand |
app/CQRS/Commands/User/CreateUserCommand.php |
php artisan cqrs:query User/GetUserQuery |
app/CQRS/Queries/User/GetUserQuery.php |
php artisan cqrs:handler User/CreateUserCommandHandler --type=command |
Command handler implementing CommandHandlerInterface |
php artisan cqrs:handler User/GetUserQueryHandler --type=query |
Query handler implementing QueryHandlerInterface |
php artisan cqrs:middleware User/TransactionMiddleware |
Middleware implementing MiddlewareInterface |
Namespaces follow config/cqrs.php (handler_namespace, etc.).
Package architecture
src/
├── Bus/
│ ├── AbstractBus.php # Handler resolution, middleware collection, pipeline execution
│ ├── CommandBus.php
│ └── QueryBus.php
├── Console/ # make:* commands + stubs
├── Contracts/
│ ├── CommandHandlerInterface.php
│ ├── QueryHandlerInterface.php
│ └── MiddlewareInterface.php
├── Exceptions/
│ ├── HandlerNotFoundException.php
│ └── InvalidHandlerException.php
├── Command.php
├── Query.php
├── CQRS.php # Static dispatch helpers
├── Pipeline.php # Middleware stack
└── CQRSServiceProvider.php
config/
└── cqrs.php
Namespaces in your app (typical):
app/CQRS/
├── Commands/...
├── Queries/...
├── Handlers/...
└── Middleware/...
Exceptions
| Exception | When |
|---|---|
HandlerNotFoundException |
Handler class missing, or auto-resolve off with no mapping. |
InvalidHandlerException |
Resolved class does not implement the expected handler interface. |
Illuminate\Validation\ValidationException |
Failed validate() or CQRS::dispatch(..., true). |
License
MIT. See the LICENSE file in the repository.
Changelog
1.0.0
- Initial release:
Command/Querywith Laravel validation integration. CommandBus/QueryBuswith configurable handler resolution and handler mappings.CQRShelper for unified dispatch with optional validation.- Middleware pipeline (
Pipeline,MiddlewareInterface) with global and per-message config. HandlerNotFoundException,InvalidHandlerException.- Service provider: singleton buses, controller injection for empty commands/queries, Artisan generators.
- Configuration: namespaces,
auto_resolve_handlers,handler_mappings,middleware.
Future versions will be listed here following Semantic Versioning and Keep a Changelog principles.
samir-hussein/laravel-cqrs 适用场景与选型建议
samir-hussein/laravel-cqrs 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 15 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 02 月 20 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「pattern」 「command」 「query」 「laravel」 「cqrs」 「architecture」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 samir-hussein/laravel-cqrs 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 samir-hussein/laravel-cqrs 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 samir-hussein/laravel-cqrs 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
A Wrapper for exec and it's results
Symfony bundle to monitor and execute commands
Shell command module for PHP.
Query filtering in your frontend
Anax Database Active Record module for model classes.
Library with classes to help you do batch.
统计信息
- 总下载量: 15
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 22
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-02-20