Skip to content

Commit 4656258

Browse files
authored
Merge pull request #139 from patchlevel/hydrator-builder
Extension API with Hydrator Builder
2 parents ec410f9 + 96de53d commit 4656258

16 files changed

Lines changed: 341 additions & 173 deletions

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ parameters:
103103
path: tests/Unit/Metadata/AttributeMetadataFactoryTest.php
104104

105105
-
106-
message: '#^Parameter \#1 \$class of method Patchlevel\\Hydrator\\MetadataHydrator\:\:hydrate\(\) expects class\-string\<Unknown\>, string given\.$#'
106+
message: '#^Parameter \#1 \$class of method Patchlevel\\Hydrator\\Hydrator\:\:hydrate\(\) expects class\-string\<Unknown\>, string given\.$#'
107107
identifier: argument.type
108108
count: 1
109109
path: tests/Unit/MetadataHydratorTest.php

src/CoreExtension.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator;
6+
7+
use Patchlevel\Hydrator\Guesser\BuiltInGuesser;
8+
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
9+
10+
final class CoreExtension implements Extension
11+
{
12+
public function configure(HydratorBuilder $builder): void
13+
{
14+
$builder->addMiddleware(new TransformMiddleware(), -64);
15+
$builder->addGuesser(new BuiltInGuesser(), -64);
16+
}
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Cryptography;
6+
7+
use Patchlevel\Hydrator\Extension;
8+
use Patchlevel\Hydrator\HydratorBuilder;
9+
10+
final class CryptographyExtension implements Extension
11+
{
12+
public function __construct(
13+
private readonly PayloadCryptographer $cryptography,
14+
) {
15+
}
16+
17+
public function configure(HydratorBuilder $builder): void
18+
{
19+
$builder->addMetadataEnricher(new CryptographyMetadataEnricher(), 64);
20+
$builder->addMiddleware(new CryptographyMiddleware($this->cryptography), 64);
21+
}
22+
}

src/Cryptography/CryptographyMetadataFactory.php renamed to src/Cryptography/CryptographyMetadataEnricher.php

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,18 @@
77
use Patchlevel\Hydrator\Attribute\DataSubjectId;
88
use Patchlevel\Hydrator\Attribute\SensitiveData;
99
use Patchlevel\Hydrator\Metadata\ClassMetadata;
10-
use Patchlevel\Hydrator\Metadata\MetadataFactory;
10+
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
1111
use ReflectionProperty;
1212

1313
use function array_key_exists;
1414

15-
final class CryptographyMetadataFactory implements MetadataFactory
15+
final class CryptographyMetadataEnricher implements MetadataEnricher
1616
{
17-
public function __construct(
18-
private readonly MetadataFactory $metadataFactory,
19-
) {
20-
}
21-
22-
public function metadata(string $class): ClassMetadata
17+
public function enrich(ClassMetadata $classMetadata): void
2318
{
24-
$metadata = $this->metadataFactory->metadata($class);
25-
2619
$subjectIdMapping = [];
2720

28-
foreach ($metadata->properties as $property) {
21+
foreach ($classMetadata->properties as $property) {
2922
$isSubjectId = false;
3023
$attributeReflectionList = $property->reflection->getAttributes(DataSubjectId::class);
3124

@@ -34,8 +27,8 @@ public function metadata(string $class): ClassMetadata
3427

3528
if (array_key_exists($subjectIdIdentifier, $subjectIdMapping)) {
3629
throw new DuplicateSubjectIdIdentifier(
37-
$metadata->className,
38-
$metadata->propertyForField($subjectIdMapping[$subjectIdIdentifier])->propertyName,
30+
$classMetadata->className,
31+
$classMetadata->propertyForField($subjectIdMapping[$subjectIdIdentifier])->propertyName,
3932
$property->propertyName,
4033
$subjectIdIdentifier,
4134
);
@@ -53,17 +46,17 @@ public function metadata(string $class): ClassMetadata
5346
}
5447

5548
if ($isSubjectId) {
56-
throw new SubjectIdAndSensitiveDataConflict($metadata->className, $property->propertyName);
49+
throw new SubjectIdAndSensitiveDataConflict($classMetadata->className, $property->propertyName);
5750
}
5851

5952
$property->extras[SensitiveDataInfo::class] = $sensitiveDataInfo;
6053
}
6154

62-
if ($subjectIdMapping !== []) {
63-
$metadata->extras[SubjectIdFieldMapping::class] = new SubjectIdFieldMapping($subjectIdMapping);
55+
if ($subjectIdMapping === []) {
56+
return;
6457
}
6558

66-
return $metadata;
59+
$classMetadata->extras[SubjectIdFieldMapping::class] = new SubjectIdFieldMapping($subjectIdMapping);
6760
}
6861

6962
private function sensitiveDataInfo(ReflectionProperty $reflectionProperty): SensitiveDataInfo|null

src/Extension.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator;
6+
7+
interface Extension
8+
{
9+
public function configure(HydratorBuilder $builder): void;
10+
}

src/HydratorBuilder.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator;
6+
7+
use Patchlevel\Hydrator\Guesser\ChainGuesser;
8+
use Patchlevel\Hydrator\Guesser\Guesser;
9+
use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory;
10+
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
11+
use Patchlevel\Hydrator\Middleware\Middleware;
12+
13+
use function array_merge;
14+
use function krsort;
15+
16+
final class HydratorBuilder
17+
{
18+
private bool $defaultLazy = false;
19+
20+
/** @var array<int, list<Middleware>> */
21+
private array $middlewares = [];
22+
23+
/** @var array<int, list<MetadataEnricher>> */
24+
private array $metadataEnrichers = [];
25+
26+
/** @var array<int, list<Guesser>> */
27+
private array $guessers = [];
28+
29+
/** @return $this */
30+
public function addMiddleware(Middleware $middleware, int $priority = 0): static
31+
{
32+
$this->middlewares[$priority][] = $middleware;
33+
34+
return $this;
35+
}
36+
37+
/** @return $this */
38+
public function addMetadataEnricher(MetadataEnricher $enricher, int $priority = 0): static
39+
{
40+
$this->metadataEnrichers[$priority][] = $enricher;
41+
42+
return $this;
43+
}
44+
45+
/** @return $this */
46+
public function addGuesser(Guesser $guesser, int $priority = 0): static
47+
{
48+
$this->guessers[$priority][] = $guesser;
49+
50+
return $this;
51+
}
52+
53+
public function enableDefaultLazy(bool $lazy = true): static
54+
{
55+
$this->defaultLazy = $lazy;
56+
57+
return $this;
58+
}
59+
60+
public function useExtension(Extension $extension): static
61+
{
62+
$extension->configure($this);
63+
64+
return $this;
65+
}
66+
67+
public function build(): Hydrator
68+
{
69+
krsort($this->middlewares);
70+
krsort($this->metadataEnrichers);
71+
krsort($this->guessers);
72+
73+
return new MetadataHydrator(
74+
new AttributeMetadataFactory(
75+
guesser: new ChainGuesser(array_merge(...$this->guessers)),
76+
),
77+
array_merge(...$this->middlewares),
78+
array_merge(...$this->metadataEnrichers),
79+
$this->defaultLazy,
80+
);
81+
}
82+
}

src/Metadata/AttributeMetadataFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function __construct(
4141
Guesser|null $guesser = null,
4242
) {
4343
$this->typeResolver = $typeResolver ?: TypeResolver::create();
44-
$this->guesser = $guesser ?: new BuiltInGuesser(false);
44+
$this->guesser = $guesser ?: new BuiltInGuesser();
4545
}
4646

4747
/**

src/Metadata/MetadataEnricher.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Metadata;
6+
7+
interface MetadataEnricher
8+
{
9+
public function enrich(ClassMetadata $classMetadata): void;
10+
}

src/MetadataHydrator.php

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,13 @@
44

55
namespace Patchlevel\Hydrator;
66

7-
use Patchlevel\Hydrator\Guesser\BuiltInGuesser;
8-
use Patchlevel\Hydrator\Guesser\ChainGuesser;
9-
use Patchlevel\Hydrator\Guesser\Guesser;
107
use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory;
118
use Patchlevel\Hydrator\Metadata\ClassMetadata;
129
use Patchlevel\Hydrator\Metadata\ClassNotFound;
10+
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
1311
use Patchlevel\Hydrator\Metadata\MetadataFactory;
1412
use Patchlevel\Hydrator\Middleware\Middleware;
1513
use Patchlevel\Hydrator\Middleware\Stack;
16-
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
1714
use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer;
1815
use ReflectionClass;
1916

@@ -26,10 +23,14 @@ final class MetadataHydrator implements Hydrator
2623
/** @var array<class-string, ClassMetadata> */
2724
private array $classMetadata = [];
2825

29-
/** @param list<Middleware> $middlewares */
26+
/**
27+
* @param list<Middleware> $middlewares
28+
* @param list<MetadataEnricher> $metadataEnrichers
29+
*/
3030
public function __construct(
3131
private readonly MetadataFactory $metadataFactory = new AttributeMetadataFactory(),
3232
private readonly array $middlewares = [],
33+
private readonly array $metadataEnrichers = [],
3334
private readonly bool $defaultLazy = false,
3435
) {
3536
}
@@ -110,33 +111,10 @@ private function metadata(string $class): ClassMetadata
110111
$property->normalizer->setHydrator($this);
111112
}
112113

113-
return $metadata;
114-
}
115-
116-
/**
117-
* @param list<Middleware> $additionalMiddleware
118-
* @param iterable<Guesser> $guessers
119-
*/
120-
public static function create(
121-
array $additionalMiddleware = [],
122-
iterable $guessers = [],
123-
bool $defaultLazy = false,
124-
): self {
125-
$guesser = new BuiltInGuesser();
126-
127-
if ($guessers !== []) {
128-
$guesser = new ChainGuesser([
129-
...$guessers,
130-
$guesser,
131-
]);
114+
foreach ($this->metadataEnrichers as $enricher) {
115+
$enricher->enrich($metadata);
132116
}
133117

134-
return new self(
135-
new AttributeMetadataFactory(
136-
guesser: $guesser,
137-
),
138-
[...$additionalMiddleware, new TransformMiddleware()],
139-
$defaultLazy,
140-
);
118+
return $metadata;
141119
}
142120
}

tests/Benchmark/HydratorBench.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
namespace Patchlevel\Hydrator\Tests\Benchmark;
66

7+
use Patchlevel\Hydrator\CoreExtension;
78
use Patchlevel\Hydrator\Hydrator;
8-
use Patchlevel\Hydrator\MetadataHydrator;
9+
use Patchlevel\Hydrator\HydratorBuilder;
910
use Patchlevel\Hydrator\Tests\Benchmark\Fixture\ProfileCreated;
1011
use Patchlevel\Hydrator\Tests\Benchmark\Fixture\ProfileId;
1112
use Patchlevel\Hydrator\Tests\Benchmark\Fixture\Skill;
@@ -18,7 +19,9 @@ final class HydratorBench
1819

1920
public function __construct()
2021
{
21-
$this->hydrator = MetadataHydrator::create();
22+
$this->hydrator = (new HydratorBuilder())
23+
->useExtension(new CoreExtension())
24+
->build();
2225
}
2326

2427
public function setUp(): void

0 commit comments

Comments
 (0)