---
title: 'What is New in PHP Event Sourcing 3.20.0'
date: '2026-07-02'
author: 'daniel-badura'
tags: ['PHP', 'EventSourcing', 'Release']
contentPreview: 'Version 3.20.0 of patchlevel/event-sourcing focuses on operating event-sourced systems over the long run. You can now keep reading old messages whose header classes were removed, and you can skip the confirmation prompts of the CLI commands to use them in automation.'
---

Version [3.20.0](https://github.com/patchlevel/event-sourcing/releases/tag/3.20.0)
for [patchlevel/event-sourcing](/docs/event-sourcing/latest) is here. This is a smaller release with two new
features that both remove friction from your day-to-day work: keeping old messages readable when their header
classes are gone, and running the CLI commands without an interactive prompt.

## Graceful Handling of Missing Headers

Every [message](/docs/event-sourcing/latest/message) in the store can carry headers next to the event payload,
for example your own custom headers like an application or tenant id. When a message is deserialized, every
header name is resolved back to its registered header class.

That works fine until you remove one of those header classes. The old events in your store still
reference the old header name, and the `DefaultHeadersSerializer` then throws a `HeaderNameNotRegistered`
exception. So a piece of code you deleted long ago keeps your application from reading its own history. This
is the same kind of friction that [upcasting](/docs/event-sourcing/latest/upcasting) solves for event payloads,
just for headers.

With 3.20.0 you can tell the serializer which header names should be handled gracefully. Instead of crashing,
those headers are collected into a single `MissingHeaders` object, with their raw names and payloads preserved:

```php
use Patchlevel\EventSourcing\Message\Serializer\DefaultHeadersSerializer;

$serializer = DefaultHeadersSerializer::createFromPaths(
    ['src/Header'],
    ['legacyApplication', 'legacyTenant'],
);
```

If you need to, you can still get to the data that was kept around:

```php
use Patchlevel\EventSourcing\Message\MissingHeaders;

/** @var Message $message */
$missingHeaders = $message->header(MissingHeaders::class);
$missingHeaders->headers; // ['legacyApplication' => [...], 'legacyTenant' => [...]]
```

This is intentionally opt-in and explicit. Only the header names you list are tolerated. If a message contains
an unregistered header whose name is **not** in the list, deserialization still throws `HeaderNameNotRegistered`.
That distinction matters: some headers are business critical, like a tenant header, and silently dropping one
of those would be far worse than a loud failure.

When you really do want to accept every unregistered header, there is a `*` wildcard:

```php
use Patchlevel\EventSourcing\Message\Serializer\DefaultHeadersSerializer;

$serializer = DefaultHeadersSerializer::createFromPaths(
    ['src/Header'],
    ['*'],
);
```

You can read the details in the [missing headers docs](/docs/event-sourcing/latest/message#missing-headers).

## A Force Flag for the CLI Commands

Several of our [CLI commands](/docs/event-sourcing/latest/cli) are destructive and ask for confirmation before
they do their work, for example `event-sourcing:database:drop`, `event-sourcing:schema:drop` or
`event-sourcing:subscription:remove`. That interactive prompt is a good safety net when you run a command by
hand, but it gets in the way the moment you want to run the same command from a script, a deployment pipeline
or some other tooling where nobody is there to answer it.

For exactly that case, these commands now accept a `--force` flag that skips the confirmation:

```bash
bin/console event-sourcing:subscription:remove --force
```

A small change, but one that makes the library noticeably friendlier to automate. Thanks to
[@Brammm](https://github.com/patchlevel/event-sourcing/pull/855) for the contribution!

## Conclusion

Two small additions in this release. The `--force` flag lets you run the destructive CLI commands from scripts
and deployment pipelines without an interactive prompt getting in the way. Graceful header handling means you can
remove header classes without locking yourself out of old events, so headers stay flexible to work with over time.

Questions or ideas? Open an [issue](https://github.com/patchlevel/event-sourcing) or start a
[discussion](https://github.com/patchlevel/event-sourcing/discussions) on GitHub.
