Skip to content
Open
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 @@ -34,7 +34,7 @@
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"doctrine/dbal": "^4.4.0",
"doctrine/migrations": "^3.3.2",
"patchlevel/hydrator": "^1.8.0",
"patchlevel/hydrator": "^2.0.0",
"patchlevel/worker": "^1.4.0",
"psr/cache": "^2.0.0 || ^3.0.0",
"psr/clock": "^1.0",
Expand Down
23 changes: 13 additions & 10 deletions composer.lock

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

274 changes: 274 additions & 0 deletions docs/UPGRADE-4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,277 @@ and replaced with the following headers:

The `Patchlevel\EventSourcing\Schema\DoctrineSchemaSubscriber` has been removed.
use the `Patchlevel\EventSourcing\Schema\DoctrineSchemaListener` instead.

## Serializer

The library now uses `patchlevel/hydrator` 2.0. The `Patchlevel\Hydrator\MetadataHydrator`
has been removed. Build a hydrator with the `StackHydratorBuilder` and the `CoreExtension` instead.
Upcasting and crypto-shredding are no longer wired through the serializer factories,
you register them on the hydrator as middleware or extension.

before:

```php
use Patchlevel\Hydrator\Hydrator;
use Patchlevel\Hydrator\MetadataHydrator;

$hydrator = new MetadataHydrator();
```
after:

```php
use Patchlevel\Hydrator\CoreExtension;
use Patchlevel\Hydrator\Hydrator;
use Patchlevel\Hydrator\StackHydratorBuilder;

$hydrator = (new StackHydratorBuilder())
->useExtension(new CoreExtension())
->build();
```

### DefaultEventSerializer

`createFromPaths()` no longer accepts an `$upcaster` or a `$cryptographer` argument.
The second argument is now an optional `Hydrator`, a default one is built when it is `null`.
Register upcasting via the `UpcastExtension` and crypto-shredding via the `CryptographyExtension`
on the hydrator you pass in.

before:

```php
use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer;
use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster;
use Patchlevel\Hydrator\Cryptography\PayloadCryptographer;

/**
* @var Upcaster $upcaster
* @var PayloadCryptographer $cryptographer
*/
$serializer = DefaultEventSerializer::createFromPaths(
[__DIR__ . '/Events'],
$upcaster,
$cryptographer,
);
```
after:

```php
use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer;
use Patchlevel\Hydrator\CoreExtension;
use Patchlevel\Hydrator\Extension\Cryptography\BaseCryptographer;
use Patchlevel\Hydrator\Extension\Cryptography\CryptographyExtension;
use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyStore;
use Patchlevel\Hydrator\Extension\Upcast\Upcaster;
use Patchlevel\Hydrator\Extension\Upcast\UpcastExtension;
use Patchlevel\Hydrator\StackHydratorBuilder;

/**
* @var Upcaster $upcaster
* @var CipherKeyStore $cipherKeyStore
*/
$hydrator = (new StackHydratorBuilder())
->useExtension(new CoreExtension())
->useExtension(new CryptographyExtension(BaseCryptographer::createWithOpenssl($cipherKeyStore)))
->useExtension(new UpcastExtension(beforeEncoding: [$upcaster]))
->build();

$serializer = DefaultEventSerializer::createFromPaths(
[__DIR__ . '/Events'],
$hydrator,
);
```

### Upcasting

The event-sourcing upcasting classes have been removed in favor of the hydrator upcast extension:

* `Patchlevel\EventSourcing\Serializer\Upcast\Upcaster`
* `Patchlevel\EventSourcing\Serializer\Upcast\Upcast`
* `Patchlevel\EventSourcing\Serializer\Upcast\UpcasterChain`

Implement `Patchlevel\Hydrator\Extension\Upcast\Upcaster` (or use `CallbackUpcaster`) instead and register
your upcasters with the `UpcastExtension`. The upcaster no longer receives an `Upcast` object, it now
works on the payload array directly and selects the event by the class name from the metadata.
Renaming an event through an upcaster is no longer possible, use event aliases instead.

before:

```php
use Patchlevel\EventSourcing\Serializer\Upcast\Upcast;
use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster;

final class ProfileCreatedEmailLowerCastUpcaster implements Upcaster
{
public function __invoke(Upcast $upcast): Upcast
{
if ($upcast->eventName !== 'profile.created') {
return $upcast;
}

return $upcast->replacePayloadByKey('email', strtolower($upcast->payload['email']));
}
}
```
after:

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

final class ProfileCreatedEmailLowerCastUpcaster implements Upcaster
{
/**
* @param ClassMetadata<object> $metadata
* @param array<string, mixed> $data
* @param array<string, mixed> $context
*
* @return array<string, mixed>
*/
public function upcast(ClassMetadata $metadata, array $data, array $context): array
{
if ($metadata->className !== ProfileCreated::class) {
return $data;
}

$data['email'] = strtolower($data['email']);

return $data;
}
}
```

### DefaultHeadersSerializer

The `$hydrator` argument of the constructor, `createFromPaths()` and `createDefault()`
is now an optional `Hydrator` defaulting to `null`. The `MetadataHydrator` default has been removed.

## Snapshots

### DefaultSnapshotStore

The constructor no longer accepts an array of adapters as its first argument,
it now requires an `AdapterRepository`. Pass adapters as an array through `createDefault()` instead.

before:

```php
use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;

