Here you will find some examples of how to use the bundle. But we provide only examples for specific symfony features.
You can find out more about event sourcing in the library documentation. This documentation is limited to bundle integration and configuration.
You can access the specific repositories using the RepositoryManager::get. Or inject directly the right repository via
argument name injection. For our aggregate Hotel it would be $hotelRepository.
namespace App\Hotel\Infrastructure\Controller;
use Patchlevel\EventSourcing\Aggregate\Uuid;
use Patchlevel\EventSourcing\Repository\Repository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
#[AsController]
final class HotelController
{
public function __construct(
/** @var Repository<Hotel> */
private readonly Repository $hotelRepository,
) {
}
public function doStuffAction(Uuid $hotelId): Response
{
$hotel = $this->hotelRepository->load($hotelId);
$hotel->doStuff();
$hotelRepository->save($hotel);
return new Response();
}
}A subscriber can be used to send an email when a guest is checked in:
namespace App\Hotel\Application\Subscriber;
use Patchlevel\EventSourcing\Attribute\Subscriber;
use Patchlevel\EventSourcing\Subscription\RunMode;
#[Subscriber('send_check_in_email', RunMode::FromNow)]
class SendCheckInEmailSubscriber
{
// ...
}If you have the symfony default service setting with autowireand autoconfiger enabled,
the subscriber is automatically recognized and registered at the Subscriber attribute.
Otherwise you have to define the subscriber in the symfony service file:
services:
App\Hotel\Application\Subscriber\SendCheckInEmailSubscriber:
tags:
- event_sourcing.subscriberA process can be for example used to send an email when a guest is checked in:
namespace App\Hotel\Application\Listener;
use App\Hotel\Domain\Event\GuestIsCheckedIn;
use Patchlevel\EventSourcing\Attribute\Subscribe;
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcingBundle\Attribute\AsListener;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use function sprintf;
#[AsListener]
final class SendCheckInEmailListener
{
private function __construct(private MailerInterface $mailer)
{
}
#[Subscribe(GuestIsCheckedIn::class)]
public function __invoke(Message $message): void
{
$event = $message->event();
$email = (new Email())
->from('noreply@patchlevel.de')
->to('hq@patchlevel.de')
->subject('Guest is checked in')
->text(sprintf('A new guest named "%s" is checked in', $event->guestName()));
$this->mailer->send($email);
}
}If you have the symfony default service setting with autowireand autoconfiger enabled,
the listener is automatically recognized and registered at the AsListener attribute.
Otherwise you have to define the listener in the symfony service file:
services:
App\Hotel\Application\Listener\SendCheckInEmailListener:
tags:
- event_sourcing.listenerYou can also determine the priority in which the listeners are executed.
The higher the priority, the earlier the listener is executed.
You have to add the tag manually and specify the priority.
namespace App\Hotel\Application\Listener;
#[AsListener(priority: 16)]
final class SendCheckInEmailListener
{
// ...
}services:
App\Hotel\Application\Listener\SendCheckInEmailListener:
autoconfigure: false
tags:
- name: event_sourcing.listener
priority: 16You have to deactivate the autoconfigure for this service,
otherwise the service will be added twice.
This bundle adds more Symfony specific normalizers in addition to the existing built-in normalizers.
You can find the other build-in normalizers here
The Hydrator can automatically determine the appropriate normalizer based on the data type and annotations. You don't have to specify the normalizer manually like in the example below.
With the Uid Normalizer, as the name suggests, you can convert Symfony Uuid and Ulid objects to a string and back again.
use Patchlevel\EventSourcingBundle\Normalizer\UidNormalizer;
use Symfony\Component\Uid\Uuid;
final class DTO
{
#[UidNormalizer]
public Uuid $id;
}The symfony uuid don't implement the AggregateId interface, so it can not be used as an aggregate id directly.
Use instead the Patchlevel\EventSourcing\Aggregate\Uuid class.
Use the Uuid implementation and IdNormalizer from the library to use it as an aggregate id.
With the DatePoint Normalizer, you can convert a DatePoint object to a string and back again.
use Patchlevel\EventSourcingBundle\Normalizer\DatePointNormalizer;
use Symfony\Component\Clock\DatePoint;
final class DTO
{
#[DatePointNormalizer]
public DatePoint $createdAt;
}use Patchlevel\EventSourcing\Serializer\Upcast\Upcast;
use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster;
final class ProfileCreatedEmailLowerCastUpcaster implements Upcaster
{
public function __invoke(Upcast $upcast): Upcast
{
// ignore if other event is processed
if ($upcast->eventName !== 'profile_created') {
return $upcast;
}
return $upcast->replacePayloadByKey('email', strtolower($upcast->payload['email']));
}
}If you have the symfony default service setting with autowireand autoconfigure enabled,
the upcaster is automatically recognized and registered at the Upcaster interface.
Otherwise you have to define the upcaster in the symfony service file:
services:
App\Upcaster\ProfileCreatedEmailLowerCastUpcaster:
tags:
- event_sourcing.upcasterWe want to add the header information which user was logged in when this event was generated.
use Patchlevel\EventSourcing\Message\Message;
use Patchlevel\EventSourcing\Repository\MessageDecorator\MessageDecorator;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
final class LoggedUserDecorator implements MessageDecorator
{
public function __construct(
private readonly TokenStorageInterface $tokenStorage,
) {
}
public function __invoke(Message $message): Message
{
$token = $this->tokenStorage->getToken();
if (!$token) {
return $message;
}
return $message->withHeader(new UserHeader($token->getUsername()));
}
}If you have the symfony default service setting with autowireand autoconfigure enabled,
the message decorator is automatically recognized and registered at the MessageDecorator interface.
Otherwise you have to define the message decorator in the symfony service file:
services:
App\Message\Decorator\LoggedUserDecorator:
tags:
- event_sourcing.message_decorator