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": "^1.21.2",
"patchlevel/hydrator": "^2.0.2",
"symfony/cache": "^6.4.0 || ^7.0.0 || ^8.0.0",
"symfony/config": "^6.4.0 || ^7.0.0 || ^8.0.0",
"symfony/console": "^6.4.1 || ^7.0.1 || ^8.0.0",
Expand Down
364 changes: 192 additions & 172 deletions composer.lock

Large diffs are not rendered by default.

23 changes: 16 additions & 7 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,24 +199,33 @@ final class DTO
## Upcasting

```php
use Patchlevel\EventSourcing\Serializer\Upcast\Upcast;
use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster;
use Patchlevel\Hydrator\Extension\Upcast\Upcaster;
use Patchlevel\Hydrator\Metadata\ClassMetadata;

final class ProfileCreatedEmailLowerCastUpcaster implements Upcaster
{
public function __invoke(Upcast $upcast): Upcast
/**
* @param array<string, mixed> $data
* @param array<string, mixed> $context
*
* @return array<string, mixed>
*/
public function upcast(ClassMetadata $metadata, array $data, array $context): array
{
// ignore if other event is processed
if ($upcast->eventName !== 'profile_created') {
return $upcast;
if ($metadata->className !== ProfileCreated::class) {
return $data;
}

return $upcast->replacePayloadByKey('email', strtolower($upcast->payload['email']));
$data['email'] = strtolower($data['email']);

return $data;
}
}
```
If you have the symfony default service setting with `autowire`and `autoconfigure` enabled,
the upcaster is automatically recognized and registered at the `Upcaster` interface.
the upcaster is automatically recognized and registered in the `UpcastExtension` of the hydrator.
The upcasters run right before the object is built, so encrypted fields are already decrypted.
Otherwise you have to define the upcaster in the symfony service file:

```yaml
Expand Down
2 changes: 1 addition & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ parameters:
path: src/Normalizer/UidNormalizer.php

-
message: '#^Method Fixtures\\DummyGuesser\:\:guess\(\) has parameter \$type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types\: T$#'
message: '#^Method Patchlevel\\EventSourcingBundle\\Tests\\Fixtures\\DummyGuesser\:\:guess\(\) has parameter \$type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: tests/Fixtures/DummyGuesser.php
Expand Down
12 changes: 12 additions & 0 deletions src/DependencyInjection/HydratorCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Patchlevel\EventSourcingBundle\DependencyInjection;

use Patchlevel\Hydrator\Extension\Upcast\UpcastExtension;
use Patchlevel\Hydrator\StackHydratorBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

Expand All @@ -14,12 +16,22 @@
/** @internal */
final class HydratorCompilerPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;

public function process(ContainerBuilder $container): void
{
if (!$container->has(StackHydratorBuilder::class)) {
return;
}

$upcasters = $this->findAndSortTaggedServices('event_sourcing.upcaster', $container);

if ($upcasters !== []) {
$container->register(UpcastExtension::class, UpcastExtension::class)
->setArguments([[], $upcasters])
->addTag('event_sourcing.hydrator.extension');
}

$builder = $container->getDefinition(StackHydratorBuilder::class);
$extensions = $container->findTaggedServiceIds('event_sourcing.hydrator.extension');

Expand Down
21 changes: 5 additions & 16 deletions src/DependencyInjection/PatchlevelEventSourcingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
use Patchlevel\EventSourcing\Console\Command\SubscriptionTeardownCommand;
use Patchlevel\EventSourcing\Console\Command\WatchCommand;
use Patchlevel\EventSourcing\Console\DoctrineHelper;
use Patchlevel\EventSourcing\Cryptography\ExtensionDoctrineCipherKeyStore;
use Patchlevel\EventSourcing\Cryptography\DoctrineCipherKeyStore;
use Patchlevel\EventSourcing\DecisionModel\DecisionModelBuilder;
use Patchlevel\EventSourcing\DecisionModel\EventAppender;
use Patchlevel\EventSourcing\DecisionModel\StoreDecisionModelBuilder;
Expand Down Expand Up @@ -91,8 +91,6 @@
use Patchlevel\EventSourcing\Serializer\Encoder\JsonEncoder;
use Patchlevel\EventSourcing\Serializer\EventSerializer;
use Patchlevel\EventSourcing\Serializer\EventTagExtractor;
use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster;
use Patchlevel\EventSourcing\Serializer\Upcast\UpcasterChain;
use Patchlevel\EventSourcing\Snapshot\Adapter\Psr16SnapshotAdapter;
use Patchlevel\EventSourcing\Snapshot\Adapter\Psr6SnapshotAdapter;
use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
Expand Down Expand Up @@ -145,6 +143,7 @@
use Patchlevel\Hydrator\Extension\Cryptography\CryptographyExtension;
use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyStore;
use Patchlevel\Hydrator\Extension\Lifecycle\LifecycleExtension;
use Patchlevel\Hydrator\Extension\Upcast\Upcaster;
use Patchlevel\Hydrator\Hydrator;
use Patchlevel\Hydrator\StackHydrator;
use Patchlevel\Hydrator\StackHydratorBuilder;
Expand Down Expand Up @@ -220,7 +219,6 @@ private function configureSerializer(array $config, ContainerBuilder $container)
new Reference(EventRegistry::class),
new Reference(Hydrator::class),
new Reference(Encoder::class),
new Reference(Upcaster::class),
]);

$container->setAlias(EventSerializer::class, DefaultEventSerializer::class);
Expand Down Expand Up @@ -597,13 +595,13 @@ private function configureHydrator(array $config, ContainerBuilder $container):
->addTag('event_sourcing.hydrator.extension');

if ($config['hydrator']['cryptography']['enabled']) {
$container->register(ExtensionDoctrineCipherKeyStore::class)
$container->register(DoctrineCipherKeyStore::class)
->setArguments([new Reference('event_sourcing.dbal_connection')])
->addTag('event_sourcing.doctrine_schema_configurator');

$container->setAlias(
CipherKeyStore::class,
ExtensionDoctrineCipherKeyStore::class,
DoctrineCipherKeyStore::class,
);

$container->register(BaseCryptographer::class)
Expand All @@ -616,11 +614,7 @@ private function configureHydrator(array $config, ContainerBuilder $container):
$container->setAlias(Cryptographer::class, BaseCryptographer::class);

$container->register(CryptographyExtension::class)
->setArguments([
new Reference(Cryptographer::class),
null,
true,
])
->setArguments([new Reference(Cryptographer::class)])
->addTag('event_sourcing.hydrator.extension');
}

Expand All @@ -642,11 +636,6 @@ private function configureUpcaster(ContainerBuilder $container): void
{
$container->registerForAutoconfiguration(Upcaster::class)
->addTag('event_sourcing.upcaster');

$container->register(UpcasterChain::class)
->setArguments([new TaggedIteratorArgument('event_sourcing.upcaster')]);

$container->setAlias(Upcaster::class, UpcasterChain::class);
}

private function configureMessageDecorator(ContainerBuilder $container): void
Expand Down
6 changes: 4 additions & 2 deletions src/Normalizer/DatePointNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public function __construct(
) {
}

public function normalize(mixed $value): string|null
/** @param array<string, mixed> $context */
public function normalize(mixed $value, array $context = []): string|null
{
if ($value === null) {
return null;
Expand All @@ -32,7 +33,8 @@ public function normalize(mixed $value): string|null
return $value->format($this->format);
}

public function denormalize(mixed $value): DatePoint|null
/** @param array<string, mixed> $context */
public function denormalize(mixed $value, array $context = []): DatePoint|null
{
if ($value === null) {
return null;
Expand Down
6 changes: 4 additions & 2 deletions src/Normalizer/UidNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public function __construct(
) {
}

public function normalize(mixed $value): string|null
/** @param array<string, mixed> $context */
public function normalize(mixed $value, array $context = []): string|null
{
if ($value === null) {
return null;
Expand All @@ -41,7 +42,8 @@ public function normalize(mixed $value): string|null
return (string)$value;
}

public function denormalize(mixed $value): Uuid|null
/** @param array<string, mixed> $context */
public function denormalize(mixed $value, array $context = []): Uuid|null
{
if ($value === null) {
return null;
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/DummyExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Fixtures;
namespace Patchlevel\EventSourcingBundle\Tests\Fixtures;

use Patchlevel\Hydrator\Extension;
use Patchlevel\Hydrator\StackHydratorBuilder;
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/DummyGuesser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace Fixtures;
namespace Patchlevel\EventSourcingBundle\Tests\Fixtures;

use Patchlevel\Hydrator\Guesser\Guesser;
use Patchlevel\Hydrator\Normalizer\Normalizer;
Expand Down
22 changes: 22 additions & 0 deletions tests/Fixtures/DummyUpcaster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Patchlevel\EventSourcingBundle\Tests\Fixtures;

use Patchlevel\Hydrator\Extension\Upcast\Upcaster;
use Patchlevel\Hydrator\Metadata\ClassMetadata;

final class DummyUpcaster implements Upcaster
{
/**
* @param array<string, mixed> $data
* @param array<string, mixed> $context
*
* @return array<string, mixed>
*/
public function upcast(ClassMetadata $metadata, array $data, array $context): array
{
return $data;
}
}
30 changes: 29 additions & 1 deletion tests/Unit/PatchlevelEventSourcingBundleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Doctrine\Migrations\Tools\Console\Command\StatusCommand;
use Doctrine\Persistence\ManagerRegistry;
use Fixtures\DummyExtension;
use InvalidArgumentException;
use Patchlevel\EventSourcing\Attribute\Aggregate;
use Patchlevel\EventSourcing\Attribute\Event;
Expand Down Expand Up @@ -102,6 +101,8 @@
use Patchlevel\EventSourcingBundle\Tests\Fixtures\CreateProfile;
use Patchlevel\EventSourcingBundle\Tests\Fixtures\CustomHeader;
use Patchlevel\EventSourcingBundle\Tests\Fixtures\DummyArgumentResolver;
use Patchlevel\EventSourcingBundle\Tests\Fixtures\DummyExtension;
use Patchlevel\EventSourcingBundle\Tests\Fixtures\DummyUpcaster;
use Patchlevel\EventSourcingBundle\Tests\Fixtures\Listener1;
use Patchlevel\EventSourcingBundle\Tests\Fixtures\Listener2;
use Patchlevel\EventSourcingBundle\Tests\Fixtures\Profile;
Expand All @@ -114,6 +115,7 @@
use Patchlevel\Hydrator\CoreExtension;
use Patchlevel\Hydrator\Extension\Cryptography\CryptographyExtension;
use Patchlevel\Hydrator\Extension\Lifecycle\LifecycleExtension;
use Patchlevel\Hydrator\Extension\Upcast\UpcastExtension;
use Patchlevel\Hydrator\Hydrator;
use Patchlevel\Hydrator\StackHydrator;
use PHPUnit\Framework\Attributes\RequiresMethod;
Expand Down Expand Up @@ -1390,6 +1392,32 @@ public function testHydrator(): void
);
}

public function testUpcaster(): void
{
$container = new ContainerBuilder();

$container->setDefinition(DummyUpcaster::class, new Definition(DummyUpcaster::class))
->setAutoconfigured(true);

$this->compileContainer(
$container,
[
'patchlevel_event_sourcing' => [
'connection' => ['service' => 'doctrine.dbal.eventstore_connection'],
],
],
);

self::assertEquals(
[[], [new Reference(DummyUpcaster::class)]],
$container->getDefinition(UpcastExtension::class)->getArguments(),
);
self::assertArrayHasKey(
UpcastExtension::class,
$container->findTaggedServiceIds('event_sourcing.hydrator.extension'),
);
}

public function testFullBuild(): void
{
$psrClock = $this->createMock(ClockInterface::class);
Expand Down
Loading