Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"require": {
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"patchlevel/event-sourcing": "4.0.x-dev",
"patchlevel/hydrator": "^2.0.2",
"patchlevel/hydrator": "^2.0.3",
"symfony/cache": "^7.3.0 || ^8.0.0",
"symfony/config": "^7.3.0 || ^8.0.0",
"symfony/console": "^7.3.0 || ^8.0.0",
Expand Down
36 changes: 18 additions & 18 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ or [custom headers](/docs/event-sourcing/latest/message/#custom-headers),
read the library documentation.
:::

## Metadata Cache

Aggregates, events and subscribers are described with attributes.
The aggregates, events and headers are already collected when the container is compiled,
but their metadata is read via reflection at runtime.
In production, meaning without `kernel.debug`, this metadata is cached
in php files in the build directory, which are created by `cache:warmup`.
This includes the hydrator metadata of your events, headers and aggregates with snapshots.
Until then, it is created on every request.
There is nothing to configure, the cache is renewed with every new build, e.g. by `cache:clear`.

:::note
With `kernel.debug`, nothing is cached, so that changes to your classes are picked up immediately.
:::

## Connection

You have to specify the connection url to the event store.
Expand Down
81 changes: 81 additions & 0 deletions src/CacheWarmer/HydratorMetadataCacheWarmer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcingBundle\CacheWarmer;

use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootMetadataFactory;
use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry;
use Patchlevel\EventSourcing\Metadata\Event\EventRegistry;
use Patchlevel\EventSourcing\Metadata\Message\MessageHeaderRegistry;
use Patchlevel\Hydrator\StackHydratorBuilder;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;

use function array_filter;
use function array_unique;
use function array_values;
use function is_file;

/**
* Collects the hydrator metadata of the events, headers and aggregates with snapshots with the uncached factories
* and dumps them into the php file that is read by the hydrator metadata cache.
*
* @internal
*/
final class HydratorMetadataCacheWarmer implements CacheWarmerInterface
{
public function __construct(
private readonly StackHydratorBuilder $hydratorBuilder,
private readonly AggregateRootRegistry $aggregateRootRegistry,
private readonly EventRegistry $eventRegistry,
private readonly AggregateRootMetadataFactory $aggregateRootMetadataFactory,
private readonly MessageHeaderRegistry $messageHeaderRegistry,
private readonly string $phpArrayFile,
) {
}

public function isOptional(): bool
{
return true;
}

/** @return array<string> classes to preload */
public function warmUp(string $cacheDir, string|null $buildDir = null): array
{
// the file lives in the build dir, so it is already up to date if it exists
if (is_file($this->phpArrayFile)) {
return [];
}

// without deep cloning, the array adapter keeps the objects as they are, which can then be exported
$cache = new ArrayAdapter(0, false);

Check warning on line 54 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "DecrementInteger": @@ @@ } // without deep cloning, the array adapter keeps the objects as they are, which can then be exported - $cache = new ArrayAdapter(0, false); + $cache = new ArrayAdapter(-1, false); $hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build();

Check warning on line 54 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "IncrementInteger": @@ @@ } // without deep cloning, the array adapter keeps the objects as they are, which can then be exported - $cache = new ArrayAdapter(0, false); + $cache = new ArrayAdapter(1, false); $hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build();

Check warning on line 54 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "IncrementInteger": @@ @@ } // without deep cloning, the array adapter keeps the objects as they are, which can then be exported - $cache = new ArrayAdapter(0, false); + $cache = new ArrayAdapter(1, false); $hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build();

Check warning on line 54 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "DecrementInteger": @@ @@ } // without deep cloning, the array adapter keeps the objects as they are, which can then be exported - $cache = new ArrayAdapter(0, false); + $cache = new ArrayAdapter(-1, false); $hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build();

$hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build();

Check warning on line 56 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "CloneRemoval": @@ @@ // without deep cloning, the array adapter keeps the objects as they are, which can then be exported $cache = new ArrayAdapter(0, false); - $hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build(); + $hydrator = ($this->hydratorBuilder)->setCache($cache)->build(); // aggregates are only hydrated for snapshots, events and headers are hydrated when they are (de)serialized $hydratedClasses = array_values($this->messageHeaderRegistry->headerClasses());

Check warning on line 56 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "CloneRemoval": @@ @@ // without deep cloning, the array adapter keeps the objects as they are, which can then be exported $cache = new ArrayAdapter(0, false); - $hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build(); + $hydrator = ($this->hydratorBuilder)->setCache($cache)->build(); // aggregates are only hydrated for snapshots, events and headers are hydrated when they are (de)serialized $hydratedClasses = array_values($this->messageHeaderRegistry->headerClasses());

// aggregates are only hydrated for snapshots, events and headers are hydrated when they are (de)serialized
$hydratedClasses = array_values($this->messageHeaderRegistry->headerClasses());

