Skip to content

Commit 1c33a73

Browse files
committed
add extension api
1 parent ec410f9 commit 1c33a73

14 files changed

Lines changed: 302 additions & 117 deletions

src/CoreExtension.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator;
6+
7+
use Patchlevel\Hydrator\Guesser\BuiltInGuesser;
8+
use Patchlevel\Hydrator\Guesser\Guesser;
9+
use Patchlevel\Hydrator\Middleware\Middleware;
10+
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
11+
12+
final class CoreExtension implements MiddlewareProvider, GuesserProvider
13+
{
14+
/** @return iterable<Middleware|array{0: Middleware, 1?: int}> */
15+
public function middlewares(): iterable
16+
{
17+
yield new TransformMiddleware();
18+
}
19+
20+
/** @return iterable<Guesser|array{0: Guesser, 1?: int}> */
21+
public function guesser(): iterable
22+
{
23+
yield new BuiltInGuesser();
24+
}
25+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Cryptography;
6+
7+
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
8+
use Patchlevel\Hydrator\MetadataEnricherProvider;
9+
use Patchlevel\Hydrator\Middleware\Middleware;
10+
use Patchlevel\Hydrator\MiddlewareProvider;
11+
12+
final class CryptographyExtension implements MiddlewareProvider, MetadataEnricherProvider
13+
{
14+
public function __construct(
15+
private readonly PayloadCryptographer $cryptography,
16+
) {
17+
}
18+
19+
/** @return iterable<Middleware|array{0: Middleware, 1?: int}> */
20+
public function middlewares(): iterable
21+
{
22+
yield [new CryptographyMiddleware($this->cryptography), 64];
23+
}
24+
25+
/** @return iterable<MetadataEnricher|array{0: MetadataEnricher, 1?: int}> */
26+
public function metadataEnrichers(): iterable
27+
{
28+
yield [new CryptographyMetadataEnricher(), 64];
29+
}
30+
}

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/GuesserProvider.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator;
6+
7+
use Patchlevel\Hydrator\Guesser\Guesser;
8+
9+
interface GuesserProvider
10+
{
11+
/** @return iterable<Guesser|array{0: Guesser, 1?: int}> */
12+
public function guesser(): iterable;
13+
}

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/MetadataEnricherProvider.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator;
6+
7+
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
8+
9+
interface MetadataEnricherProvider
10+
{
11+
/** @return iterable<MetadataEnricher|array{0: MetadataEnricher, 1?: int}> */
12+
public function metadataEnrichers(): iterable;
13+
}

src/MetadataHydrator.php

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@
44

55
namespace Patchlevel\Hydrator;
66

7-
use Patchlevel\Hydrator\Guesser\BuiltInGuesser;
87
use Patchlevel\Hydrator\Guesser\ChainGuesser;
98
use Patchlevel\Hydrator\Guesser\Guesser;
109
use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory;
1110
use Patchlevel\Hydrator\Metadata\ClassMetadata;
1211
use Patchlevel\Hydrator\Metadata\ClassNotFound;
12+
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
1313
use Patchlevel\Hydrator\Metadata\MetadataFactory;
1414
use Patchlevel\Hydrator\Middleware\Middleware;
1515
use Patchlevel\Hydrator\Middleware\Stack;
16-
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
1716
use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer;
1817
use ReflectionClass;
1918

2019
use function array_key_exists;
20+
use function array_merge;
21+
use function krsort;
2122

2223
use const PHP_VERSION_ID;
2324

@@ -26,10 +27,14 @@ final class MetadataHydrator implements Hydrator
2627
/** @var array<class-string, ClassMetadata> */
2728
private array $classMetadata = [];
2829

29-
/** @param list<Middleware> $middlewares */
30+
/**
31+
* @param list<Middleware> $middlewares
32+
* @param list<MetadataEnricher> $metadataEnrichers
33+
*/
3034
public function __construct(
3135
private readonly MetadataFactory $metadataFactory = new AttributeMetadataFactory(),
3236
private readonly array $middlewares = [],
37+
private readonly array $metadataEnrichers = [],
3338
private readonly bool $defaultLazy = false,
3439
) {
3540
}
@@ -110,32 +115,68 @@ private function metadata(string $class): ClassMetadata
110115
$property->normalizer->setHydrator($this);
111116
}
112117

118+
foreach ($this->metadataEnrichers as $enricher) {
119+
$enricher->enrich($metadata);
120+
}
121+
113122
return $metadata;
114123
}
115124

