PHPackages                             thecorps/laravel-cqrs - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. thecorps/laravel-cqrs

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

thecorps/laravel-cqrs
=====================

Lightweight CQRS pipeline infrastructure for Laravel applications using PHP 8 attributes

v1.0.0(today)111↑2900%MITPHPPHP ^8.2

Since Jun 20Pushed todayCompare

[ Source](https://github.com/PriviteraGabriele/laravel-cqrs)[ Packagist](https://packagist.org/packages/thecorps/laravel-cqrs)[ RSS](/packages/thecorps-laravel-cqrs/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (3)Versions (2)Used By (0)

thecorps/laravel-cqrs
=====================

[](#thecorpslaravel-cqrs)

> [🇮🇹 Leggi in italiano](README.it.md)

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
------------

[](#requirements)

DependencyVersionPHP`^8.2`Laravel`^11.0`, `^12.0` or `^13.0`---

Installation
------------

[](#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

[](#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
------------

[](#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
-----

[](#usage)

### 1 — Command

[](#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

[](#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

[](#3--dispatch)

```
use TheCorps\LaravelCqrs\Facades\CommandBus;

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

---

### 4 — Query

[](#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)

[](#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.',
            ]);
        }
    }
}
```

---

### 6 — Transactions

[](#6--transactions)

`TransactionBehavior` is included in the default command pipeline. You do not need to do anything: every command is automatically wrapped in a `DB::transaction()`. If any exception is thrown — whether from the validator, the handler, or any downstream behavior — the entire transaction is rolled back.

```
class TransferFundsCommandHandler implements CommandHandlerInterface
{
    public function handle(TransferFundsCommand $command): void
    {
        // Both writes run inside the same DB transaction.
        // If the second one throws, the first is rolled back automatically.
        Account::find($command->fromId)->decrement('balance', $command->amount);
        Account::find($command->toId)->increment('balance', $command->amount);
    }
}
```

To disable automatic transactions, remove `TransactionBehavior` from `command_pipeline` in your published `config/cqrs.php`:

```
'command_pipeline' => [
    LoggingBehavior::class,
    ValidationBehavior::class,
    // TransactionBehavior::class — removed
    HandlerExecutionBehavior::class,
],
```

> **Note:** Never add `TransactionBehavior` to the query pipeline. Read-only queries do not need transactions.

---

### 7 — Logging

[](#7--logging)

`LoggingBehavior` is included in the default command pipeline. It writes two log entries for every command dispatched: one at dispatch time and one on completion.

```
[CQRS] CreateUserCommand dispatched
[CQRS] CreateUserCommand completed

```

By default it uses the application's default log channel at `debug` level. Both settings are configurable:

```
// config/cqrs.php
'logging' => [
    'channel' => 'daily',   // write to a named Laravel log channel
    'level'   => 'info',    // any PSR-3 level: debug, info, notice, warning, error
],
```

To disable logging entirely, remove `LoggingBehavior` from `command_pipeline`:

```
'command_pipeline' => [
    // LoggingBehavior::class — removed
    ValidationBehavior::class,
    TransactionBehavior::class,
    HandlerExecutionBehavior::class,
],
```

---

Configuration
-------------

[](#configuration)

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

### Pipeline composition

[](#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

[](#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)

```
'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
-----------------

[](#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
--------------

[](#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.

---

License
-------

[](#license)

This package is open-source software released under the [MIT License](LICENSE).

You are free to use, modify, and distribute it in both private and commercial projects. The only requirement is to keep the copyright notice in any copies or substantial portions of the software.

###  Health Score

42

—

FairBetter than 89% of packages

Maintenance100

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/102214867?v=4)[Gabriele Privitera](/maintainers/PriviteraGabriele)[@PriviteraGabriele](https://github.com/PriviteraGabriele)

---

Top Contributors

[![PriviteraGabriele](https://avatars.githubusercontent.com/u/102214867?v=4)](https://github.com/PriviteraGabriele "PriviteraGabriele (4 commits)")

### Embed Badge

![Health badge](/badges/thecorps-laravel-cqrs/health.svg)

```
[![Health](https://phpackages.com/badges/thecorps-laravel-cqrs/health.svg)](https://phpackages.com/packages/thecorps-laravel-cqrs)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[wearepixel/laravel-cart

A cart implementation for Laravel

1355.6k](/packages/wearepixel-laravel-cart)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
