PHPackages                             zolta/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. [Framework](/categories/framework)
4. /
5. zolta/cqrs

ActiveLibrary[Framework](/categories/framework)

zolta/cqrs
==========

CQRS application layer for PHP 8.2+ with command/query buses, decorator-based middleware pipeline, Result/Option monads, repository abstractions with caching, transaction management, domain event dispatching, and automatic handler discovery via attributes.

v1.0.2(1mo ago)19↓100%MITPHPPHP ^8.2

Since Apr 25Pushed 1mo agoCompare

[ Source](https://github.com/zoltasoft/cqrs)[ Packagist](https://packagist.org/packages/zolta/cqrs)[ RSS](/packages/zolta-cqrs/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (14)Versions (5)Used By (0)

Zolta CQRS
==========

[](#zolta-cqrs)

[![PHP Version](https://camo.githubusercontent.com/5fe05c705bf034839bda7651781e4d0a9d42f4a840478ca5e343873a0361bb89/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322b2d626c75652e737667)](https://php.net/)[![PHPStan Level](https://camo.githubusercontent.com/06dd4b884a37ff91769f1a4da178490f3e883cb4cc0efa500ef129fe5a25f7b4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d4c6576656c253230362d627269676874677265656e2e737667)](https://phpstan.org/)[![Laravel Version](https://camo.githubusercontent.com/3f3872fde8203e931d8bb8abb1d9b2af03b3e3f27257cd7211b1f023da6ba87b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31302b2d7265642e737667)](https://laravel.com/)[![License](https://camo.githubusercontent.com/784362b26e4b3546254f1893e778ba64616e362bd6ac791991d2c9e880a3a64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e737667)](LICENSE)

**CQRS that fits in your stack, not the other way around.**

A complete application layer for PHP 8.2+: command/query buses with a decorator pipeline, `Result`/`Option` monads for predictable error handling, transactional orchestration with automatic event dispatching, repository abstractions with caching, and automatic handler discovery via PHP 8 attributes. No event sourcing required — but nothing stopping you if you want it.

```
$result = $cqrs->dispatch(new CreateUserCommand(
    name: 'John',
    email: 'john@example.com',
    password: 'secret123',
));
// Validated → executed → events dispatched → transaction committed. One call.
```

---

Why Zolta CQRS?
---------------

[](#why-zolta-cqrs)

### The problem

[](#the-problem)

Laravel gives you Eloquent, queues, and events — excellent infrastructure. But the **application architecture layer** between "HTTP request" and "database query" is left as a DIY exercise. Most teams end up with fat controllers, service classes that mix concerns, and event handling scattered across listeners. Testing is painful because business logic is tangled with framework code.

### What Zolta CQRS does differently

[](#what-zolta-cqrs-does-differently)

ApproachHow it worksTrade-offEcotoneFull messaging framework with aggregates, projections, sagasHeavyweight, steep learning curve, all-or-nothingBroadwayEvent sourcing toolkitRequires event sourcing commitmentSpatie Event SourcingLaravel event sourcingEvent-sourcing only, no command/query separationTacticianSimple command busCommand-only, no queries, no Result monads, no orchestration**Zolta CQRS****Decorator-based buses + monads + Application Service orchestration****Pragmatic CQRS without event sourcing tax**Zolta CQRS occupies a pragmatic middle ground: you get clean command/query separation, type-safe results, automatic event dispatching, and transactional orchestration — without being forced into full event sourcing. Use as much or as little as your project needs.

### Who is this for?

[](#who-is-this-for)

- Teams who want **command/query separation** without rewriting their entire architecture
- Projects that need **multi-step transactional workflows** (registration, checkout, onboarding) with automatic rollback
- Developers who want **predictable error handling** without try/catch pyramids
- Applications that will **eventually need** event sourcing or queued commands, but not today

---

Install
-------

[](#install)

```
composer require zolta/cqrs
```

Laravel auto-discovers the service provider. No manual registration needed.

### Publish configuration

[](#publish-configuration)

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

This creates `config/zolta.php` with paths to scan for handlers:

```
return [
    'commands' => [app_path('Application/Commands')],
    'queries'  => [app_path('Application/Queries')],
    'events'   => [app_path('Infrastructure/Events')],
];
```

---

Quick Start
-----------

[](#quick-start)

### 1. Define a command

[](#1-define-a-command)

```
use Zolta\Cqrs\Commands\Command;

class CreateUserCommand extends Command
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly string $password,
    ) {}
}
```

### 2. Create a handler

[](#2-create-a-handler)

```
use Zolta\Cqrs\Attributes\HandlesCommand;
use Zolta\Cqrs\Services\Result;

#[HandlesCommand(CreateUserCommand::class)]
class CreateUserHandler
{
    public function __construct(
        private readonly UserRepositoryInterface $repository,
    ) {}

    public function __invoke(CreateUserCommand $command): Result
    {
        $user = User::create(
            id: UserId::generate(),
            name: Username::resolve(['value' => $command->name]),
            email: Email::resolve(['address' => $command->email]),
            password: HashedPassword::fromPlaintext($command->password),
        );

        $this->repository->save($user);

        return Result::success(
            value: $user->toArray(),
            events: $user->releaseEvents(),
        );
    }
}
```

### 3. Add validation (optional)

[](#3-add-validation-optional)

```
use Zolta\Cqrs\Attributes\ValidatesCommand;

#[ValidatesCommand(CreateUserCommand::class)]
class CreateUserValidator
{
    public function validate(CreateUserCommand $command): void
    {
        if ($this->repository->findByEmail($command->email)) {
            throw new ValidationException(['email' => 'Already registered.']);
        }
    }
}
```

### 4. Dispatch

[](#4-dispatch)

```
$result = $cqrs->dispatch(new CreateUserCommand(
    name: 'John',
    email: 'john@example.com',
    password: 'secret123',
));

$userId = $result->getValue()['id'];
```

### 5. Query data

[](#5-query-data)

```
use Zolta\Cqrs\Queries\Query;
use Zolta\Cqrs\Attributes\HandlesQuery;
use Zolta\Cqrs\Services\Option;

class GetUserQuery extends Query
{
    public function __construct(public readonly string $userId) {}
}

#[HandlesQuery(GetUserQuery::class)]
class GetUserHandler
{
    public function __invoke(GetUserQuery $query): Option
    {
        $user = $this->repository->findById($query->userId);
        return $user ? Option::some($user->toArray()) : Option::none();
    }
}

$option = $cqrs->ask(new GetUserQuery(userId: '123'));
$data = $option->getOrFail(fn() => new NotFoundException('User not found'));
```

### 6. Orchestrate with ApplicationService

[](#6-orchestrate-with-applicationservice)

```
class RegistrationService
{
    public function __construct(private ApplicationService $appService) {}

    public function register(string $name, string $email, string $password): array
    {
        return $this->appService->transactional(function () use ($name, $email, $password) {
            $this->appService->runAndCapture(new CreateUserCommand($name, $email, $password));

            $this->appService->cqrs()->dispatch(new AssignRoleCommand(
                userId: new MapPlaceholder('createUser.id'),
                role: 'user',
            ));

            return $this->appService->response([
                'id' => 'createUser.id',
                'name' => 'createUser.name',
                'email' => 'createUser.email',
            ]);
        });
    }
}
```

---

Architecture
------------

[](#architecture)

### The command bus decorator chain

[](#the-command-bus-decorator-chain)

Commands flow through a composable decorator pipeline — each layer adds one concern:

```
WorkerAwareRoutingCommandBus
├─ ShouldQueue? → QueuedCommandBus → ExecuteCommandJob (async)
└─ Sync path:
   EventDispatchingCommandBus          ← dispatches domain events post-success
   └─ ValidatingCommandBus             ← runs #[ValidatesCommand] validators
      └─ SynchronousCommandBus         ← resolves handler, injects dependencies, executes

```

Every decorator is optional. Need just sync dispatch? Use `SynchronousCommandBus` directly. Need validation without events? Stack only what you need. The `WorkerAwareRoutingCommandBus` detects worker context to prevent re-enqueue loops automatically.

### Result &amp; Option monads

[](#result--option-monads)

Commands return `Result`, queries return `Option` — no more guessing what a method returns:

```
// Result: success or failure, always carrying domain events
$result = Result::success(value: $user->toArray(), events: $user->releaseEvents());
$result = Result::failure(new DomainException('Email taken'));

$result->isSuccess();           // bool
$result->getValue();            // mixed — the success value
$result->getError();            // Throwable — the failure
$result->getEvents();           // EventInterface[] — extracted post-commit

// Option: some, none, or error — null-safe query results
$option = Option::some(['id' => '123', 'name' => 'John']);
$option = Option::none();

$option->getOrElse(['fallback']);
$option->getOrFail(fn() => new NotFoundException('User not found'));
```

No more returning `null | array | false | throw` from service methods. The type tells you what happened.

### ApplicationService orchestration

[](#applicationservice-orchestration)

Multi-command workflows with automatic transactions, result capturing, and response mapping:

```
return $this->appService->transactional(function () use ($name, $email, $password) {
    // Each command result is captured with a key
    $this->appService->runAndCapture(new CreateUserCommand($name, $email, $password));

    // Reference earlier results via MapPlaceholder
    $this->appService->cqrs()->dispatch(new AssignRoleCommand(
        userId: new MapPlaceholder('createUser.id'),
        role: 'user',
    ));

    // Build response from captured values across commands
    return $this->appService->response([
        'id'    => 'createUser.id',
        'name'  => 'createUser.name',
        'email' => 'createUser.email',
    ]);
});
// If any command fails → auto-rollback. Events dispatch only on commit.
```

### Repository framework

[](#repository-framework)

Framework-agnostic repositories with 12 filter operators, relation loading, pagination, sorting, field selection, and namespace-scoped caching:

```
class UserRepository extends EloquentBaseRepository
{
    protected array $allowedFilters = ['name', 'email', 'role_id'];
    protected array $allowedRelations = ['role', 'permissions'];
    protected array $filterableRelations = ['role' => ['name']];

    // Built-in operators: eq, ne, gt, gte, lt, lte, like, not_like,
    //                     in, not_in, null, not_null, between
}
```

Cache layer uses tagged keys with configurable TTL — `RepositoryCache` interface with Laravel, APCu, or null implementations.

### Message hydration

[](#message-hydration)

Automatic construction of Commands, Queries, and Value Objects from raw arrays — no manual `new` calls:

```
$command = $cqrs->make(CreateUserCommand::class, [
    'name' => 'John',
    'email' => 'john@example.com',
    'password' => 'secret123',
]);
// Reflection-cached, type-aware, handles nested VOs via Forge integration
```

---

Performance
-----------

[](#performance)

Benchmarked on a real application (Laravel 12, PHP 8.3, SQLite):

ComponentTime (warm)CommandBus dispatch overhead&lt; 1msQueryBus ask overhead&lt; 1msApplicationService wrapping&lt; 2msMessage hydration (cached class)&lt; 0.6msEvent dispatching&lt; 1ms**Total CQRS overhead per request****&lt; 5ms**The dominant costs in any request are your application logic — database queries, bcrypt hashing, external API calls. The CQRS layer stays invisible.

---

Features at a glance
--------------------

[](#features-at-a-glance)

FeatureDetails**Command bus**5-layer decorator chain: sync → validating → event-dispatching → queued → worker-aware**Query bus**In-memory with automatic handler resolution and dependency injection**Result monad**`success(value, events)` / `failure(error, events)` with event accumulation**Option monad**`some(values)` / `none()` / `error(throwable)` — null-safe queries**ApplicationService**Transactional orchestration, capture store, placeholder resolution, response mapping**Handler discovery**`#[HandlesCommand]` · `#[HandlesQuery]` · `#[ValidatesCommand]` · `#[HandlesDomainEvent]`**Argument resolution**Container injection + command/query type matching + variadic support**Message hydration**Reflection-cached construction from arrays, nested VO support via Forge**Repository**Abstract base + Eloquent impl with 12 filter operators, caching, pagination, sorting**Transactions**Auto-commit on `Result::success`, auto-rollback on `Result::failure`**Domain events**Aggregates record → Results carry → bus dispatches post-commit**Queue integration**`ShouldQueue` marker → automatic defer via `ExecuteCommandJob`**Framework agnostic**PSR-11 core, Laravel adapter with 13 service providers---

Part of the Zolta Ecosystem
---------------------------

[](#part-of-the-zolta-ecosystem)

Zolta CQRS is the **application layer** — it bridges domain logic and transport:

```
┌─────────────────────────────────────────────┐
│  zolta/http (Transport)                     │
│  Attribute-driven routing & response        │
├─────────────────────────────────────────────┤
│  zolta/cqrs (Application) ← you are here   │
│  Commands, queries, events, transactions    │
├─────────────────────────────────────────────┤
│  zolta/forge (Domain)                       │
│  Value Objects, rules, specs, entities      │
└─────────────────────────────────────────────┘

```

When used together: **HTTP** resolves the pipeline via attributes → **Forge** hydrates the command with validated VOs → **CQRS** dispatches through the bus, captures events, wraps transactions → **HTTP** transforms and returns the response. Sub-10ms package overhead for the entire vertical stack.

PackageLayerLinkzolta/forgeDomain[`packages/forge`](../zolta-forge)**zolta/cqrs****Application**You are herezolta/httpTransport[`packages/http`](../zolta-http)---

QA
--

[](#qa)

```
composer run qa          # Full suite: lint + analyse + phpmd + rector + test
composer run test        # PHPUnit only
```

**61 tests, 103 assertions** covering Result/Option monads, command and query bus dispatch, validator chains, event dispatching, message hydration, and argument resolution.

---

Documentation
-------------

[](#documentation)

Full documentation is available in the [`docs/`](./docs/) directory, organized for serving via Nuxt Content.

---

License
-------

[](#license)

[MIT](LICENSE) © 2026 Redouane Taleb

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance91

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

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

Every ~2 days

Total

3

Last Release

42d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/751b94d8f42b73b4851757bbdb5eed691d145fb41e94df15ce3a69557ea9d8e7?d=identicon)[redouaneT](/maintainers/redouaneT)

---

Top Contributors

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

---

Tags

phplaravelquerycommandrepositorydddevent sourcingcqrsdomain-eventshexagonal-architecturetransaction-managementzoltaapplication-layer

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

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

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

###  Alternatives

[api-platform/core

Build a fully-featured hypermedia or GraphQL API in minutes!

2.6k50.1M306](/packages/api-platform-core)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M195](/packages/sulu-sulu)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.4M506](/packages/shopware-core)[pimcore/pimcore

Content &amp; Product Management Framework (CMS/PIM/E-Commerce)

3.8k3.8M444](/packages/pimcore-pimcore)[craftcms/cms

Craft CMS

3.6k3.6M2.9k](/packages/craftcms-cms)[oro/platform

Business Application Platform (BAP)

641140.7k103](/packages/oro-platform)

PHPackages © 2026

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