PHPackages                             star/domain-event - 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. star/domain-event

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

star/domain-event
=================

Domain events extension

3.0.0(1y ago)2400.2k↑188.3%1[3 issues](https://github.com/yvoyer/domain-event/issues)[1 PRs](https://github.com/yvoyer/domain-event/pulls)MITPHPPHP ^8.0CI failing

Since Nov 24Pushed 4mo ago1 watchersCompare

[ Source](https://github.com/yvoyer/domain-event)[ Packagist](https://packagist.org/packages/star/domain-event)[ RSS](/packages/star-domain-event/feed)WikiDiscussions master Synced 2d ago

READMEChangelog (3)Dependencies (10)Versions (23)Used By (0)

domain-event
============

[](#domain-event)

[![Build Status](https://github.com/yvoyer/domain-event/actions/workflows/php.yml/badge.svg)](https://github.com/yvoyer/domain-event/actions/workflows/php.yml/badge.svg)

Small implementation of the aggregate root in [ddd](https://en.wikipedia.org/wiki/Domain-driven_design). The `AggregateRoot` implementation triggers events that can be collected for publishing by an implementation of `EventPublisher`.

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

[](#installation)

- Add the package using [composer](https://getcomposer.org/) in your `composer.json`

`composer require star/domain-event`

Usage
-----

[](#usage)

1. Make your class inherit the `AggregateRoot` class.

```
// Product.php
class Product extends AggregateRoot
{
}
```

2. Create the event for the mutation.

```
class ProductWasCreated implements DomainEvent
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

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

3. Create a named constructor (static method), and a mutation method. The protected method will be invoked internally using the [naming standard](#naming-standard) of mutation methods.

```
// Product.php
    /**
     * Static construct, since the __construct() is protected
     *
     * @param string $name
     * @return Product
     */
    public static function draftProduct(string $name): Product
    {
        return self::fromStream([new ProductWasCreated($name)]);
    }

    /**
     * Mutation method that handles the business logic of your aggregate
     */
    public function confirm(): void
    {
        $this->mutate(new ProductWasConfirmed($this->getId()));
    }
```

4. Create the [callback](#naming-standard) method on the `AggregateRoot` that would be used to set the state after an event mutation.

```
    protected function onProductWasCreated(ProductWasCreated $event): void
    {
        $this->name = $event->name();
    }
}
```

Listening to an event
---------------------

[](#listening-to-an-event)

When you wish to perform an operation after an event was dispatched by the `EventPublisher`, you need to define your listener:

```
class DoSomethingProductCreated implements EventListener
{
    // methods on listener can be anything, it is configured by getListenedEvents
    public function doSomething(ProductWasCreated $event): void
    {
        // do something with the event
    }

    public function doSomethingAtFirst(PostWasPublished $event): void
    {
    }

    public function doSomethingInBetween(PostWasPublished $event): void
    {
    }

    public function doSomethingAtLast(PostWasPublished $event): void
    {
    }

    public static function getListenedEvents(): array
    {
        return [
            ProductWasCreated::class => 'doSomething', // priority will be assigned at runtime
            PostWasPublished::class => [ // multiple methods may be assigned priority
                100 => 'doSomethingAtFirst',
                0 => 'doSomethingInBetween',
                -20 => 'doSomethingAtLast',
            ],
        ];
    }
}
```

The listener needs to be given to the publisher, so that he can send the event.

```
$publisher = new class() implements EventPublisher {}; // your implementation choice
$publisher->subscribe(new DoSomethingProductCreated()); // This is a subscriber that listens to the ProductWasCreated event

$product = Product::draftProduct('lightsaber');
$publisher->publish(...$product->uncommitedEvents()); // will notify the listener and call the DoSomethingProductCreated::doSomething() method
```

**Warning**: Be advised that events will be removed from aggregate once collected and published, to avoid republishing the same event twice.

We currently support [third party](/docs/ports.md) adapters to allow you to plug-in the library into your infrastructure.

Naming standard
---------------

[](#naming-standard)

The events method on the `AggregateRoot` children must be prefixed with `on` and followed by the event name. ie. For an event class named `StuffWasDone` the aggregate should have a method:

```
protected function onStuffWasDone(StuffWasDone $event): void;
```

Note: The callback method can be changed to another format, by overriding the `AggregateRoot::getEventMethod()`.

```
protected function getEventMethod(DomainEvent $event): string
{
    if ($event instanceof StuffWasDone) {
        return 'whenYouDoStuff'; // the protected method whenYouDoStuff() would be invoked to apply the change to the aggregate
    }
}
```

Message bus
-----------

[](#message-bus)

The package adds the ability to dispatch messages (`Command` and `Query`). Compared to the `EventPubliser`, the `CommandBus` and `QueryBus` have different usages.

- Command bus: Responsible to dispatch an operation that returns nothing.
- Query bus: Responsible to fetch some information. The returned information is recommended to be returned in a readonly format.

([Example of usage](/examples/Blog/Application/Http/Controller/PostController.php))

Serialization of events
-----------------------

[](#serialization-of-events)

When persisting your event in a data store, you may use a [PayloadSerializer](src/Serialization/PayloadSerializer.php) instance to convert your event to a serializable string. The current implementation [PayloadFromReflection](src/Serialization/PayloadFromReflection.php) allow you to:

- register [PropertyValueTransformer](src/Serialization/Transformation/PropertyValueTransformer.php) to ensure your value objects are converted to a serializable format.
- Or implement the [SerializableAttribute interface](src/Serialization/SerializableAttribute.php) to contain the logic in each of your value objects.

Example
-------

[](#example)

The [blog](/examples/blog.phpt) example shows a use case for a blog application.

Symfony usage
-------------

[](#symfony-usage)

Using a Symfony application, you may use the provided compiler passes to use the buses.

```
use Star\Component\DomainEvent\Ports\Symfony\DependencyInjection\CommandBusPass;
use Star\Component\DomainEvent\Ports\Symfony\DependencyInjection\EventPublisherPass;
use Star\Component\DomainEvent\Ports\Symfony\DependencyInjection\QueryBusPass;

// Kernel.php
public function build(ContainerBuilder $container): void {
    $container->addCompilerPass(new CommandBusPass());
    $container->addCompilerPass(new QueryBusPass());
    $container->addCompilerPass(new EventPublisherPass());
}
```

Once registered, three new tags will be available:

- `star.command_handler`
- `star.query_handler`
- `star.event_publisher`

The tags `star.command_handler` and `star.query_handler` have an optional attribute `message` to specify the message FQCN that will be mapped to this handler. By default the system will try to resolve the same FQCN as the handler, without the `Handler` suffix.

```
// services.yaml
services:
    Path/For/My/Project/DoStuffHandler:
      tags:
        - { name star.command_handler, message: Path/For/My/Project/DoStuff }

    Path/For/My/Project/FetchStuffHandler:
      tags:
        - { name star.query_handler, message: Path\For\My\Project\FetchStuff }

```

*Note*: In both cases, omitting the message attributes would result in the same behavior.

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

[](#event-store)

Event stores are where your events will be persisted. You define which platform is used by extending the provided store.

[Example](/docs/ports.md#doctrine-dbal) with `DBALEventStore`.

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance44

Moderate activity, may be stable

Popularity36

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity78

Established project with proven stability

 Bus Factor1

Top contributor holds 98.5% 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 ~227 days

Recently: every ~166 days

Total

18

Last Release

379d ago

Major Versions

0.1.0 → 1.0.02016-05-25

1.0.1 → 2.0.02019-08-07

2.6.1 → 3.0.02025-06-20

PHP version history (6 changes)0.1.0PHP &gt;=5.3.3

1.0.0PHP &gt;=5.5.0

2.0.0PHP &gt;=7.1

2.3.2PHP ^7.1|^8.0

2.6.0PHP ^7.1||^8.0

3.0.0PHP ^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/36116f724f1bd3ed808e54e2b2de6c17209c5c2c1954685fba18958ec8d055ee?d=identicon)[yvoyer](/maintainers/yvoyer)

---

Top Contributors

[![yvoyer](https://avatars.githubusercontent.com/u/1745744?v=4)](https://github.com/yvoyer "yvoyer (66 commits)")[![sylvainfilteau](https://avatars.githubusercontent.com/u/337578?v=4)](https://github.com/sylvainfilteau "sylvainfilteau (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/star-domain-event/health.svg)

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

###  Alternatives

[symfony/symfony

The Symfony PHP framework

31.4k87.2M2.2k](/packages/symfony-symfony)[symfony/event-dispatcher-contracts

Generic abstractions related to dispatching event

3.4k807.0M621](/packages/symfony-event-dispatcher-contracts)[deptrac/deptrac

Deptrac is a static code analysis tool that helps to enforce rules for dependencies between software layers.

3.0k8.8M118](/packages/deptrac-deptrac)[phpro/soap-client

A general purpose SoapClient library

8896.1M54](/packages/phpro-soap-client)[mcp/sdk

Model Context Protocol SDK for Client and Server applications in PHP

1.5k1.5M88](/packages/mcp-sdk)[cognesy/instructor-php

The complete AI toolkit for PHP: unified LLM API, structured outputs, agents, and coding agent control

318123.0k1](/packages/cognesy-instructor-php)

PHPackages © 2026

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