thecorps/laravel-cqrs 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

thecorps/laravel-cqrs

Composer 安装命令:

composer require thecorps/laravel-cqrs

包简介

Lightweight CQRS pipeline infrastructure for Laravel applications using PHP 8 attributes

README 文档

README

🇮🇹 Leggi in italiano

Lightweight CQRS pipeline infrastructure for Laravel applications, built on PHP 8 attributes.

Provides a CommandBus and a QueryBus that route your commands and queries through a fully configurable pipeline of behaviors — logging, validation, database transactions, and handler execution — with zero boilerplate in the consuming code.

Requirements

Dependency Version
PHP ^8.2
Laravel ^11.0 or ^12.0

Installation

composer require thecorps/laravel-cqrs

Laravel's auto-discovery registers the CqrsServiceProvider and the CommandBus / QueryBus facades automatically. No manual registration needed.

Publishing the configuration file

php artisan vendor:publish --tag=cqrs-config

This creates config/cqrs.php in your application where you can customize the pipeline and logging settings.

How it works

CommandBus::dispatch(new CreateUserCommand(...))
    │
    ▼
LoggingBehavior           → logs "[CQRS] CreateUserCommand dispatched"
    │
    ▼
ValidationBehavior        → resolves #[CommandValidator] and calls validate()
    │
    ▼
TransactionBehavior       → wraps everything below in DB::transaction()
    │
    ▼
HandlerExecutionBehavior  → resolves #[CommandHandler] and calls handle()
    │
    ▼
CreateUserCommandHandler::handle(CreateUserCommand $command)

Queries follow the same pattern but skip LoggingBehavior and TransactionBehavior by default.

Usage

1 — Command

A command is an immutable DTO that implements CommandInterface and declares its handler via the #[CommandHandler] attribute.

use TheCorps\LaravelCqrs\Contracts\Interfaces\Commands\CommandInterface;
use TheCorps\LaravelCqrs\Contracts\Attributes\Commands\CommandHandler;

#[CommandHandler(CreateUserCommandHandler::class)]
final class CreateUserCommand implements CommandInterface
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
    ) {}
}

2 — Command Handler

use TheCorps\LaravelCqrs\Contracts\Interfaces\Commands\CommandHandlerInterface;

class CreateUserCommandHandler implements CommandHandlerInterface
{
    public function handle(CreateUserCommand $command): User
    {
        return User::create([
            'name'  => $command->name,
            'email' => $command->email,
        ]);
    }
}

3 — Dispatch

use TheCorps\LaravelCqrs\Facades\CommandBus;

$user = CommandBus::dispatch(new CreateUserCommand(
    name:  $request->validated('name'),
    email: $request->validated('email'),
));

4 — Query

use TheCorps\LaravelCqrs\Contracts\Interfaces\Queries\QueryInterface;
use TheCorps\LaravelCqrs\Contracts\Attributes\Queries\QueryHandler;

#[QueryHandler(GetUserQueryHandler::class)]
final class GetUserQuery implements QueryInterface
{
    public function __construct(
        public readonly int $userId,
    ) {}
}
use TheCorps\LaravelCqrs\Contracts\Interfaces\Queries\QueryHandlerInterface;

class GetUserQueryHandler implements QueryHandlerInterface
{
    public function handle(GetUserQuery $query): ?User
    {
        return User::find($query->userId);
    }
}
use TheCorps\LaravelCqrs\Facades\QueryBus;

$user = QueryBus::dispatch(new GetUserQuery(userId: $id));

5 — Validator (optional)

Validators contain domain rules that go beyond HTTP input validation. They are opt-in: without #[CommandValidator], the ValidationBehavior is a no-op.

#[CommandHandler(CreateUserCommandHandler::class)]
#[CommandValidator(CreateUserCommandValidator::class)]
final class CreateUserCommand implements CommandInterface { ... }
use TheCorps\LaravelCqrs\Contracts\Interfaces\Commands\ValidatesCommandInterface;

class CreateUserCommandValidator implements ValidatesCommandInterface
{
    public function validate(object $command): void
    {
        if (User::where('email', $command->email)->exists()) {
            throw ValidationException::withMessages([
                'email' => 'This email is already registered.',
            ]);
        }
    }
}

Configuration

After publishing, config/cqrs.php exposes three configuration areas.

Pipeline composition