Check warning on line 59 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayValues": @@ @@ $hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build(); // aggregates are only hydrated for snapshots, events and headers are hydrated when they are (de)serialized - $hydratedClasses = array_values($this->messageHeaderRegistry->headerClasses()); + $hydratedClasses = $this->messageHeaderRegistry->headerClasses(); foreach ($this->aggregateRootRegistry->aggregateClasses() as $aggregateClass) { if ($this->aggregateRootMetadataFactory->metadata($aggregateClass)->snapshot === null) {

Check warning on line 59 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayValues": @@ @@ $hydrator = (clone $this->hydratorBuilder)->setCache($cache)->build(); // aggregates are only hydrated for snapshots, events and headers are hydrated when they are (de)serialized - $hydratedClasses = array_values($this->messageHeaderRegistry->headerClasses()); + $hydratedClasses = $this->messageHeaderRegistry->headerClasses(); foreach ($this->aggregateRootRegistry->aggregateClasses() as $aggregateClass) { if ($this->aggregateRootMetadataFactory->metadata($aggregateClass)->snapshot === null) {

foreach ($this->aggregateRootRegistry->aggregateClasses() as $aggregateClass) {
if ($this->aggregateRootMetadataFactory->metadata($aggregateClass)->snapshot === null) {
continue;
}

$hydratedClasses[] = $aggregateClass;
}

foreach ($this->eventRegistry->eventClasses() as $eventClass) {
$hydratedClasses[] = $eventClass;
}

foreach (array_unique($hydratedClasses) as $hydratedClass) {

Check warning on line 73 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayUnique": @@ @@ $hydratedClasses[] = $eventClass; } - foreach (array_unique($hydratedClasses) as $hydratedClass) { + foreach ($hydratedClasses as $hydratedClass) { $hydrator->metadata($hydratedClass); }

Check warning on line 73 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayUnique": @@ @@ $hydratedClasses[] = $eventClass; } - foreach (array_unique($hydratedClasses) as $hydratedClass) { + foreach ($hydratedClasses as $hydratedClass) { $hydrator->metadata($hydratedClass); }
$hydrator->metadata($hydratedClass);
}

$values = array_filter($cache->getValues(), static fn (mixed $value): bool => $value !== null);

Check warning on line 77 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayFilter": @@ @@ $hydrator->metadata($hydratedClass); } - $values = array_filter($cache->getValues(), static fn (mixed $value): bool => $value !== null); + $values = $cache->getValues(); return (new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()))->warmUp($values); }

Check warning on line 77 in src/CacheWarmer/HydratorMetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayFilter": @@ @@ $hydrator->metadata($hydratedClass); } - $values = array_filter($cache->getValues(), static fn (mixed $value): bool => $value !== null); + $values = $cache->getValues(); return (new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()))->warmUp($values); }

return (new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()))->warmUp($values);
}
}
79 changes: 79 additions & 0 deletions src/CacheWarmer/MetadataCacheWarmer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcingBundle\CacheWarmer;

use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootMetadataFactory;
use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry;
use Patchlevel\EventSourcing\Metadata\AggregateRoot\Psr6AggregateRootMetadataFactory;
use Patchlevel\EventSourcing\Metadata\Event\EventMetadataFactory;
use Patchlevel\EventSourcing\Metadata\Event\EventRegistry;
use Patchlevel\EventSourcing\Metadata\Event\Psr6EventMetadataFactory;
use Patchlevel\EventSourcing\Metadata\Subscriber\Psr6SubscriberMetadataFactory;
use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadataFactory;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;

use function array_filter;
use function is_file;

/**
* Collects the metadata of the aggregates, events and subscribers with the uncached factories
* and dumps them into the php file that is read by the metadata cache.
*
* @internal
*/
final class MetadataCacheWarmer implements CacheWarmerInterface
{
/** @param list<class-string> $subscriberClasses */
public function __construct(
private readonly AggregateRootRegistry $aggregateRootRegistry,
private readonly EventRegistry $eventRegistry,
private readonly AggregateRootMetadataFactory $aggregateRootMetadataFactory,
private readonly EventMetadataFactory $eventMetadataFactory,
private readonly SubscriberMetadataFactory $subscriberMetadataFactory,
private readonly array $subscriberClasses,
private readonly string $phpArrayFile,
) {
}

public function isOptional(): bool
{
return true;
}

/** @return array<string> classes to preload */
public function warmUp(string $cacheDir, string|null $buildDir = null): array
{
// the file lives in the build dir, so it is already up to date if it exists
if (is_file($this->phpArrayFile)) {
return [];
}

// without deep cloning, the array adapter keeps the objects as they are, which can then be exported
$cache = new ArrayAdapter(0, false);

Check warning on line 57 in src/CacheWarmer/MetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "IncrementInteger": @@ @@ } // without deep cloning, the array adapter keeps the objects as they are, which can then be exported - $cache = new ArrayAdapter(0, false); + $cache = new ArrayAdapter(1, false); $aggregateRootMetadataFactory = new Psr6AggregateRootMetadataFactory($this->aggregateRootMetadataFactory, $cache); $eventMetadataFactory = new Psr6EventMetadataFactory($this->eventMetadataFactory, $cache);

Check warning on line 57 in src/CacheWarmer/MetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "DecrementInteger": @@ @@ } // without deep cloning, the array adapter keeps the objects as they are, which can then be exported - $cache = new ArrayAdapter(0, false); + $cache = new ArrayAdapter(-1, false); $aggregateRootMetadataFactory = new Psr6AggregateRootMetadataFactory($this->aggregateRootMetadataFactory, $cache); $eventMetadataFactory = new Psr6EventMetadataFactory($this->eventMetadataFactory, $cache);

