PHPackages                             lingoda/domain-events - 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. lingoda/domain-events

ActiveSymfony-bundle[Utility &amp; Helpers](/categories/utility)

lingoda/domain-events
=====================

Lingoda Domain Events Bundle

2.4.1(1mo ago)829.5k↓45.5%3[1 PRs](https://github.com/lingoda/domain-events/pulls)MITPHPPHP ^8.3

Since Jun 10Pushed 1mo ago20 watchersCompare

[ Source](https://github.com/lingoda/domain-events)[ Packagist](https://packagist.org/packages/lingoda/domain-events)[ RSS](/packages/lingoda-domain-events/feed)WikiDiscussions main Synced 3d ago

READMEChangelog (10)Dependencies (76)Versions (25)Used By (0)

Domain Events Bundle
====================

[](#domain-events-bundle)

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

[](#installation)

```
composer req lingoda/domain-events
```

### Bundle configuration

[](#bundle-configuration)

```
# config/packages/domain_events.yaml

lingoda_domain_events:
    message_bus_name: 'event.bus'

    // default is false, you can turn on event publishing on Kernel, Console and WorkerMessageHandledEvent events, usefull in test environment
    enable_event_publisher: true
```

Usage
-----

[](#usage)

**IMPORTANT NOTE:** Never record domain events in doctrine lifecycle hooks!

Example of simple User entity that triggers a Domain Event.

### Sample Domain Event

[](#sample-domain-event)

```
use Lingoda\DomainEventsBundle\Domain\Model\DomainEvent;
use Lingoda\DomainEventsBundle\Domain\Model\Traits\DomainEventTrait;

/**
 * Sample domain event
 */
class UserCreatedEvent implements DomainEvent
{
    use DomainEventTrait;

    private string $username;

    public function __construct(string $entityId, string $username)
    {
        $this->username = $username;
        $this->init($entityId);
    }

    public function getUsername(): string
    {
        return $this->username;
    }
}
```

### Sample User entity that records the event

[](#sample-user-entity-that-records-the-event)

```
use Lingoda\DomainEventsBundle\Domain\Model\DomainEventAware;
use Lingoda\DomainEventsBundle\Domain\Model\Traits\EventRecorderTrait;
use Symfony\Component\Uid\Ulid;

// DomainEventAware interface is a helper that brings RecordsEvents and ContainsEvents together
class User implements DomainEventAware
{
    // helper trait for event recording
    use EventRecorderTrait;

    private Ulid $id;
    private string $username;

    public function __construct(string $username)
    {
        $this->id = new Ulid();
        $this->username = $username;

        $this->recordEvent(new UserCreatedEvent(
            $this->id->toRfc4122(),
            $username
        ));
    }

    public function getId(): Ulid
    {
        return $this->id;
    }

    public function setId(Ulid $id): void
    {
        $this->id = $id;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function setUsername(string $username): void
    {
        $this->username = $username;
    }
}
```

### In action

[](#in-action)

```
// create the entity will record the domain event
$user = new User('john-doe');

$entityManager->persist($user);

/**
 * When we flush the changes PersistDomainEventsSubscriber will kick in and create a OutboxRecord entity containing
 * the domain event in it that will be stored within the same transaction together with the User entity
 */
$entityManager->flush();

// Later on the PublishDomainEventsSubscriber will publish via Messenger all unpublished Domain Event from OutboxRecord
// database on the following events KernelEvents::TERMINATE, ConsoleEvents::TERMINATE or WorkerMessageHandledEvent
```

### Dispatching domain events with Messenger Worker

[](#dispatching-domain-events-with-messenger-worker)

First configure the outbox messenger transport

```
framework:
  messenger:
    transports:
      outbox:
        dsn: 'outbox://default' // the host part is the doctrine enity mananager name, this case default

    routing:
      Lingoda\DomainEventsBundle\Infra\Symfony\Messenger\OutboxMessage: outbox
```

#### Skip Locked

[](#skip-locked)

When running multiple consumers concurrently, you can enable `SKIP LOCKED` to avoid row contention. Instead of consumers blocking each other on locked rows, each consumer will skip already-locked rows and pick the next available one.

This requires MySQL 8.0+ or PostgreSQL 9.5+.

Enable it via the DSN:

```
framework:
  messenger:
    transports:
      outbox:
        dsn: 'outbox://default?skip_locked=true'
```

Or via transport options:

```
framework:
  messenger:
    transports:
      outbox:
        dsn: 'outbox://default'
        options:
          skip_locked: true
```

After that we can consume the Outbox table and dispatch domain events from it with the below command

```
bin/console messenger:consume outbox
```

### Scheduling events

[](#scheduling-events)

We can schedule Domain Events to be published in the future

```
// let's say we have AskForUserFeedbackEvent the following event that should be triggered 2 weeks after user registration
// and send a followup email to the user

// we could schedule this like follow

$askForUserFeedbackEvent = new AskForUserFeedbackEvent($user->getId());
$askForUserFeedbackEvent->setOccuredAt(
    new CarbonImmutable('+2 weeks')
);

$user->recordEvent($askForUserFeedbackEvent);

// this will be stored in OutboxRecord table and unpublished until the due date
```

### Replacing/Re-scheduling events in the event\_store

[](#replacingre-scheduling-events-in-the-event_store)

We can replace/re-schedule unpublished events by implementing the `ReplaceableEventInterface` for the Domain Event If you implement this interface, before the `OutboxRecord` persister stores a new domain event, it will check if there is any previously stored but unpublished events from the same entity id, if yes it will delete them and add the new one only.

### Enriching Domain Events

[](#enriching-domain-events)

While domain events should be immutable, sometimes it's inevitable that you need to enrich with additional information but you don't want to assign at creation time because the service is not accessible inside the entity.

You can listen to the `PreAppendEvent` in a subscriber/listener that is dispatched right before the Domain Event gets persisted. At this point you can enrich with additional information.

Simple example would be injecting and actorId which corresponds to the user id that is currently interacting with the app.

Testing
-------

[](#testing)

### Install dev dependencies

[](#install-dev-dependencies)

```
# Install dev dependecies
composer install --dev
```

### Run tests

[](#run-tests)

```
vendor/bin/phpunit
vendor/bin/phpspec run
```

TODO
----

[](#todo)

- Add functional tests
- Add instructions for doctrine mapping and routing DomainEvent
- Fix issues around Carbon serialization

###  Health Score

58

—

FairBetter than 98% of packages

Maintenance91

Actively maintained with recent releases

Popularity35

Limited adoption so far

Community21

Small or concentrated contributor base

Maturity73

Established project with proven stability

 Bus Factor2

2 contributors hold 50%+ of commits

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

Recently: every ~14 days

Total

17

Last Release

44d ago

Major Versions

1.2.3 → 2.0.02022-09-02

1.4.0 → 2.0.12024-08-31

PHP version history (3 changes)1.0PHP ^8.1

1.4.0PHP ^8.2

2.1.0PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/30c0431d32647bf0f71a0dbb9d2c6b9e71f18ae88124366d07a3305dc2505114?d=identicon)[balazscsaba2006](/maintainers/balazscsaba2006)

![](https://www.gravatar.com/avatar/738b2feca38c5cdbcab33a54f62a4e373eafa286479f8a16cdd5f8341ff6f439?d=identicon)[picur](/maintainers/picur)

---

Top Contributors

[![balazscsaba2006](https://avatars.githubusercontent.com/u/1202594?v=4)](https://github.com/balazscsaba2006 "balazscsaba2006 (11 commits)")[![TamasSzigeti](https://avatars.githubusercontent.com/u/148897?v=4)](https://github.com/TamasSzigeti "TamasSzigeti (6 commits)")[![picur](https://avatars.githubusercontent.com/u/226394?v=4)](https://github.com/picur "picur (5 commits)")[![StefanGramadnikov](https://avatars.githubusercontent.com/u/9464113?v=4)](https://github.com/StefanGramadnikov "StefanGramadnikov (2 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")[![kdaniel95](https://avatars.githubusercontent.com/u/40911283?v=4)](https://github.com/kdaniel95 "kdaniel95 (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Type Coverage Yes

### Embed Badge

![Health badge](/badges/lingoda-domain-events/health.svg)

```
[![Health](https://phpackages.com/badges/lingoda-domain-events/health.svg)](https://phpackages.com/packages/lingoda-domain-events)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.9M388](/packages/easycorp-easyadmin-bundle)[open-dxp/opendxp

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

9421.6k61](/packages/open-dxp-opendxp)[pimcore/pimcore

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

3.8k3.8M508](/packages/pimcore-pimcore)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M736](/packages/sylius-sylius)[sulu/sulu

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

1.3k1.4M204](/packages/sulu-sulu)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1189.8k](/packages/rcsofttech-audit-trail-bundle)

PHPackages © 2026

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