PHPackages                             gilsegura/shared - 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. gilsegura/shared

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

gilsegura/shared
================

Framework-agnostic DDD, Event Sourcing and CQRS building blocks: domain value objects, aggregates, event store, command/query buses, criteria DSL and read models.

2.2.0(3d ago)13621MITPHPPHP ^8.4CI passing

Since Dec 2Pushed 3d ago1 watchersCompare

[ Source](https://github.com/gilsegura/shared)[ Packagist](https://packagist.org/packages/gilsegura/shared)[ RSS](/packages/gilsegura-shared/feed)WikiDiscussions main Synced yesterday

READMEChangelogDependencies (7)Versions (14)Used By (1)

SHARED
======

[](#shared)

[![tests](https://github.com/gilsegura/shared/actions/workflows/tests.yaml/badge.svg)](https://github.com/gilsegura/shared/actions/workflows/tests.yaml)[![codecov](https://camo.githubusercontent.com/a885927a8f06bd031cbe397ba2f931ce0a01cf67ce6d36211e49b4b8672f6f5a/68747470733a2f2f636f6465636f762e696f2f6769746875622f67696c7365677572612f7368617265642f67726170682f62616467652e737667)](https://codecov.io/github/gilsegura/shared)[![static analysis](https://github.com/gilsegura/shared/actions/workflows/static-analysis.yaml/badge.svg)](https://github.com/gilsegura/shared/actions/workflows/static-analysis.yaml)[![coding standards](https://github.com/gilsegura/shared/actions/workflows/coding-standards.yaml/badge.svg)](https://github.com/gilsegura/shared/actions/workflows/coding-standards.yaml)

Framework-agnostic building blocks for DDD, CQRS and event sourcing. It provides the domain primitives (value objects, events, aggregates), the command/query and event bus contracts, the event store and upcasting contracts, a criteria DSL for querying, and read-model projection support — all as plain PHP, with no framework dependency. The `gilsegura/shared-bundle` package wires these onto Symfony, Messenger and Doctrine.

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

[](#installation)

```
composer require gilsegura/shared
```

Domain primitives
-----------------

[](#domain-primitives)

`Shared\Domain` holds the value objects and the event envelope every other piece builds on:

- **`Uuid`, `Email`, `HashedPassword`, `NotEmptyString`, `DateTimeImmutable`** — immutable value objects with validation and equality.
- **`DomainEventInterface`** — marker for domain events; events are serializable so they can be stored and replayed.
- **`DomainMessage`** — wraps a domain event (`payload`) together with the aggregate `id`, `playhead`, `Metadata` and `recordedAt`. The `type` is derived from the event class. This is the unit the event store persists and the buses carry.
- **`DomainEventStream`** — an ordered, iterable stream of `DomainMessage`s.
- **`Metadata`** — arbitrary key/value context attached to a message.

Command and query handling
--------------------------

[](#command-and-query-handling)

`Shared\CommandHandling` defines the CQRS contracts, with no transport opinion:

- **`CommandInterface` / `QueryInterface`** — markers for messages.
- **`CommandHandlerInterface` / `QueryHandlerInterface`** — markers for handlers, generic over the message they handle.
- **`CommandBusInterface` / `QueryBusInterface`** — the buses a use case depends on. An adapter (e.g. in the bundle) provides the concrete bus.

Event handling
--------------

[](#event-handling)

`Shared\EventHandling` is the synchronous, in-process event bus:

- **`EventListenerInterface`** — a listener invoked with a `DomainMessage`.
- **`EventBusInterface`** — publishes a `DomainEventStream` to its listeners.
- **`SimpleEventBus`** — publishes to its listeners in order, fail-fast: the first listener that throws stops the dispatch.

Event sourcing
--------------

[](#event-sourcing)

`Shared\EventSourcing` turns event streams into aggregates and back:

- **`AbstractEventSourcedAggregateRoot`** — base aggregate. `apply()` records an event and routes it to an `applyXxx` method resolved from the event's short name; `uncommittedEvents()` returns what to persist; `initialize()` rebuilds state by replaying a stream onto the aggregate, advancing the playhead from its current position (a fresh aggregate replays from the start; one restored from a snapshot replays only the events after it). Child entities are reachable through `childEntities()`.
- **`AbstractEventSourcedEntity`** — same apply mechanics for entities nested inside an aggregate.
- **`AbstractEventSourcingRepository`** — delegates reading to a loader and writing to a register, so it neither reads the stream nor appends, publishes or snapshots itself: both are decorated seams.
- **`AggregateRootFactoryInterface`** + **`ReflectionAggregateRootFactory`** / **`PublicConstructorAggregateRootFactory`** — turn a given stream into an aggregate. A factory only transforms a stream; it does not fetch anything.
- **`AggregateRootLoaderInterface`** — loads a fully rebuilt aggregate by id. Unlike a factory, a loader fetches what it needs and produces the aggregate, so it takes the id. This is the read seam the repository depends on and the seam snapshotting decorates.
- **`Loader\EventStoreAggregateRootLoader`** — the base loader: fetches the full stream from the event store and hands it to the factory.
- **`AggregateRootRegisterInterface`** + **`Register\EventStoreAggregateRootRegister`**— the write seam: decorate the uncommitted events, append them and publish them. Snapshotting decorates this too, symmetric with the loader.
- **`EventStreamDecoratorInterface`** + **`MetadataEnricher`** — decorate the outgoing stream, e.g. to enrich every message's metadata.

Event store
-----------

[](#event-store)

`Shared\EventStore` is the persistence contract for streams:

- **`EventStoreInterface`** — `load`/`append` a `DomainEventStream` by aggregate id, with `StreamNotFoundException` / `StreamAlreadyExistsException`.
- **`EventStoreManagerInterface`** — visiting/streaming events for replay and maintenance.
- **`EventVisitorInterface` / `CallableEventVisitor`** — visit matching events.

Upcasting
---------

[](#upcasting)

`Shared\Upcasting` keeps old event shapes loadable as the schema evolves:

- **`UpcasterInterface`** — transforms a `DomainMessage` into its newer shape, or returns it unchanged when the event is not its concern.
- **`SequentialUpcasterChain`** — runs the message through its upcasters in order, each receiving the output of the previous one. With no upcasters the message passes through unchanged.
- **`UpcastingEventStore`** — decorates an event store so events are upcast both when they are loaded (`load`) and when they are visited (`visitEvents`), so a rebuild sees the same current shapes a load produces. An empty chain is a transparent pass-through.

Snapshotting
------------

[](#snapshotting)

`Shared\Snapshotting` makes loading long-lived aggregates cheap by skipping the replay of their whole history. It is opt-in: without it, aggregates always rebuild from the full stream.

- **`Snapshot`** — a point-in-time capture of an aggregate at a given playhead.
- **`SnapshotStoreInterface`** — the persistence port for snapshots, keyed by aggregate id (the "where"). It only stores and retrieves; it holds no policy.
- **`SnapshotStrategyInterface`** + **`EventCountSnapshotStrategy`** — decide when a snapshot is due from the current playhead (the "when"). The event-count strategy snapshots each time the aggregate crosses a multiple of N events.
- **`SnapshotAggregateRootLoader`** — a loader that decorates another loader: when a snapshot exists it restores the captured aggregate and replays only the events recorded after it; otherwise it delegates a full rebuild to the inner loader. Composed like the upcasting store: `Snapshot(EventStore(...))`. This saves both the read and the replay of everything up to the snapshot.
- **`SnapshotAggregateRootRegister`** — the write-side counterpart: a register that decorates another register, delegating the append-and-publish first, then asking the strategy whether a snapshot is due and capturing it. Composed the same way, so loading and saving are symmetric, decorated seams.

The aggregate is unaware of snapshotting throughout: capturing and restoring its state is done from the outside.

Criteria
--------

[](#criteria)

`Shared\Criteria` is a small DSL for expressing filters and ordering in domain terms, independent of any database:

- Composites **`AndX` / `OrX`** combine comparisons such as **`EqId`**, **`EqEmail`**, **`EqPlayhead`**, **`GtePlayhead`**, **`ByPlayhead`**, **`ByRecordedAt`**.
- **`OrderX`** expresses sorting.
- The `Expr` namespace is the lower-level expression tree the comparisons map to.

An infrastructure adapter translates these into a concrete query (the bundle, for example, converts them to Doctrine criteria).

Queries and read models
-----------------------

[](#queries-and-read-models)

- **`Shared\Query`** — `QueryBuilder` builds a typed query from criteria; `CollectionQuery` / `SingleResultQuery` express the expected result shape. A `CollectionQuery` carries a `Pagination` (offset + limit, validated) and yields a `Page` (the items plus the total), so the result type the bus returns is the page itself and no per-query page class is needed.
- **`Shared\Projection`** — `AbstractProjector` is the shared mechanism behind any projection, read model or index: it implements `EventListenerInterface` and resolves an `applyXxx` method from each event's short name, so a projector only writes handlers for the events it reacts to.
- **`Shared\ReadModel`** — `ReadModelInterface` and `ReadModelRepositoryInterface`are the read-side contracts for a presentable projection: a model queried by criteria and served as a result.
- **`Shared\Index`** — the contracts for a *lookup index*: a projection that resolves a complete key to the ids filed under it, never presented as a resource. `IndexInterface` declares only `lookup(TKey): Uuid[]` — the read side, so it stays usable polymorphically; a concrete index adds `save`/`remove` with its own native `IndexEntryInterface` type, the same way a read-model repository declares its writes. `IndexKeyInterface` marks a typed key (an index answers one key, not an arbitrary criterion — that is what keeps it an index and not a queryable model). Use it for search or uniqueness lookups (e.g. tag → ids, email → user id) kept in sync by a projector.

Replaying
---------

[](#replaying)

`Shared\Replaying\Replayer` visits the events matching a criteria from the store and feeds them to an event visitor — e.g. to rebuild a read model from history. When the store is an `UpcastingEventStore`, the visited events are upcast too, so a projector reacting to the replay sees the current event shapes.

Specifications
--------------

[](#specifications)

`Shared\Specification\AbstractSpecification` is the base for composable domain specifications (business rules expressed as objects).

Exceptions
----------

[](#exceptions)

`Shared\Exception` provides a hierarchy mapping domain failures to intent — `NotFoundException`, `ConflictException`, `ForbiddenException`, `UnauthorizedException`, `InfrastructureException`, `UnexpectedException` — so transport layers can translate them to the right status without leaking domain details. Value objects validate their input with the native `\InvalidArgumentException`. Namespace-specific exceptions (`EventStoreException`, `CorruptSnapshotException`, `InvalidAggregateRootException`, `InvalidExpressionException`, …) extend the base that matches their intent.

Support
-------

[](#support)

`Shared\Support\ClassName` resolves the short name of a class, used to map events to their `applyXxx` methods.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

50

—

FairBetter than 95% of packages

Maintenance99

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity64

Established project with proven stability

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 ~48 days

Recently: every ~3 days

Total

13

Last Release

3d ago

Major Versions

0.1.0 → 1.0.02026-06-11

1.1.4 → 2.0.02026-06-28

PHP version history (3 changes)0.1.0PHP &gt;=8.3

1.0.0PHP ^8.5

1.1.3PHP ^8.4

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/29969702?v=4)[josué](/maintainers/gilsegura)[@gilsegura](https://github.com/gilsegura)

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/gilsegura-shared/health.svg)

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

###  Alternatives

[pocketmine/pocketmine-mp

A server software for Minecraft: Bedrock Edition written in PHP

3.5k78.3k90](/packages/pocketmine-pocketmine-mp)[firefly-iii/data-importer

Firefly III Data Import Tool.

8045.8k](/packages/firefly-iii-data-importer)[markocupic/calendar-event-booking-bundle

Contao Calendar Event Booking Bundle

135.2k1](/packages/markocupic-calendar-event-booking-bundle)[mynaparrot/plugnmeet-sdk

plugNmeet PHP SDK

102.8k](/packages/mynaparrot-plugnmeet-sdk)

PHPackages © 2026

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