Check warning on line 57 in src/CacheWarmer/MetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "IncrementInteger": @@ @@ } // without deep cloning, the array adapter keeps the objects as they are, which can then be exported - $cache = new ArrayAdapter(0, false); + $cache = new ArrayAdapter(1, false); $aggregateRootMetadataFactory = new Psr6AggregateRootMetadataFactory($this->aggregateRootMetadataFactory, $cache); $eventMetadataFactory = new Psr6EventMetadataFactory($this->eventMetadataFactory, $cache);

Check warning on line 57 in src/CacheWarmer/MetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "DecrementInteger": @@ @@ } // without deep cloning, the array adapter keeps the objects as they are, which can then be exported - $cache = new ArrayAdapter(0, false); + $cache = new ArrayAdapter(-1, false); $aggregateRootMetadataFactory = new Psr6AggregateRootMetadataFactory($this->aggregateRootMetadataFactory, $cache); $eventMetadataFactory = new Psr6EventMetadataFactory($this->eventMetadataFactory, $cache);

$aggregateRootMetadataFactory = new Psr6AggregateRootMetadataFactory($this->aggregateRootMetadataFactory, $cache);
$eventMetadataFactory = new Psr6EventMetadataFactory($this->eventMetadataFactory, $cache);
$subscriberMetadataFactory = new Psr6SubscriberMetadataFactory($this->subscriberMetadataFactory, $cache);

foreach ($this->aggregateRootRegistry->aggregateClasses() as $aggregateClass) {
$aggregateRootMetadataFactory->metadata($aggregateClass);
}

foreach ($this->eventRegistry->eventClasses() as $eventClass) {
$eventMetadataFactory->metadata($eventClass);
}

foreach ($this->subscriberClasses as $subscriberClass) {
$subscriberMetadataFactory->metadata($subscriberClass);
}

$values = array_filter($cache->getValues(), static fn (mixed $value): bool => $value !== null);

Check warning on line 75 in src/CacheWarmer/MetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayFilter": @@ @@ $subscriberMetadataFactory->metadata($subscriberClass); } - $values = array_filter($cache->getValues(), static fn (mixed $value): bool => $value !== null); + $values = $cache->getValues(); return (new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()))->warmUp($values); }

Check warning on line 75 in src/CacheWarmer/MetadataCacheWarmer.php

View workflow job for this annotation

GitHub Actions / Mutation tests (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayFilter": @@ @@ $subscriberMetadataFactory->metadata($subscriberClass); } - $values = array_filter($cache->getValues(), static fn (mixed $value): bool => $value !== null); + $values = $cache->getValues(); return (new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()))->warmUp($values); }

return (new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()))->warmUp($values);
}
}
45 changes: 45 additions & 0 deletions src/DependencyInjection/MetadataCacheWarmerCompilerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcingBundle\DependencyInjection;

use Patchlevel\EventSourcingBundle\CacheWarmer\MetadataCacheWarmer;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

use function array_keys;
use function array_unique;
use function array_values;
use function class_exists;
use function is_string;

/**
* Passes the subscriber classes to the warmer, so that the subscribers do not have to be instantiated.
*
* @internal
*/
final class MetadataCacheWarmerCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasDefinition(MetadataCacheWarmer::class)) {
return;
}

$subscriberClasses = [];

foreach (array_keys($container->findTaggedServiceIds('event_sourcing.subscriber')) as $id) {
$class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());

if (!is_string($class) || !class_exists($class)) {
continue;
}

$subscriberClasses[] = $class;
}

$container->getDefinition(MetadataCacheWarmer::class)
->setArgument(5, array_values(array_unique($subscriberClasses)));

Check warning on line 43 in src/DependencyInjection/MetadataCacheWarmerCompilerPass.php

View workflow job for this annotation

GitHub Actions / Mutation tests on diff (locked, 8.5, ubuntu-latest)

Escaped Mutant for Mutator "UnwrapArrayValues": @@ @@ } $container->getDefinition(MetadataCacheWarmer::class) - ->setArgument(5, array_values(array_unique($subscriberClasses))); + ->setArgument(5, array_unique($subscriberClasses)); } }
}
}
Loading
Loading