Open-source project
patchlevel/event-sourcing-bundle avatar
patchlevel/event-sourcing-bundle

event-sourcing-bundle ships mutation thresholds in its Makefile

An event sourcing bundle, complete with all the essential features, powered by the reliable Doctrine ecosystem and focused on developer experience.

52 stars11 forksPHPMIT

At a glance

What is it?
patchlevel's event-sourcing-bundle is a Symfony integration for the event-sourcing library, built on Doctrine DBAL. Its feature list covers snapshots, stream splitting, subscriptions, upcasting and crypto-shredding, and its default branch is a maintenance branch named after the next release rather than main.
Who is it for?
event-sourcing-bundle suits a Symfony team that wants event sourcing wired up with typed code, schema management and built-in command line tooling rather than assembled by hand. It does not suit someone evaluating the underlying pattern, since the repository documents integration rather than concepts.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Symfony bundle, and a library that lives elsewhere

The first thing to get straight is which project this is. The repository is a bundle, meaning a Symfony integration, for a separate event-sourcing library maintained by the same author. The library is where the conceptual documentation lives, linked from the feature list, and the bundle documentation is a separate site. Everything in the README describes integration: wiring the store into a Symfony application, managing the schema, registering command line commands, integrating with a static analyser through a dedicated plugin. The bundle claims that everything needed is included in the package, which is a real statement in the Symfony ecosystem, where the usual failure mode is discovering at deployment that a feature needs a package you did not install. The licence is MIT and the packagist badges are the distribution channel, so there is nothing to build.

The default branch is a maintenance branch, not main

The repository's default branch is named after the next minor version rather than a stable-sounding main. That is unusual enough to explain twice. The most recent releases are 3.16.1, 3.17.0 and 3.17.1, with the newest in July 2026, and the default branch carries a 3.18 label, so the branch you land on when you open the repository is ahead of the newest tag. In a project that publishes pull requests this is a deliberate arrangement rather than a mistake, and the Makefile confirms it: the mutation testing target for differences compares against origin/HEAD, which means the default branch is treated as the integration branch that pull requests are measured against. If you are pinning a version in production, pin a tag rather than a branch, because this repository's HEAD moves with unreleased work.

Mutation thresholds are numbers in a Makefile

The Makefile is the most informative file in the repository and it is not documented in the README. Nine targets exist, each depending on a vendor directory that is itself declared to depend on the committed lock file, so a fresh clone installs and then has every tool available. Beyond the obvious linting and unit test targets, there is a target for generating a static analysis baseline, one for running a mutation testing tool with five threads, and one for running it only on the lines a pull request touched. The thresholds are explicit rather than left to a configuration file:

code
php -d memory_limit=312M vendor/bin/phpstan analyse
code
XDEBUG_MODE=coverage vendor/bin/phpunit

A minimum mutation score of 80 and a minimum covered-code score of 95 are enforced on the diff check, and the target is told to ignore mutants that produced no mutations. Those numbers are a statement about how seriously the project takes its own test suite, and they are enforced in the same place a contributor will run the command from.

The lock file is committed, which libraries usually do not

A library normally ignores its dependency lock file, because the whole point is that consumers resolve versions themselves. This repository commits one, and pairs it with a Makefile target that installs exactly that lock file when the vendor directory is missing. The practical effect is that continuous integration runs against a frozen dependency graph, so a break caused by a transitive release cannot reach the test suite unnoticed, and the mutation testing numbers mean the same thing on every run. The cost is the usual one: the lock file has to be refreshed deliberately, and a contributor who updates a dependency is changing what everyone else tests against. Several other configuration files are shipped in the same deliberately explicit style, with dist suffixes so a project's own copy does not silently override them: a mutation testing configuration, a coding standard configuration, and the unit test configuration.

Crypto-shredding is the answer to the personal data question

Two of the listed features are about what happens to data you already stored. The first is safe usage of personal data with crypto-shredding, which links to its own documentation page. The idea, in short, is that the key protecting a person's data is destroyed rather than the data being deleted, so the record becomes unreadable without rewriting history. For an event store that is the only honest option, since appending a deletion event would leave the original values in the stream. The second is upcasting, which is about the opposite problem: events written by an older version of your code often have a shape your current code no longer expects, and upcasting is the mechanism for transforming them so old streams keep loading. Both features exist because event sourcing is unforgiving about history, and both are the kind of thing a team discovers they need after the store is already in production.

The install command comes with a warning

Installation is a single Composer require, and then there is a warning box. If you are not using the Symfony Flex recipe for this bundle, the README says you have to follow separate installation documentation instead. That warning is doing real work, because a Symfony bundle is not inert: it ships configuration, service definitions and a schema that has to be created, and the recipe is what wires all of that. A project that installs the package without the recipe and does not follow the linked steps will find the services unregistered and the store tables missing, and the error will appear at runtime rather than at build time. The same page set covers the pieces the feature list implies: scheme management, the integration with the ORM's own migration tooling, and built-in command line commands. There is a sponsor section at the bottom of the README and a link to the author's blog, which is also where the deeper explanations live.

A committed baseline means some errors are known

Among the configuration files at the root there is a static analysis baseline file alongside the configuration that generates it, and the Makefile has a target whose only job is to regenerate that baseline. This is a normal arrangement and a slightly awkward one at the same time. It lets the project run a static analyser across a large codebase without failing on pre-existing findings, and it means the bar for new code is absolute rather than relative. The cost is that the baseline can grow. Nothing in the Makefile prevents a contributor from regenerating it as part of a feature, so the discipline depends on review rather than on tooling, and a reviewer who does not notice a baseline diff has approved a permanent exemption. That is worth knowing about before you rely on the analyser output as a guarantee. The mutation testing target is harder to dilute in the same way, since its thresholds are on the diff and the baseline has no equivalent.

Editorial conclusion

event-sourcing-bundle suits a Symfony team that wants event sourcing wired up with typed code, schema management and built-in command line tooling rather than assembled by hand. It does not suit someone evaluating the underlying pattern, since the repository documents integration rather than concepts. Before you adopt it, check three things: whether you are using the Symfony Flex recipe, because the README attaches a warning to the other path, how many subscriptions you intend to run, since their lifecycle is managed rather than yours, and whether crypto-shredding matches your data retention obligations rather than merely satisfying them.

Frequently asked questions

What is the meaning of event sourcing in event-sourcing-bundle?

The bundle is the Symfony integration layer for an event-sourcing library, so the repository documents integration rather than the pattern itself. Its feature list covers snapshots, splitting big aggregates across streams, managed subscription lifecycles, upcasting old events, and crypto-shredding for personal data.

How do I install the patchlevel event sourcing bundle?

With one Composer command requiring patchlevel/event-sourcing-bundle. The README attaches a warning to that step: if you are not using the Symfony Flex recipe for the bundle, you have to follow the separate installation documentation instead, since the bundle ships configuration and a schema that has to be registered.

How does event-sourcing-bundle handle very large aggregates?

Two documented mechanisms. A snapshot system runs automatically to improve performance, and big aggregates can be split into multiple streams. Both address the same problem, which is an event history that grows large enough to slow down every load.

Does event-sourcing-bundle handle personal data and old event formats?

It supports safe usage of personal data with crypto-shredding, and smooth upcasting of old events so streams written by earlier versions of your code keep loading. Both are the mechanisms it provides for the two ways event storage makes life hard, immutability and schema drift.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/patchlevel-event-sourcing-bundle.svg)](https://hysenlabs.com/projects/patchlevel-event-sourcing-bundle)