---
title: 'What is New in PHP Event Sourcing 3.11.0'
date: '2025-04-22'
author: 'daniel-badura'
tags: ['PHP', 'EventSourcing', 'Release']
contentPreview: 'Discover what is new in patchlevel/event-sourcing 3.11.0, including improved console commands, a powerful query bus, and gap detection for subscriptions. This release focuses on enhancing developer experience and system resilience with practical, real-world features.'
---

Version [3.11.0](https://github.com/patchlevel/event-sourcing/releases/tag/3.11.0)
for [patchlevel/event-sourcing](/docs/event-sourcing/latest) is here. This release comes with some
excellent improvements, including enhancements to certain console commands, query bus integration via attributes, and a
gap detection mechanism for subscriptions. These are some really interesting updates so we shouldn't waste any time and
go through the changes immediately!

## Console Command Improvements

The first improvement is the addition of the `ConsoleLogger` for our `WatchCommand`. This enables you to see the logs
from the worker in the terminal if desired, significantly enhancing the developer experience.

The next enhancement concerns our `DebugCommand`. Previously, we only displayed information about the configured
aggregates and events. Now, we also display the configured subscribers, such as the `id`, `group`, `run_mode`, and the
subscribed `events`.

These two improvements were contributed by the community. If you’re interested in contributing to
[patchlevel/event-sourcing](https://github.com/patchlevel/event-sourcing), please feel free to open a PR or an issue if
you’d like to discuss your idea first - we’re happy to help!

## Query Bus

After adding a [command bus](/docs/event-sourcing/latest/command-bus) in `3.8.0`, we have now also
introduced a [query bus](/docs/event-sourcing/latest/query-bus). Unlike the command bus, the query bus
is not intended to perform actions on the system, instead, it retrieves information. It allows you to fully utilize the
read/write split and use small, independent, tailored projections.

To enable this feature as a standalone solution, we introduced a simple built-in query bus. For advanced use cases, we
still recommend external options like [symfony/messenger](https://symfony.com/doc/current/messenger.html). If you are
using our [event sourcing symfony bundle](/docs/event-sourcing-bundle/latest/configuration#query-bus),
the handlers are automatically registered as query handlers for Symfony Messenger when using the `#[Answer]` attribute.

Below is an example of how a projection can be implemented as a query handler:

```php
use Patchlevel\EventSourcing\Attribute\Projector;

#[Projector('profiles')]
final class ProfileProjector
{
    #[Answer]
    public function answerQueryProfile(QueryProfile $query): Profile
    {
        $builder = $this->projectionConnection->createQueryBuilder();

        $builder->select('*')
            ->from($this->tableName)
            ->where('id = :id')
            ->setParameter('id', $query->profileId->toString());

        return Profile::fromArray($builder->executeQuery()->fetchAssociative());
    }

    // projector related methods to maintain the state of profiles
}
```

For more details, check out our [documentation](/docs/event-sourcing/latest/query-bus), where you will
find an in-depth explanation of the query bus and how to configure it.

## Gap Detection for Subscriptions

When using an RDBMS for the event store instead of a designated database for event sourcing, it may happen that
subscriptions skip some events. Why does this occur? This behavior is due to the internals of these systems and how they
handle auto increments. When saving takes too long and a parallel save operation is started and finished earlier, the
later reserved auto-incremented ID will be saved before the lower one. This issue happens more frequently when using
transactions, especially when they remain open for an extended period. To mitigate this, we already have a locking
mechanism in place that only allows one write at a time. But what if this happens regardless, or if you want to improve
write performance by deactivating the locking?

Now, the subscription engine has the capability
to [check for these gaps](/docs/event-sourcing/latest/subscription#gap-resolver-store-message-loader)
by using an additional `MessageLoader`. The `GapResolverStoreMessageLoader` can detect gaps and, if necessary, wait a
defined amount of time before re-fetching the messages. You can freely configure how often the retry should occur and
how long the system should wait between retries. By default, the configuration will retry **4** times with different
waiting periods in between. The first retry occurs**immediately**, the second after **5ms**, the third after **50ms**,
and the final retry after **500ms**.

You can also configure the timeframe during which this gap detection is considered. For example, if you want to
re-create a projection and encountered a gap months ago, you might not want to retry and wait for several seconds if the
gap closes, since you already know it will never happen.

```php
$messageLoader = new GapResolverStoreMessageLoader(
    $store,
    $clock,
    [0, 5, 50, 500], // each entry is a retry and the wait time in ms
    new \DateInterval('PT5M'), // don’t retry if the message is older than 5 minutes
);
```

When using our [symfony bundle](/docs/event-sourcing-bundle/latest), you can
[enable](/docs/event-sourcing-bundle/latest/configuration#gap-detection) it like this:

```yaml
patchlevel_event_sourcing:
  subscription:
    gap_detection: ~
```

You can also configure it with custom settings:

```yaml
patchlevel_event_sourcing:
  subscription:
    gap_detection:
      retries_in_ms: [0, 500, 1000]
      detection_window: 'PT5M'
```

## Conclusion

Version [3.11.0](https://github.com/patchlevel/event-sourcing/releases/tag/3.11.0) of **patchlevel/event-sourcing**
introduces several valuable enhancements aimed at improving the overall developer experience and system reliability.
From refined console commands and the addition of a flexible, attribute-driven query bus, to a new gap detection
mechanism for subscriptions-this release addresses practical needs and enables more robust event-driven architectures.

Some of these improvements were inspired by community feedback, and contributions continue to be welcome. Whether you're
exploring the project or have ideas to share, feel free to open an issue or pull request. As always, be sure to check
out the [documentation](/docs/event-sourcing/latest) for further details and guidance.