$snapshotStore = new DefaultSnapshotStore(['default' => $adapter]);
```
after:

```php
use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;

$snapshotStore = DefaultSnapshotStore::createDefault(['default' => $adapter]);
```

The `$cryptographer` argument of `createDefault()` has been replaced by an optional `Hydrator`.
Build the hydrator with the `CryptographyExtension` like for the event serializer.

before:

```php
use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
use Patchlevel\Hydrator\Cryptography\PayloadCryptographer;

/** @var PayloadCryptographer $cryptographer */
$snapshotStore = DefaultSnapshotStore::createDefault(
['default' => $adapter],
$cryptographer,
);
```
after:

```php
use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
use Patchlevel\Hydrator\Hydrator;

/** @var Hydrator $hydrator */
$snapshotStore = DefaultSnapshotStore::createDefault(
['default' => $adapter],
$hydrator,
);
```

## Sensitive Data

The crypto-shredding stack moved to the cryptography extension of `patchlevel/hydrator` 2.0.

### Attributes

The attributes moved namespace and `PersonalData` was renamed to `SensitiveData`:

* `Patchlevel\Hydrator\Attribute\DataSubjectId` -> `Patchlevel\Hydrator\Extension\Cryptography\Attribute\DataSubjectId`
* `Patchlevel\Hydrator\Attribute\PersonalData` -> `Patchlevel\Hydrator\Extension\Cryptography\Attribute\SensitiveData`

before:

```php
use Patchlevel\EventSourcing\Identifier\Uuid;
use Patchlevel\Hydrator\Attribute\DataSubjectId;
use Patchlevel\Hydrator\Attribute\PersonalData;

final class EmailChanged
{
public function __construct(
#[DataSubjectId]
public readonly Uuid $profileId,
#[PersonalData(fallback: 'unknown')]
public readonly string $email,
) {
}
}
```
after:

```php
use Patchlevel\EventSourcing\Identifier\Uuid;
use Patchlevel\Hydrator\Extension\Cryptography\Attribute\DataSubjectId;
use Patchlevel\Hydrator\Extension\Cryptography\Attribute\SensitiveData;

final class EmailChanged
{
public function __construct(
#[DataSubjectId]
public readonly Uuid $profileId,
#[SensitiveData(fallback: 'unknown')]
public readonly string $email,
) {
}
}
```

### DoctrineCipherKeyStore

The legacy `Patchlevel\EventSourcing\Cryptography\DoctrineCipherKeyStore` and
`Patchlevel\EventSourcing\Cryptography\ExtensionDoctrineCipherKeyStore` have been merged into a
single `Patchlevel\EventSourcing\Cryptography\DoctrineCipherKeyStore` that implements the new
`Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyStore`. The default table name changed
from `crypto_keys` to `cryptography_keys`, and keys are now stored per id with a subject index.

To erase the data of a subject, call `removeWithSubjectId()`, the `remove()` method now deletes by key id.

before:

```php
$cipherKeyStore->remove($subjectId);
```
after:

```php
$cipherKeyStore->removeWithSubjectId($subjectId);
```

:::danger
The key table layout changed (`crypto_keys` -> `cryptography_keys` with new columns).
Existing keys must be migrated, otherwise stored sensitive data can no longer be decrypted.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a note how to migrate

:::

### Cryptographer

`Patchlevel\Hydrator\Cryptography\PayloadCryptographer` and its implementations
(`PersonalDataPayloadCryptographer`, `SensitiveDataPayloadCryptographer`) have been removed.
Create a `Patchlevel\Hydrator\Extension\Cryptography\BaseCryptographer` and register it on the
hydrator through the `CryptographyExtension` (see the Serializer section above).
2 changes: 1 addition & 1 deletion docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ powered by the reliable Doctrine ecosystem and focused on developer experience.
* Automatic [snapshot](snapshots.md)-system to boost your performance
* [Split](split-stream.md) big aggregates into multiple streams
* Versioned and managed lifecycle of [subscriptions](subscription.md) like projections and processors
* Safe usage of [Personal Data](personal-data.md) with crypto-shredding
* Safe usage of [Personal Data](sensitive-data.md) with crypto-shredding
* Smooth [upcasting](upcasting.md) of old events
* Simple setup with [schema management](store.md) and [doctrine migration](store.md)
* Built in [cli commands](cli.md) with [symfony](https://symfony.com/)
Expand Down
4 changes: 2 additions & 2 deletions docs/normalizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ final class HotelCreated
}
```
:::note
If you have personal data, you can use [crypto-shredding](personal-data.md).
If you have personal data, you can use [crypto-shredding](sensitive_data.md).
:::

### Aggregate
Expand Down Expand Up @@ -459,4 +459,4 @@ final class DTO
* [How to define aggregates](aggregate.md)
* [How to define events](events.md)
* [How to snapshot aggregates](snapshots.md)
* [How to work with personal data](personal-data.md)
* [How to work with personal data](sensitive-data.md)
Loading
Loading