@@ -327,3 +327,277 @@ and replaced with the following headers:
327327
328328The ` Patchlevel\EventSourcing\Schema\DoctrineSchemaSubscriber ` has been removed.
329329use the ` Patchlevel\EventSourcing\Schema\DoctrineSchemaListener ` instead.
330+
331+ ## Serializer
332+
333+ The library now uses ` patchlevel/hydrator ` 2.0. The ` Patchlevel\Hydrator\MetadataHydrator `
334+ has been removed. Build a hydrator with the ` StackHydratorBuilder ` and the ` CoreExtension ` instead.
335+ Upcasting and crypto-shredding are no longer wired through the serializer factories,
336+ you register them on the hydrator as middleware or extension.
337+
338+ before:
339+
340+ ``` php
341+ use Patchlevel\Hydrator\Hydrator;
342+ use Patchlevel\Hydrator\MetadataHydrator;
343+
344+ $hydrator = new MetadataHydrator();
345+ ```
346+ after:
347+
348+ ``` php
349+ use Patchlevel\Hydrator\CoreExtension;
350+ use Patchlevel\Hydrator\Hydrator;
351+ use Patchlevel\Hydrator\StackHydratorBuilder;
352+
353+ $hydrator = (new StackHydratorBuilder())
354+ ->useExtension(new CoreExtension())
355+ ->build();
356+ ```
357+
358+ ### DefaultEventSerializer
359+
360+ ` createFromPaths() ` no longer accepts an ` $upcaster ` or a ` $cryptographer ` argument.
361+ The second argument is now an optional ` Hydrator ` , a default one is built when it is ` null ` .
362+ Register upcasting via the ` UpcastExtension ` and crypto-shredding via the ` CryptographyExtension `
363+ on the hydrator you pass in.
364+
365+ before:
366+
367+ ``` php
368+ use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer;
369+ use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster;
370+ use Patchlevel\Hydrator\Cryptography\PayloadCryptographer;
371+
372+ /**
373+ * @var Upcaster $upcaster
374+ * @var PayloadCryptographer $cryptographer
375+ */
376+ $serializer = DefaultEventSerializer::createFromPaths(
377+ [__DIR__ . '/Events'],
378+ $upcaster,
379+ $cryptographer,
380+ );
381+ ```
382+ after:
383+
384+ ``` php
385+ use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer;
386+ use Patchlevel\Hydrator\CoreExtension;
387+ use Patchlevel\Hydrator\Extension\Cryptography\BaseCryptographer;
388+ use Patchlevel\Hydrator\Extension\Cryptography\CryptographyExtension;
389+ use Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyStore;
390+ use Patchlevel\Hydrator\Extension\Upcast\Upcaster;
391+ use Patchlevel\Hydrator\Extension\Upcast\UpcastExtension;
392+ use Patchlevel\Hydrator\StackHydratorBuilder;
393+
394+ /**
395+ * @var Upcaster $upcaster
396+ * @var CipherKeyStore $cipherKeyStore
397+ */
398+ $hydrator = (new StackHydratorBuilder())
399+ ->useExtension(new CoreExtension())
400+ ->useExtension(new CryptographyExtension(BaseCryptographer::createWithOpenssl($cipherKeyStore)))
401+ ->useExtension(new UpcastExtension(beforeEncoding: [$upcaster]))
402+ ->build();
403+
404+ $serializer = DefaultEventSerializer::createFromPaths(
405+ [__DIR__ . '/Events'],
406+ $hydrator,
407+ );
408+ ```
409+
410+ ### Upcasting
411+
412+ The event-sourcing upcasting classes have been removed in favor of the hydrator upcast extension:
413+
414+ * ` Patchlevel\EventSourcing\Serializer\Upcast\Upcaster `
415+ * ` Patchlevel\EventSourcing\Serializer\Upcast\Upcast `
416+ * ` Patchlevel\EventSourcing\Serializer\Upcast\UpcasterChain `
417+
418+ Implement ` Patchlevel\Hydrator\Extension\Upcast\Upcaster ` (or use ` CallbackUpcaster ` ) instead and register
419+ your upcasters with the ` UpcastExtension ` . The upcaster no longer receives an ` Upcast ` object, it now
420+ works on the payload array directly and selects the event by the class name from the metadata.
421+ Renaming an event through an upcaster is no longer possible, use event aliases instead.
422+
423+ before:
424+
425+ ``` php
426+ use Patchlevel\EventSourcing\Serializer\Upcast\Upcast;
427+ use Patchlevel\EventSourcing\Serializer\Upcast\Upcaster;
428+
429+ final class ProfileCreatedEmailLowerCastUpcaster implements Upcaster
430+ {
431+ public function __invoke(Upcast $upcast): Upcast
432+ {
433+ if ($upcast->eventName !== 'profile.created') {
434+ return $upcast;
435+ }
436+
437+ return $upcast->replacePayloadByKey('email', strtolower($upcast->payload['email']));
438+ }
439+ }
440+ ```
441+ after:
442+
443+ ``` php
444+ use Patchlevel\Hydrator\Extension\Upcast\Upcaster;
445+ use Patchlevel\Hydrator\Metadata\ClassMetadata;
446+
447+ final class ProfileCreatedEmailLowerCastUpcaster implements Upcaster
448+ {
449+ /**
450+ * @param ClassMetadata<object > $metadata
451+ * @param array<string , mixed > $data
452+ * @param array<string , mixed > $context
453+ *
454+ * @return array<string , mixed >
455+ */
456+ public function upcast(ClassMetadata $metadata, array $data, array $context): array
457+ {
458+ if ($metadata->className !== ProfileCreated::class) {
459+ return $data;
460+ }
461+
462+ $data['email'] = strtolower($data['email']);
463+
464+ return $data;
465+ }
466+ }
467+ ```
468+
469+ ### DefaultHeadersSerializer
470+
471+ The ` $hydrator ` argument of the constructor, ` createFromPaths() ` and ` createDefault() `
472+ is now an optional ` Hydrator ` defaulting to ` null ` . The ` MetadataHydrator ` default has been removed.
473+
474+ ## Snapshots
475+
476+ ### DefaultSnapshotStore
477+
478+ The constructor no longer accepts an array of adapters as its first argument,
479+ it now requires an ` AdapterRepository ` . Pass adapters as an array through ` createDefault() ` instead.
480+
481+ before:
482+
483+ ``` php
484+ use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
485+
486+ $snapshotStore = new DefaultSnapshotStore(['default' => $adapter]);
487+ ```
488+ after:
489+
490+ ``` php
491+ use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
492+
493+ $snapshotStore = DefaultSnapshotStore::createDefault(['default' => $adapter]);
494+ ```
495+
496+ The ` $cryptographer ` argument of ` createDefault() ` has been replaced by an optional ` Hydrator ` .
497+ Build the hydrator with the ` CryptographyExtension ` like for the event serializer.
498+
499+ before:
500+
501+ ``` php
502+ use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
503+ use Patchlevel\Hydrator\Cryptography\PayloadCryptographer;
504+
505+ /** @var PayloadCryptographer $cryptographer */
506+ $snapshotStore = DefaultSnapshotStore::createDefault(
507+ ['default' => $adapter],
508+ $cryptographer,
509+ );
510+ ```
511+ after:
512+
513+ ``` php
514+ use Patchlevel\EventSourcing\Snapshot\DefaultSnapshotStore;
515+ use Patchlevel\Hydrator\Hydrator;
516+
517+ /** @var Hydrator $hydrator */
518+ $snapshotStore = DefaultSnapshotStore::createDefault(
519+ ['default' => $adapter],
520+ $hydrator,
521+ );
522+ ```
523+
524+ ## Sensitive Data
525+
526+ The crypto-shredding stack moved to the cryptography extension of ` patchlevel/hydrator ` 2.0.
527+
528+ ### Attributes
529+
530+ The attributes moved namespace and ` PersonalData ` was renamed to ` SensitiveData ` :
531+
532+ * ` Patchlevel\Hydrator\Attribute\DataSubjectId ` -> ` Patchlevel\Hydrator\Extension\Cryptography\Attribute\DataSubjectId `
533+ * ` Patchlevel\Hydrator\Attribute\PersonalData ` -> ` Patchlevel\Hydrator\Extension\Cryptography\Attribute\SensitiveData `
534+
535+ before:
536+
537+ ``` php
538+ use Patchlevel\EventSourcing\Identifier\Uuid;
539+ use Patchlevel\Hydrator\Attribute\DataSubjectId;
540+ use Patchlevel\Hydrator\Attribute\PersonalData;
541+
542+ final class EmailChanged
543+ {
544+ public function __construct(
545+ #[DataSubjectId]
546+ public readonly Uuid $profileId,
547+ #[PersonalData(fallback: 'unknown')]
548+ public readonly string $email,
549+ ) {
550+ }
551+ }
552+ ```
553+ after:
554+
555+ ``` php
556+ use Patchlevel\EventSourcing\Identifier\Uuid;
557+ use Patchlevel\Hydrator\Extension\Cryptography\Attribute\DataSubjectId;
558+ use Patchlevel\Hydrator\Extension\Cryptography\Attribute\SensitiveData;
559+
560+ final class EmailChanged
561+ {
562+ public function __construct(
563+ #[DataSubjectId]
564+ public readonly Uuid $profileId,
565+ #[SensitiveData(fallback: 'unknown')]
566+ public readonly string $email,
567+ ) {
568+ }
569+ }
570+ ```
571+
572+ ### DoctrineCipherKeyStore
573+
574+ The legacy ` Patchlevel\EventSourcing\Cryptography\DoctrineCipherKeyStore ` and
575+ ` Patchlevel\EventSourcing\Cryptography\ExtensionDoctrineCipherKeyStore ` have been merged into a
576+ single ` Patchlevel\EventSourcing\Cryptography\DoctrineCipherKeyStore ` that implements the new
577+ ` Patchlevel\Hydrator\Extension\Cryptography\Store\CipherKeyStore ` . The default table name changed
578+ from ` crypto_keys ` to ` cryptography_keys ` , and keys are now stored per id with a subject index.
579+
580+ To erase the data of a subject, call ` removeWithSubjectId() ` , the ` remove() ` method now deletes by key id.
581+
582+ before:
583+
584+ ``` php
585+ $cipherKeyStore->remove($subjectId);
586+ ```
587+ after:
588+
589+ ``` php
590+ $cipherKeyStore->removeWithSubjectId($subjectId);
591+ ```
592+
593+ ::: danger
594+ The key table layout changed (` crypto_keys ` -> ` cryptography_keys ` with new columns).
595+ Existing keys must be migrated, otherwise stored sensitive data can no longer be decrypted.
596+ :::
597+
598+ ### Cryptographer
599+
600+ ` Patchlevel\Hydrator\Cryptography\PayloadCryptographer ` and its implementations
601+ (` PersonalDataPayloadCryptographer ` , ` SensitiveDataPayloadCryptographer ` ) have been removed.
602+ Create a ` Patchlevel\Hydrator\Extension\Cryptography\BaseCryptographer ` and register it on the
603+ hydrator through the ` CryptographyExtension ` (see the Serializer section above).
0 commit comments