PHPackages                             xprt64/dudulina - 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. xprt64/dudulina

ActiveLibrary[Framework](/categories/framework)

xprt64/dudulina
===============

CQRS with Event Sourcing mini framework for PHP

6.0.5(1y ago)5512462proprietaryPHPPHP ^8.0CI failing

Since Jan 18Pushed 1y ago9 watchersCompare

[ Source](https://github.com/xprt64/dudulina)[ Packagist](https://packagist.org/packages/xprt64/dudulina)[ RSS](/packages/xprt64-dudulina/feed)WikiDiscussions master Synced yesterday

READMEChangelogDependencies (7)Versions (116)Used By (2)

CQRS + Event Sourcing library for PHP 7+
========================================

[](#cqrs--event-sourcing-library-for-php-7)

[![MIT Licence](https://camo.githubusercontent.com/fabb40ab22588a0746bb0916ed92739171bde7fb31f281c627aa588bcba62cc2/68747470733a2f2f6261646765732e66726170736f66742e636f6d2f6f732f6d69742f6d69742e7376673f763d313033)](https://opensource.org/licenses/mit-license.php)[![Build Status](https://camo.githubusercontent.com/677649d164c5274a1fb9e8c1d4bdbf0e5ab8069656d92b17d3ba4004dc093586/68747470733a2f2f7472617669732d63692e6f72672f7870727436342f647564756c696e612e7376673f6272616e63683d6d61737465722672616e643d32)](https://travis-ci.org/xprt64/dudulina)[![Scrutinizer Code Quality](https://camo.githubusercontent.com/e1622e1313391cb6f7dbb027bcb40723aad6e0cc094060a63e09ffb44ec26aea/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f7870727436342f637172732d65732f6261646765732f7175616c6974792d73636f72652e706e673f623d6d61737465722672616e643d34)](https://scrutinizer-ci.com/g/xprt64/cqrs-es/?branch=master)[![Code Climate](https://camo.githubusercontent.com/1d71f925062a4fe7cde12d9be90bbf1167b01314a8d30ca0e8983a3f8b07bfff/68747470733a2f2f636f6465636c696d6174652e636f6d2f6769746875622f7870727436342f637172732d65732f6261646765732f6770612e737667)](https://codeclimate.com/github/xprt64/cqrs-es)[![Code Coverage](https://camo.githubusercontent.com/c8d21fac0962215f0514d60d0551e085a9d549d37dc532ecc55f5de23a7cbd42/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f7870727436342f637172732d65732f6261646765732f636f7665726167652e706e673f623d6d61737465722672616e643d34)](https://scrutinizer-ci.com/g/xprt64/cqrs-es/?branch=master)[![Build Status](https://camo.githubusercontent.com/9a91f136b56f4f28a368bcc23a6e106cb9a3ae22b20bd0383cc06f4e4e4c217f/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f7870727436342f637172732d65732f6261646765732f6275696c642e706e673f623d6d61737465722672616e643d34)](https://scrutinizer-ci.com/g/xprt64/cqrs-es/build-status/master)

This is a non-obtrusive [CQRS](http://www.cqrs.nu/) + Event Sourcing library that helps building complex DDD web applications.

Minimum dependency on the library in the domain code
----------------------------------------------------

[](#minimum-dependency-on-the-library-in-the-domain-code)

### Only 3 interfaces need to be implemented

[](#only-3-interfaces-need-to-be-implemented)

No inheritance! Your domain code remains clean and infrastructure/framework agnostic as should be.

- `\Dudulina\Event` for each domain event; no methods, it is just a marker interface; the domain events need to be detected by the automated code generation tools;
- `\Dudulina\Command` for each domain command; only one method, `getAggregateId()`; it is needed by the command dispatcher to know that Aggregate instance to load from Repository
- `\Dudulina\ReadModel\ReadModelInterface` for each read model; this is required only if you use the `ReadModelRecreator` to rebuild your read-models (projections)

Even if only a few interfaces need to be implemented, you could loose the coupling to the library even more. You could define and use your own domain interfaces and only that interfaces would inherit from the library interfaces. In this way, when you change the library, you change only those interfaces.

Minimum code duplication on the write side
------------------------------------------

[](#minimum-code-duplication-on-the-write-side)

On the write side, you only need to instantiate a command and send it to the `CommandDispatcher`;

Let's create a command.

```
// immutable and Plain PHP Object (Value Object)
// No inheritance!
class DoSomethingImportantCommand implements Command
{
    private $idOfTheAggregate;
    private $someDataInTheCommand;

    public function __construct($idOfTheAggregate, $someDataInTheCommand)
    {
        $this->idOfTheAggregate = $idOfTheAggregate;
        $this->someDataInTheCommand = $someDataInTheCommand;
    }

    public function getAggregateId()
    {
        return $this->idOfTheAggregate;
    }

    public function getSomeDataInTheCommand()
    {
        return $this->someDataInTheCommand;
    }
}
```

Now, let's create a simple event:

```
// immutable, simple object, no inheritance, minimum dependency
class SomethingImportantHappened implements Event
{
    public function __construct($someDataInTheEvent)
    {
        $this->someDataInTheEvent = $someDataInTheEvent;
    }

    public function getSomeDataInTheEvent()
    {
        return $this->someDataInTheEvent;
    }
}
```

Somewhere in the UI or Application layer:

```
class SomeHttpAction
{
    public function getDoSomethingImportant(RequestInterface $request)
    {
        $idOfTheAggregate = $request->getParsedBody()['id'];
        $someDataInTheCommand = $request->getParsedBody()['data'];

        $this->commandDispatcher->dispatchCommand(new DoSomethingImportantCommand(
            $idOfTheAggregate,
            $someDataInTheCommand
        ));

        return new JsonResponse([
            'success' => 1,
        ]);
    }
}
```

That's it. No transaction management, no loading from the repository, nothing. The command arrives to the aggregate's command handler, as an argument, like this:

```
class OurAggregate
{
    //....
    public function handleDoSomethingImportant(DoSomethingImportantCommand $command)
    {
        if($this->ourStateDoesNotPermitThis()){
            throw new \Exception("No no, it is not possible!");
        }

        yield new SomethingImportantHappened($command->getSomeDataInTheCommand());
    }

    public function applySomethingImportantHappened(SomethingImportantHappened $event, Metadata $metadata)
    {
        //Metadata is optional
        $this->someNewState = $event->someDataInTheEvent;
    }
}
```

The read models receive the raised event to. They process the event after it is persisted. Take a look at a possible read model:

```
class SomeReadModel
{
    //...some database initialization, i.e. a MongoDB database injected in the constructor

    public function onSomethingImportantHappened(SomethingImportantHappened $event, Metadata $metadata)
    {
        $this->database->getCollection('ourReadModel')->insertOne([
            '_id' => $metadata->getAggregateId()
            'someData' => $event->getSomeDataInTheEvent()
        ]);
    }

    //this method could be used by the UI to display the data
    public function getSomeDataById($id)
    {
        $document = $this->database->getCollection('ourReadModel')->findOne([
            '_id' => $metadata->getAggregateId()
         ]);

         return $document ? $document['someData'] : null;
    }
}
```

The Read-models can be updated in a separate process, in realtime-like (by tailing) or by polling the Event store or even using [JavaScript](https://github.com/xprt64/dudulina-js-connector). Read more here about how you can [keep the Read-model up-to-date](https://github.com/xprt64/dudulina/blob/master/docs/ReadmodelsAndMicroservices.md).

So, when a command is dispatched the following things happen:

- the aggregate class is identified
- the aggregate is loaded from the repository, replaying all previous events
- the command is dispatched to the aggregate instance
- the aggregate yields the events
- the events are persisted to the event store
- the read-models are notified about the new events
- the sagas are notified also; if the sagas generate other commands, the loop is started again.

If an exception is thrown by the command handler on the aggregate, no events are persisted and the exception reach the caller

Read the entire [documentation here](DOCUMENTATION.md)

The Event store
---------------

[](#the-event-store)

There is a [MongoDB](https://github.com/xprt64/mongolina) implementation of the Event store and a [Restful HTTP API](https://github.com/xprt64/dudulina-eventstore-api) for this Event store if you want to build Read-models in any other languages than PHP.

A [JavaScript connector](https://github.com/xprt64/dudulina-js-connector) is also available. [Here you can find some examples of updating a Read-model in JavaScript](https://github.com/xprt64/dudulina-js-connector/tree/master/sample).

The Queries
-----------

[](#the-queries)

The library can dispatch queries also. The *Askers* ask questions and the *Answerers* answser them.

The Askers ask question to the `\Dudulina\Query\Asker` and the can receive the answer as return value or as callback on them (the method `$this->whenAnsweredXYZ` or the one marked with `@QueryAsker`).

The Answerers answer questions at the `$this->whenAskedXXX` or `@QueryHandler` marked methods. They can also answer a question when they know that the answer has changed and all the askers are notified, by calling `\Dudulina\Query\Answerer::answer()`.

CQRS bindings
-------------

[](#cqrs-bindings)

How does the library know what command handler to call when a command is dispatched? Or what read models to notify when a new event is published? The answer to all these questions is CQRS bindings.

Long story short, [the tools](https://github.com/xprt64/dudulina/tree/master/bin) analyze the domain code, detect the handlers and build a PHP file with all the bindings as classes. Then you use those classes to configure the CommandDispatcher. The `create_bindings.php` must be run every time the domain code changes.

```
php -f vendor/xprt64/dudulina/bin/create_bindings.php -- --src="/some/source/directory" --src="/some/other/source/directory" > cqrs_bindings.php
```

Then you need to include the file `create_bindings.php` to your `index.php` usually after `vendors/autoload.php`.

```
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../deploy/cqrs_bindings.php';
```

Sample application
------------------

[](#sample-application)

A Todo list sample application can be found at [github.com/xprt64/todosample-cqrs-es](https://github.com/xprt64/todosample-cqrs-es).

Querying an Aggregate in DDD
----------------------------

[](#querying-an-aggregate-in-ddd)

Read more about how to [query an Aggregate](https://github.com/xprt64/dudulina/blob/master/docs/QueryingAnAggregate.md) in order to test if a command will succeed or not, without actually executing it.

Questions?
----------

[](#questions)

Feel free to post to this group: .

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance43

Moderate activity, may be stable

Popularity23

Limited adoption so far

Community19

Small or concentrated contributor base

Maturity85

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 90% 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 ~26 days

Recently: every ~298 days

Total

115

Last Release

458d ago

Major Versions

1.2.7 → 2.0.02017-03-28

2.1.13 → 3.0.12017-08-28

3.0.10 → 4.0.02018-02-05

4.2.6 → 5.0.02019-11-14

5.1.10 → 6.02021-12-21

PHP version history (2 changes)0.0.1PHP ^7.0

5.1.10PHP ^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/915088?v=4)[Constantin Galbenu](/maintainers/xprt64)[@xprt64](https://github.com/xprt64)

---

Top Contributors

[![xprt64](https://avatars.githubusercontent.com/u/915088?v=4)](https://github.com/xprt64 "xprt64 (18 commits)")[![aand18](https://avatars.githubusercontent.com/u/36073609?v=4)](https://github.com/aand18 "aand18 (2 commits)")

---

Tags

cqrscqrs-frameworkdependency-injectiondomain-driven-designevent-drivenevent-sourcingphp7psr7-httpsolid-principlesframeworkDomain Driven Designdddevent sourcingcqrsscalablecommand query responsibility segregation

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/xprt64-dudulina/health.svg)

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

###  Alternatives

[symfony/symfony

The Symfony PHP framework

31.4k86.9M2.2k](/packages/symfony-symfony)[laravel/framework

The Laravel Framework.

34.8k532.1M19.4k](/packages/laravel-framework)[cakephp/cakephp

The CakePHP framework

8.8k19.1M1.7k](/packages/cakephp-cakephp)[ecotone/ecotone

Enterprise architecture layer for Laravel and Symfony — CQRS, Event Sourcing, Durable Workflows (Sagas, Orchestrators), Projections, and Outbox messaging via PHP attributes.

562565.8k42](/packages/ecotone-ecotone)[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k12](/packages/tempest-framework)[broadway/broadway

Infrastructure and testing helpers for creating CQRS and event sourced applications.

1.5k2.0M61](/packages/broadway-broadway)

PHPackages © 2026

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