PHPackages                             wwwision/dcb-eventstore - 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. wwwision/dcb-eventstore

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

wwwision/dcb-eventstore
=======================

Implementation of the Dynamic Consistency Boundary pattern described by Sara Pellegrini

5.0.4(2mo ago)2131.5k↓14.4%3[1 issues](https://github.com/bwaidelich/dcb-eventstore/issues)9MITPHPPHP &gt;=8.3CI passing

Since Jun 5Pushed 1mo ago3 watchersCompare

[ Source](https://github.com/bwaidelich/dcb-eventstore)[ Packagist](https://packagist.org/packages/wwwision/dcb-eventstore)[ GitHub Sponsors](https://github.com/sponsors/bwaidelich)[ Fund](https://www.paypal.me/bwaidelich)[ RSS](/packages/wwwision-dcb-eventstore/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (12)Versions (30)Used By (9)

Dynamic Consistency Boundary Event Store
========================================

[](#dynamic-consistency-boundary-event-store)

Interfaces and types for Event Stores implementing [Dynamic Consistency Boundaries](https://dcb.events/) according to the [specification](https://dcb.events/specification/).

To actually commit events, a corresponding [adapter package](#adapters) is required!

Adapters
--------

[](#adapters)

The following adapter implementations can be used with this package:

AdapterStorage/EngineTransport, SDK[ekvedaras/dcb-eventstore-illuminate](https://github.com/ekvedaras/dcb-eventstore-illuminate)SQLite, MySQL/MariaDB, PostgreSQL[Laravel Database](https://laravel.com/docs/12.x/database)[wwwision/dcb-eventstore-doctrine](https://github.com/bwaidelich/dcb-eventstore-doctrine)SQLite, MySQL/MariaDB, PostgreSQL[Doctrine DBAL](https://www.doctrine-project.org/projects/dbal.html)[wwwision/dcb-eventstore-esdb](https://github.com/bwaidelich/dcb-eventstore-esdb)[EventSourcing Database](https://www.eventsourcingdb.io/) (file based, proprietary)HTTP[wwwision/dcb-eventstore-umadb](https://github.com/bwaidelich/dcb-eventstore-umadb)[UmaDB](https://umadb.io/) (file based, open source)Rust FFI (via custom [PHP Extension](https://github.com/bwaidelich/umadb-php))[wwwision/dcb-eventstore-umadb-grpc](https://github.com/bwaidelich/dcb-eventstore-umadb-grpc)[UmaDB](https://umadb.io/) (file based, open source)gRPC (via official [PHP Extension](https://github.com/grpc/grpc))*Feel free to contact me or extend this list via [pull request](https://github.com/bwaidelich/dcb-eventstore/pulls) if you wrote another adapter implementation*

Usage
-----

[](#usage)

Install via [composer](https://getcomposer.org):

```
composer require wwwision/dcb-eventstore
```

### Create Event Store instance

[](#create-event-store-instance)

Instantiation of Event Stores depend on the corresponding [adapter package](#adapters). This package comes with an in-memory Event Store for testing, that can be created like so:

```
$eventStore = \Wwwision\DCBEventStore\InMemoryEventStore\InMemoryEventStore::create();
```

### Read Events

[](#read-events)

The `read()` function allows to read events. To obtain a stream of all events in the Event Store, `Query::all()` can be used:

```
use Wwwision\DCBEventStore\Query\Query;

$eventStream = $eventStore->read(Query::all());
```

The result is an iterable stream of `SequencedEvents`, that contain the originally appended event, the `position` of that event in the stream and some metadata:

```
// ...
foreach ($eventStream as $sequencedEvent) {
  $tags = implode(', ', $sequencedEvent->event->tags->toStrings());
  $metadata = print_r($sequencedEvent->event->metadata->value, true);
  echo "Position: {$sequencedEvent->position->value}\n";
  echo "Event type: {$sequencedEvent->event->type}\n";
  echo "Event tags: $tags\n";
  echo "Recorded at: {$sequencedEvent->recordedAt->format(DATE_ATOM)}\n";
  echo "Event data: {$sequencedEvent->event->data}\n";
  echo "Event metadata: $metadata\n";
  echo "----\n";
}
```

#### Filter events

[](#filter-events)

`Query::fromItems()` can be used to filter events, by their type:

```
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// return only events of the type "SomeEventType"
$eventStore->read(
  Query::fromItems(
    QueryItem::create(eventTypes: 'SomeEventType')
  )
);
```

...by tags:

```
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// return only events that are tagged "some:tag"
$eventStore->read(
  Query::fromItems(
    QueryItem::create(tags: 'some:tag')
  )
);
```

...or by a combination:

```
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// return only events that are tagged with "some:tag" AND "some:other-tag" and are of type "SomeType" OR "SomeOtherType"
$eventStore->read(
  Query::fromItems(
    QueryItem::create(eventTypes: ['SomeType', 'SomeOtherType'], tags: ['some:tag', 'some:other-tag'])
  )
);
```

Multiple `QueryItem`s can be specified to filter events that match *any* of the specified items:

```
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// return only events that are tagged "some:tag" and are of type "SomeType" OR that are tagged "some:other-tag" and are of type "SomeOtherType"
$eventStore->read(
  Query::fromItems(
    QueryItem::create(eventTypes: 'SomeType', tags: 'some:tag'),
    QueryItem::create(eventTypes: 'SomeOtherType', tags: 'some:other-tag')
  )
);
```

Note

Tags within a single QueryItem are conjunctive (combined with AND) while individual QueryItems are disjunctive (combined with OR)

### Read Options

[](#read-options)

An optional 2nd argument can be specified in order to define custom limits/orderings:

```
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\ReadOptions;

// read 100 events starting from sequence position 1234:
$eventStore->read(
  Query::all(),
  ReadOptions::create(
    from: 1234,
    limit: 100,
  )
);
```

By default events are always ordered by their `SequencePosition` in *ascending* order i.e. FIFO. Sometimes it can be useful to order events in *descending* order, for example in order to provide cursor-based pagination:

```
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\ReadOptions;

// read 50 events before (and including) event at sequence number 321
$eventStore->read(
  Query::all(),
  ReadOptions::create(
    from: 321,
    limit: 50,
    backwards: true,
  )
);
```

This can also be used to load the last event(s) with a certain type or tag:

```
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;
use Wwwision\DCBEventStore\ReadOptions;

// get last "InvoiceCreated" event (or NULL if none exists yet)
$lastInvoiceCreatedEvent = $eventStore->read(
  Query::fromItems(
    QueryItem::create(eventTypes: 'InvoiceCreated')
  ),
  ReadOptions::create(
    limit: 1,
    backwards: true,
  )
)->first();

$lastInvoiceNumber = $lastInvoiceCreatedEvent?->event->data->jsonDecode()['invoiceNumber'] ?? 0;
```

Write Events
------------

[](#write-events)

The `append()` function allows to write events.

### Unconditional writes

[](#unconditional-writes)

DCB is all about enforcing consistency when appending new events. But in some cases (e.g. when importing data or for testing purposes) it can be necessary to write events without enforcing any constraint. Therefor, the `appendCondition` parameter can be left out:

```
use Wwwision\DCBEventStore\Event\Event;

// append a single event without conditions
$eventStore->append(
  Event::create(
    type: 'SomeEventType',
    data: ['foo' => 'bar', 'bar' => 'baz'],
    tags: ['tag1', 'tag2'],
  )
);
```

Multiple events can be written atomically using `Events`:

```
use Wwwision\DCBEventStore\Event\Event;
use Wwwision\DCBEventStore\Event\Events;

// append two events atomically without conditions
$eventStore->append(
  Events::fromArray([
    Event::create(type: 'SomeEventType', data: 'data1'),
    Event::create(type: 'SomeOtherEventType', data: 'data2'),
  ])
);
```

### Append Condition

[](#append-condition)

The following call appends a `ProductDefined` event, but fails if a corresponding event for the same product id was appended previously (or practically at the same time, i.e. this operation ensures transaction safety):

```
use Wwwision\DCBEventStore\AppendCondition\AppendCondition;
use Wwwision\DCBEventStore\Event\Event;
use Wwwision\DCBEventStore\Event\Events;
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// append a single "ProductDefined" event only if no corresponding event with the same tag was appended previously
$eventStore->append(
  Event::create(type: 'ProductDefined', data: ['id' => 'p123', 'title' => 'Some product'], tags: ['product:p123']),
  condition: AppendCondition::create(
    failIfEventsMatch: Query::fromItems(QueryItem::create(eventTypes: 'ProductDefined', tags: 'product:p123')),
  ),
);
```

In the previous example, no event in the entire stream must match the specified query – it can be compared with a `NoStream` expectation of a traditional event store. But DCB also supports to specify a "safe point" using the optional `after` parameter of the `AppendCondition`:

```
use Wwwision\DCBEventStore\AppendCondition\AppendCondition;
use Wwwision\DCBEventStore\Event\Event;
use Wwwision\DCBEventStore\Event\Events;
use Wwwision\DCBEventStore\Query\Query;
use Wwwision\DCBEventStore\Query\QueryItem;

// append a single "ProductPriceChanged" event only if no corresponding event with the same tag was appended after the safe point (sequence position 1234)
$eventStore->append(
  Event::create(type: 'ProductPriceChanged', data: ['id' => 'p123', 'newPrice' => 54321], tags: ['product:p123']),
  condition: AppendCondition::create(
    failIfEventsMatch: Query::fromItems(QueryItem::create(eventTypes: 'ProductPriceChanged', tags: 'product:p123')),
    after: 1234,
  ),
);
```

Higher Level API
----------------

[](#higher-level-api)

This package mainly implements the low-level DCB specification (see [dcb.events website](https://dcb.events/specification/)). It's highly advised to introduce a higher level abstraction for the usage within the actual application logic.

Feel free to get in touch to see how this can be combined with the idea of [composed projections](https://dcb.events/topics/projections/#composing-projections) in practice!

Contribution
------------

[](#contribution)

Contributions in the form of [issues](https://github.com/bwaidelich/dcb-eventstore/issues), [pull requests](https://github.com/bwaidelich/dcb-eventstore/pulls) or [discussions](https://github.com/bwaidelich/dcb-eventstore/discussions) are highly appreciated

License
-------

[](#license)

See [LICENSE](./LICENSE)

###  Health Score

58

—

FairBetter than 98% of packages

Maintenance88

Actively maintained with recent releases

Popularity38

Limited adoption so far

Community21

Small or concentrated contributor base

Maturity70

Established project with proven stability

 Bus Factor1

Top contributor holds 92.9% 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 ~37 days

Recently: every ~21 days

Total

28

Last Release

60d ago

Major Versions

1.2.3 → 2.0.02023-08-02

2.1.1 → 3.0.02023-08-16

3.2.0 → 4.0.02024-12-10

4.2.1 → 5.0-beta-12025-10-17

PHP version history (5 changes)1.0.0PHP 8.2

1.2.1PHP &gt;=8.2

2.1.0PHP &gt;=8.1

4.0.0PHP &gt;=8.4

4.1.0PHP &gt;=8.3

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/307571?v=4)[Bastian Waidelich](/maintainers/bwaidelich)[@bwaidelich](https://github.com/bwaidelich)

---

Top Contributors

[![bwaidelich](https://avatars.githubusercontent.com/u/307571?v=4)](https://github.com/bwaidelich "bwaidelich (105 commits)")[![localheinz](https://avatars.githubusercontent.com/u/605483?v=4)](https://github.com/localheinz "localheinz (8 commits)")

---

Tags

dcbddddomaindrivendesigndynamicconsistencyboundaryeventsourcing

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/wwwision-dcb-eventstore/health.svg)

```
[![Health](https://phpackages.com/badges/wwwision-dcb-eventstore/health.svg)](https://phpackages.com/packages/wwwision-dcb-eventstore)
```

###  Alternatives

[phpdocumentor/reflection-docblock

With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.

9.4k722.2M1.2k](/packages/phpdocumentor-reflection-docblock)[eventsauce/eventsauce

A pragmatic event sourcing library for PHP with a focus on developer experience.

8632.1M47](/packages/eventsauce-eventsauce)[symplify/monorepo-builder

Not only Composer tools to build a Monorepo.

5205.3M82](/packages/symplify-monorepo-builder)[ecotone/ecotone

Supporting you in building DDD, CQRS, Event Sourcing applications with ease.

558549.8k17](/packages/ecotone-ecotone)[flow-php/etl

PHP ETL - Extract Transform Load - Abstraction

374468.4k51](/packages/flow-php-etl)[sylius/promotion

Flexible promotion management for PHP applications.

28477.8k9](/packages/sylius-promotion)

PHPackages © 2026

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