patchlevel / hydrator
Hydrator
Requires
- php: ~8.1.0 || ~8.2.0 || ~8.3.0
- ext-openssl: *
Requires (Dev)
- cspray/phinal: ^2.0.0
- infection/infection: ^0.27.8
- patchlevel/coding-standard: ^1.3.0
- phpbench/phpbench: ^1.2.15
- phpspec/prophecy-phpunit: ^2.1.0
- phpstan/phpstan: ^1.10.49
- phpunit/phpunit: ^10.5.2
- psalm/plugin-phpunit: ^0.19.0
- psr/cache: ^2.0.0|^3.0.0
- psr/simple-cache: ^2.0.0|^3.0.0
- roave/infection-static-analysis-plugin: ^1.34.0
- symfony/var-dumper: ^5.4.29|^6.4.0|^7.0.0
- vimeo/psalm: ^5.17.0
- 1.6.x-dev
- 1.5.x-dev
- 1.5.0
- 1.4.x-dev
- 1.4.1
- 1.4.0
- 1.3.x-dev
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.x-dev
- 1.2.0
- 1.1.x-dev
- 1.1.0
- 1.0.x-dev
- 1.0.0
- 1.0.0-beta5
- 1.0.0-beta4
- 1.0.0-beta3
- 1.0.0-beta2
- 1.0.0-beta1
- dev-renovate/phpunit-phpunit-11.x
- dev-renovate/infection-infection-0.x
- dev-renovate/lock-file-maintenance
- dev-guesser
- dev-union-object-normalizer
- dev-service-normalizer
- dev-fix-readme
This package is auto-updated.
Last update: 2024-09-23 03:43:08 UTC
README
Hydrator
使用这个库,您可以将数组中的对象转换为对象,并再次将对象转换回数组,重点关注从数据库到数据库的数据处理。现在,它已作为event-sourcing库的独立库外包。
安装
composer require patchlevel/hydrator
用法
要使用hydrator,只需创建其实例。
use Patchlevel\Hydrator\MetadataHydrator; $hydrator = new MetadataHydrator();
之后,您可以为任何类或对象进行填充。还可以使用属性提升对final
和readonly
类进行填充。
final readonly class ProfileCreated { public function __construct( public string $id, public string $name ) { } }
提取数据
要将对象转换为可序列化的数组,您可以使用extract
方法
$event = new ProfileCreated('1', 'patchlevel'); $data = $hydrator->extract($event);
[ 'id' => '1', 'name' => 'patchlevel' ]
填充对象
$event = $hydrator->hydrate( ProfileCreated::class, [ 'id' => '1', 'name' => 'patchlevel' ] ); $oldEvent == $event // true
规范器
有时您还需要提取或填充更复杂的对象。为此,您可以使用规范器。对于一些标准情况,我们已经提供了内置规范器。
数组
如果您有一个要规范化的对象列表,则必须单独规范化每个对象。这正是ArrayNormalizer
为您所做的事情。为了使用ArrayNormaliser
,您仍然需要指定应应用于单个对象的规范器。内部,它基本上执行了一个array_map
,然后对每个元素运行指定的规范器。
use Patchlevel\Hydrator\Normalizer\ArrayNormalizer; use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer; final class DTO { #[ArrayNormalizer(new DateTimeImmutableNormalizer())] public array $dates; }
注意
这里采用了数组的键。
DateTimeImmutable
如名称所示,使用DateTimeImmutable
规范器,您可以转换DateTimeImmutable对象并将其转换回字符串。
use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer; final class DTO { #[DateTimeImmutableNormalizer] public DateTimeImmutable $date; }
您也可以定义格式。您可以将其描述为字符串或使用现有的常量之一。默认为DateTimeImmutable::ATOM
。
use Patchlevel\Hydrator\Normalizer\DateTimeImmutableNormalizer; final class DTO { #[DateTimeImmutableNormalizer(format: DateTimeImmutable::RFC3339_EXTENDED)] public DateTimeImmutable $date; }
注意
您可以在php 文档中了解格式的结构。
DateTime
DateTime
规范器的工作方式与DateTime规范器完全相同。仅适用于DateTime对象。
use Patchlevel\Hydrator\Normalizer\DateTimeNormalizer; final class DTO { #[DateTimeNormalizer] public DateTime $date; }
您也可以在这里指定格式。默认为DateTime::ATOM
。
use Patchlevel\Hydrator\Normalizer\DateTimeNormalizer; final class DTO { #[DateTimeNormalizer(format: DateTime::RFC3339_EXTENDED)] public DateTime $date; }
注意
您可以在php 文档中了解格式的结构。
DateTimeZone
要规范化DateTimeZone
,可以使用DateTimeZoneNormalizer
。
use Patchlevel\Hydrator\Normalizer\DateTimeZoneNormalizer; final class DTO { #[DateTimeZoneNormalizer] public DateTimeZone $timeZone; }
枚举
受支持的枚举也可以进行规范化。为此,必须传递枚举FQCN,以便EnumNormalizer
知道它是哪个枚举。
use Patchlevel\Hydrator\Normalizer\EnumNormalizer; final class DTO { #[EnumNormalizer] public Status $status; }
对象
如果您有一个要规范化的复杂对象,则可以使用ObjectNormalizer
。这使用hydrator内部进行对象规范化。
use Patchlevel\Hydrator\Normalizer\ObjectNormalizer; final class DTO { #[ObjectNormalizer] public AnohterDto $anotherDto; #[ObjectNormalizer(AnohterDto::class)] public object $object; } final class AnotherDto { #[EnumNormalizer] public Status $status; }
警告
不支持循环引用,这会导致异常。
自定义规范器
由于我们只提供PHP原生事物的规范器,因此您必须为您自己的结构(如值对象)编写自己的规范器。
在我们的示例中,我们构建了一个应包含名称的值对象。
final class Name { private string $value; public function __construct(string $value) { if (strlen($value) < 3) { throw new NameIsToShortException($value); } $this->value = $value; } public function toString(): string { return $this->value; } }
为此,我们现在需要一个自定义规范器。此规范器必须实现Normalizer
接口。您还需要实现normalize
和denormalize
方法。最后,您必须允许规范器作为属性使用。
use Patchlevel\Hydrator\Normalizer\Normalizer; use Patchlevel\Hydrator\Normalizer\InvalidArgument; #[Attribute(Attribute::TARGET_PROPERTY)] class NameNormalizer implements Normalizer { public function normalize(mixed $value): string { if (!$value instanceof Name) { throw InvalidArgument::withWrongType(Name::class, $value); } return $value->toString(); } public function denormalize(mixed $value): ?Name { if ($value === null) { return null; } if (!is_string($value)) { throw InvalidArgument::withWrongType('string', $value); } return new Name($value); } }
警告
重要的是,规范化操作的结果必须是可序列化的!
现在我们也可以直接使用规范器。
final class DTO { #[NameNormalizer] public Name $name }
在类级别上定义规范器
您还可以在类级别上设置值对象的属性。为此,规范化器需要允许在类级别上设置。
use Patchlevel\Hydrator\Normalizer\Normalizer; use Patchlevel\Hydrator\Normalizer\InvalidArgument; #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS)] class NameNormalizer implements Normalizer { // ... same as before }
然后设置值对象的属性。
#[NameNormalizer] final class Name { // ... same as before }
之后,DTO可以看起来像这样。
final class DTO { public Name $name }
推断规范化器
我们还集成了一个过程,其中规范化器通过类型进行推断。这意味着您不需要在属性或类级别上定义规范化器。目前这仅适用于我们库定义的规范化器。但是有一些例外,即ObjectNormalizer
和ArrayNormalizer
。
这些规范化器可以被推断
DateTimeImmutableNormalization
DateTimeNormalization
DateTimeZoneNormalization
EnumNormalization
标准化名称
默认情况下,属性名称用于在规范化结果中命名字段。这可以通过NormalizedName
属性进行自定义。
use Patchlevel\Hydrator\Attribute\NormalizedName; final class DTO { #[NormalizedName('profile_name')] public string $name }
整个结构看起来像这样
[ 'profile_name' => 'David' ]
提示
您还可以通过保持序列化名称来重命名属性为事件,而不会破坏向后兼容性。
忽略
有时需要排除属性。您可以使用Ignore
属性来做到这一点。属性在提取和填充时都会被忽略。
use Patchlevel\Hydrator\Attribute\Ignore; readonly class ProfileCreated { public function __construct( public string $id, public string $name, #[Ignore] public string $ignoreMe, ) { } }
钩子
有时需要在提取或填充过程之前或之后执行某些操作。为此,我们有PreExtract
和PostHydrate
属性。
use Patchlevel\Hydrator\Attribute\PostHydrate; use Patchlevel\Hydrator\Attribute\PreExtract; readonly class Dto { #[PostHydrate] private function postHydrate(): void { // do something } #[PreExtract] private function preExtract(): void { // do something } }
密码学
库还提供了加密和解密个人数据的功能。
个人数据
首先,我们必须标记包含个人数据的字段。在我们的示例中,我们使用事件,但您也可以使用聚合体。
use Patchlevel\Hydrator\Attribute\PersonalData; final class DTO { #[PersonalData] public readonly string|null $email; }
如果信息无法解密,则插入回退值。默认回退值是null
。您可以通过设置fallback
参数来更改此值。在这种情况下,添加了unknown
。
use Patchlevel\Hydrator\Attribute\PersonalData; final class DTO { public function __construct( #[PersonalData(fallback: 'unknown')] public readonly string $email, ) { } }
[!DANGER]您必须在您的业务逻辑中处理此情况,例如聚合体和订阅。
警告
您需要定义一个主题ID以使用个人数据属性。
数据主体ID
为了使用正确的密钥,必须定义主题ID。没有主题ID,就无法加密或解密个人数据。
use Patchlevel\Hydrator\Attribute\DataSubjectId; use Patchlevel\Hydrator\Attribute\PersonalData; final class EmailChanged { public function __construct( #[DataSubjectId] public readonly string $personId, #[PersonalData(fallback: 'unknown')] public readonly string|null $email, ) { } }
警告
主题ID不能是个人数据。
配置密码学
在这里,我们向您展示如何配置密码学。
use Patchlevel\Hydrator\Cryptography\PersonalDataPayloadCryptographer; use Patchlevel\Hydrator\Cryptography\Store\CipherKeyStore; use Patchlevel\Hydrator\Metadata\Event\EventMetadataFactory; use Patchlevel\Hydrator\MetadataHydrator; $cipherKeyStore = new InMemoryCipherKeyStore(); $cryptographer = PersonalDataPayloadCryptographer::createWithOpenssl($cipherKeyStore); $hydrator = new MetadataHydrator(cryptographer: $cryptographer);
密钥存储库
密钥必须存储在某个地方。出于测试目的,我们提供了一个内存实现。
use Patchlevel\Hydrator\Cryptography\Cipher\CipherKey; use Patchlevel\Hydrator\Cryptography\Store\InMemoryCipherKeyStore; $cipherKeyStore = new InMemoryCipherKeyStore(); /** @var CipherKey $cipherKey */ $cipherKeyStore->store('foo-id', $cipherKey); $cipherKey = $cipherKeyStore->get('foo-id'); $cipherKeyStore->remove('foo-id');
因为我们不知道您想在何处存储密钥,所以我们没有提供任何其他实现。您应该使用数据库或密钥存储来做到这一点。为此,您必须实现CipherKeyStore
接口。
删除个人数据
要删除个人数据,您只需要从存储库中删除密钥。
$cipherKeyStore->remove('foo-id');