116-
/**
117-
* @param list<Middleware> $additionalMiddleware
118-
* @param iterable<Guesser> $guessers
119-
*/
125+
/** @param iterable<MiddlewareProvider|MetadataEnricherProvider|GuesserProvider> $extensions */
120126
public static function create(
121-
array $additionalMiddleware = [],
122-
iterable $guessers = [],
127+
iterable $extensions = [],
123128
bool $defaultLazy = false,
124129
): self {
125-
$guesser = new BuiltInGuesser();
130+
$extensions = [...$extensions, new CoreExtension()];
131+
132+
$middlewares = [];
133+
$enrichers = [];
134+
$guessers = [];
135+
136+
foreach ($extensions as $extension) {
137+
if ($extension instanceof MiddlewareProvider) {
138+
foreach ($extension->middlewares() as $entry) {
139+
if ($entry instanceof Middleware) {
140+
$middlewares[0][] = $entry;
141+
} else {
142+
$middlewares[$entry[1] ?? 0][] = $entry[0];
143+
}
144+
}
145+
}
126146

127-
if ($guessers !== []) {
128-
$guesser = new ChainGuesser([
129-
...$guessers,
130-
$guesser,
131-
]);
147+
if ($extension instanceof MetadataEnricherProvider) {
148+
foreach ($extension->metadataEnrichers() as $entry) {
149+
if ($entry instanceof MetadataEnricher) {
150+
$enrichers[0][] = $entry;
151+
} else {
152+
$enrichers[$entry[1] ?? 0][] = $entry[0];
153+
}
154+
}
155+
}
156+
157+
if (!($extension instanceof GuesserProvider)) {
158+
continue;
159+
}
160+
161+
foreach ($extension->guesser() as $entry) {
162+
if ($entry instanceof Guesser) {
163+
$guessers[0][] = $entry;
164+
} else {
165+
$guessers[$entry[1] ?? 0][] = $entry[0];
166+
}
167+
}
132168
}
133169

170+
krsort($middlewares);
171+
krsort($enrichers);
172+
krsort($guessers);
173+
134174
return new self(
135175
new AttributeMetadataFactory(
136-
guesser: $guesser,
176+
guesser: new ChainGuesser([...array_merge(...$guessers)]),
137177
),
138-
[...$additionalMiddleware, new TransformMiddleware()],
178+
[...array_merge(...$middlewares)],
179+
[...array_merge(...$enrichers)],
139180
$defaultLazy,
140181
);
141182
}

src/MiddlewareProvider.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator;
6+
7+
use Patchlevel\Hydrator\Middleware\Middleware;
8+
9+
interface MiddlewareProvider
10+
{
11+
/** @return iterable<Middleware|array{0: Middleware, 1?: int}> */
12+
public function middlewares(): iterable;
13+
}

tests/Benchmark/HydratorWithCryptographyBench.php

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44

55
namespace Patchlevel\Hydrator\Tests\Benchmark;
66

7-
use Patchlevel\Hydrator\Cryptography\CryptographyMetadataFactory;
8-
use Patchlevel\Hydrator\Cryptography\CryptographyMiddleware;
7+
use Patchlevel\Hydrator\Cryptography\CryptographyExtension;
98
use Patchlevel\Hydrator\Cryptography\SensitiveDataPayloadCryptographer;
109
use Patchlevel\Hydrator\Cryptography\Store\InMemoryCipherKeyStore;
1110
use Patchlevel\Hydrator\Hydrator;
12-
use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory;
1311
use Patchlevel\Hydrator\MetadataHydrator;
14-
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
1512
use Patchlevel\Hydrator\Tests\Benchmark\Fixture\ProfileCreated;
1613
use Patchlevel\Hydrator\Tests\Benchmark\Fixture\ProfileId;
1714
use Patchlevel\Hydrator\Tests\Benchmark\Fixture\Skill;
@@ -28,13 +25,11 @@ public function __construct()
2825
{
2926
$this->store = new InMemoryCipherKeyStore();
3027

31-
$this->hydrator = new MetadataHydrator(
32-
new CryptographyMetadataFactory(new AttributeMetadataFactory()),
33-
[
34-
new CryptographyMiddleware(SensitiveDataPayloadCryptographer::createWithDefaultSettings($this->store)),
35-
new TransformMiddleware(),
36-
],
37-
);
28+
$this->hydrator = MetadataHydrator::create([
29+
new CryptographyExtension(
30+
SensitiveDataPayloadCryptographer::createWithDefaultSettings($this->store),
31+
),
32+
]);
3833
}
3934

4035
public function setUp(): void

0 commit comments

Comments
 (0)