'command_pipeline' => [
    LoggingBehavior::class,
    ValidationBehavior::class,
    TransactionBehavior::class,
    HandlerExecutionBehavior::class,  // must always be last
],

'query_pipeline' => [
    ValidationBehavior::class,
    HandlerExecutionBehavior::class,  // must always be last
],

You can add, remove, or reorder behaviors freely. The order in the array is the execution order.

Custom behaviors

Any class implementing PipelineBehaviorInterface can be inserted into the pipeline. Custom behaviors are resolved from the Laravel container, so they support constructor injection.

use Closure;
use TheCorps\LaravelCqrs\Contracts\Interfaces\Pipeline\PipelineBehaviorInterface;

class RateLimitingBehavior implements PipelineBehaviorInterface
{
    public function __construct(private readonly RateLimiter $limiter) {}

    public function handle(object $command, Closure $next): mixed
    {
        // your cross-cutting logic here
        return $next($command);
    }
}
// config/cqrs.php
'command_pipeline' => [
    LoggingBehavior::class,
    RateLimitingBehavior::class,   // ← inserted
    ValidationBehavior::class,
    TransactionBehavior::class,
    HandlerExecutionBehavior::class,
],

Logging

'logging' => [
    'channel' => null,     // null = default Laravel log channel; or 'daily', 'slack', etc.
    'level'   => 'debug',  // any PSR-3 level: debug, info, notice, warning, error
],

Package structure

src/
├── Contracts/
│   ├── Attributes/
│   │   ├── Commands/
│   │   │   ├── CommandHandler.php     ← #[CommandHandler(HandlerClass::class)]
│   │   │   └── CommandValidator.php   ← #[CommandValidator(ValidatorClass::class)]
│   │   └── Queries/
│   │       ├── QueryHandler.php       ← #[QueryHandler(HandlerClass::class)]
│   │       └── QueryValidator.php     ← #[QueryValidator(ValidatorClass::class)]
│   └── Interfaces/
│       ├── Commands/
│       │   ├── CommandInterface.php           ← implement on every Command DTO
│       │   ├── CommandHandlerInterface.php    ← implement on every CommandHandler
│       │   └── ValidatesCommandInterface.php  ← implement on every CommandValidator
│       ├── Queries/
│       │   ├── QueryInterface.php             ← implement on every Query DTO
│       │   ├── QueryHandlerInterface.php      ← implement on every QueryHandler
│       │   └── ValidatesQueryInterface.php    ← implement on every QueryValidator
│       └── Pipeline/
│           └── PipelineBehaviorInterface.php  ← implement for custom behaviors
├── Pipeline/
│   ├── Behaviors/
│   │   ├── LoggingBehavior.php           ← logs dispatch and completion
│   │   ├── ValidationBehavior.php        ← runs the validator if declared
│   │   ├── TransactionBehavior.php       ← wraps in DB::transaction()
│   │   └── HandlerExecutionBehavior.php  ← resolves and calls the handler
│   ├── MetadataResolver.php  ← reads PHP attributes via reflection (statically cached)
│   └── Pipeline.php          ← composes and executes the behavior chain
├── Bus/
│   ├── CommandBus.php  ← concrete bus registered in the container
│   └── QueryBus.php
├── Facades/
│   ├── CommandBus.php  ← Laravel facade
│   └── QueryBus.php
└── CqrsServiceProvider.php  ← registers buses, merges config, publishes files

Best practices

Keep Commands and Queries as immutable DTOs. Use readonly constructor properties. A command must carry exactly what it needs — no optional nullable fields that mask missing data.

Separate HTTP validation from domain validation. Use Laravel FormRequest to check input format, presence, and types. Use CommandValidator for domain rules: uniqueness, state machine transitions, cross-aggregate constraints.

FormRequest       → is the email a valid format? is the field present?
CommandValidator  → is this email already taken? can this subscription be paused?

Handlers must be thin orchestrators. Keep business logic in models, domain services, or value objects. The handler's only job is to coordinate them.

Queries must never write. If you find yourself calling save() inside a query handler, move that logic to a command.

HandlerExecutionBehavior must always be last. Any behavior placed after it will never execute.

Do not add TransactionBehavior to the query pipeline. Wrapping read-only queries in transactions is unnecessary overhead. The default configuration already reflects this.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 3
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-20

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固