Skip to content

Commit 5f9cb0c

Browse files
committed
allow cache in hydrator builder & introduce EnrichingMetadataFactory
1 parent 738d17b commit 5f9cb0c

11 files changed

Lines changed: 382 additions & 25 deletions

phpstan-baseline.neon

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

111111
-
112-
message: '#^Parameter \#1 \$class of method Patchlevel\\Hydrator\\Hydrator\:\:hydrate\(\) expects class\-string\<Unknown\>, string given\.$#'
112+
message: '#^Parameter \#1 \$class of method Patchlevel\\Hydrator\\MetadataHydrator\:\:hydrate\(\) expects class\-string\<Unknown\>, string given\.$#'
113113
identifier: argument.type
114114
count: 1
115115
path: tests/Unit/MetadataHydratorTest.php

src/HydratorBuilder.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77
use Patchlevel\Hydrator\Guesser\ChainGuesser;
88
use Patchlevel\Hydrator\Guesser\Guesser;
99
use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory;
10+
use Patchlevel\Hydrator\Metadata\EnrichingMetadataFactory;
1011
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
12+
use Patchlevel\Hydrator\Metadata\Psr16MetadataFactory;
13+
use Patchlevel\Hydrator\Metadata\Psr6MetadataFactory;
1114
use Patchlevel\Hydrator\Middleware\Middleware;
15+
use Psr\Cache\CacheItemPoolInterface;
16+
use Psr\SimpleCache\CacheInterface;
1217

1318
use function array_merge;
1419
use function krsort;
@@ -26,6 +31,8 @@ final class HydratorBuilder
2631
/** @var array<int, list<Guesser>> */
2732
private array $guessers = [];
2833

34+
private CacheItemPoolInterface|CacheInterface|null $cache = null;
35+
2936
/** @return $this */
3037
public function addMiddleware(Middleware $middleware, int $priority = 0): static
3138
{
@@ -64,18 +71,37 @@ public function useExtension(Extension $extension): static
6471
return $this;
6572
}
6673

74+
public function setCache(CacheItemPoolInterface|CacheInterface|null $cache): static
75+
{
76+
$this->cache = $cache;
77+
78+
return $this;
79+
}
80+
6781
public function build(): Hydrator
6882
{
69-
krsort($this->middlewares);
70-
krsort($this->metadataEnrichers);
7183
krsort($this->guessers);
84+
krsort($this->metadataEnrichers);
85+
krsort($this->middlewares);
7286

73-
return new MetadataHydrator(
87+
$metadataFactory = new EnrichingMetadataFactory(
7488
new AttributeMetadataFactory(
7589
guesser: new ChainGuesser(array_merge(...$this->guessers)),
7690
),
77-
array_merge(...$this->middlewares),
7891
array_merge(...$this->metadataEnrichers),
92+
);
93+
94+
if ($this->cache instanceof CacheItemPoolInterface) {
95+
$metadataFactory = new Psr6MetadataFactory($metadataFactory, $this->cache);
96+
}
97+
98+
if ($this->cache instanceof CacheInterface) {
99+
$metadataFactory = new Psr16MetadataFactory($metadataFactory, $this->cache);
100+
}
101+
102+
return new MetadataHydrator(
103+
$metadataFactory,
104+
array_merge(...$this->middlewares),
79105
$this->defaultLazy,
80106
);
81107
}

src/Metadata/ClassMetadata.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* @psalm-type serialized array{
1111
* className: class-string,
12-
* properties: list<PropertyMetadata>,
12+
* properties: array<string, PropertyMetadata>,
1313
* lazy: bool|null,
1414
* extras: array<string, mixed>,
1515
* }
@@ -20,18 +20,29 @@ final class ClassMetadata
2020
/** @var class-string<T> */
2121
public readonly string $className;
2222

23+
/** @var array<string, PropertyMetadata> */
24+
public readonly array $properties;
25+
2326
/**
2427
* @param ReflectionClass<T> $reflection
2528
* @param list<PropertyMetadata> $properties
2629
* @param array<string, mixed> $extras
2730
*/
2831
public function __construct(
2932
public readonly ReflectionClass $reflection,
30-
public readonly array $properties = [],
33+
array $properties = [],
3134
public readonly bool|null $lazy = null,
3235
public array $extras = [],
3336
) {
3437
$this->className = $reflection->getName();
38+
39+
$map = [];
40+
41+
foreach ($properties as $property) {
42+
$map[$property->propertyName] = $property;
43+
}
44+
45+
$this->properties = $map;
3546
}
3647

3748
public function propertyForField(string $name): PropertyMetadata
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Metadata;
6+
7+
final readonly class EnrichingMetadataFactory implements MetadataFactory
8+
{
9+
/** @param iterable<MetadataEnricher> $enrichers */
10+
public function __construct(
11+
private MetadataFactory $factory,
12+
private iterable $enrichers,
13+
) {
14+
}
15+
16+
public function metadata(string $class): ClassMetadata
17+
{
18+
$metadata = $this->factory->metadata($class);
19+
20+
foreach ($this->enrichers as $enricher) {
21+
$enricher->enrich($metadata);
22+
}
23+
24+
return $metadata;
25+
}
26+
}

src/MetadataHydrator.php

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory;
88
use Patchlevel\Hydrator\Metadata\ClassMetadata;
99
use Patchlevel\Hydrator\Metadata\ClassNotFound;
10-
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
1110
use Patchlevel\Hydrator\Metadata\MetadataFactory;
1211
use Patchlevel\Hydrator\Middleware\Middleware;
1312
use Patchlevel\Hydrator\Middleware\Stack;
13+
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
1414
use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer;
1515
use ReflectionClass;
1616

@@ -23,14 +23,10 @@ final class MetadataHydrator implements Hydrator
2323
/** @var array<class-string, ClassMetadata> */
2424
private array $classMetadata = [];
2525

26-
/**
27-
* @param list<Middleware> $middlewares
28-
* @param list<MetadataEnricher> $metadataEnrichers
29-
*/
26+
/** @param list<Middleware> $middlewares */
3027
public function __construct(
3128
private readonly MetadataFactory $metadataFactory = new AttributeMetadataFactory(),
32-
private readonly array $middlewares = [],
33-
private readonly array $metadataEnrichers = [],
29+
private readonly array $middlewares = [new TransformMiddleware()],
3430
private readonly bool $defaultLazy = false,
3531
) {
3632
}
@@ -95,7 +91,7 @@ public function extract(object $object, array $context = []): array
9591
*
9692
* @template T of object
9793
*/
98-
private function metadata(string $class): ClassMetadata
94+
public function metadata(string $class): ClassMetadata
9995
{
10096
if (array_key_exists($class, $this->classMetadata)) {
10197
return $this->classMetadata[$class];
@@ -111,10 +107,6 @@ private function metadata(string $class): ClassMetadata
111107
$property->normalizer->setHydrator($this);
112108
}
113109

114-
foreach ($this->metadataEnrichers as $enricher) {
115-
$enricher->enrich($metadata);
116-
}
117-
118110
return $metadata;
119111
}
120112
}

tests/Unit/HydratorBuilderTest.php

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99
use Patchlevel\Hydrator\Guesser\Guesser;
1010
use Patchlevel\Hydrator\HydratorBuilder;
1111
use Patchlevel\Hydrator\Metadata\AttributeMetadataFactory;
12+
use Patchlevel\Hydrator\Metadata\EnrichingMetadataFactory;
1213
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
14+
use Patchlevel\Hydrator\Metadata\Psr16MetadataFactory;
15+
use Patchlevel\Hydrator\Metadata\Psr6MetadataFactory;
1316
use Patchlevel\Hydrator\MetadataHydrator;
1417
use Patchlevel\Hydrator\Middleware\Middleware;
1518
use PHPUnit\Framework\Attributes\CoversClass;
1619
use PHPUnit\Framework\TestCase;
20+
use Psr\Cache\CacheItemPoolInterface;
21+
use Psr\SimpleCache\CacheInterface;
1722
use ReflectionProperty;
1823

1924
#[CoversClass(HydratorBuilder::class)]
@@ -54,8 +59,13 @@ public function testAddMetadataEnricherWithPriority(): void
5459

5560
$hydrator = $builder->build();
5661

57-
$reflection = new ReflectionProperty(MetadataHydrator::class, 'metadataEnrichers');
58-
$enrichers = $reflection->getValue($hydrator);
62+
$reflection = new ReflectionProperty(MetadataHydrator::class, 'metadataFactory');
63+
$enrichingMetadataFactory = $reflection->getValue($hydrator);
64+
65+
self::assertInstanceOf(EnrichingMetadataFactory::class, $enrichingMetadataFactory);
66+
67+
$reflection = new ReflectionProperty(EnrichingMetadataFactory::class, 'enrichers');
68+
$enrichers = $reflection->getValue($enrichingMetadataFactory);
5969

6070
self::assertSame([$enricher2, $enricher1], $enrichers);
6171
}
@@ -72,7 +82,12 @@ public function testAddGuesserWithPriority(): void
7282
$hydrator = $builder->build();
7383

7484
$reflection = new ReflectionProperty(MetadataHydrator::class, 'metadataFactory');
75-
$metadataFactory = $reflection->getValue($hydrator);
85+
$enrichingMetadataFactory = $reflection->getValue($hydrator);
86+
87+
self::assertInstanceOf(EnrichingMetadataFactory::class, $enrichingMetadataFactory);
88+
89+
$reflection = new ReflectionProperty(EnrichingMetadataFactory::class, 'factory');
90+
$metadataFactory = $reflection->getValue($enrichingMetadataFactory);
7691

7792
self::assertInstanceOf(AttributeMetadataFactory::class, $metadataFactory);
7893

@@ -109,4 +124,34 @@ public function testUseExtension(): void
109124

110125
$builder->useExtension($extension);
111126
}
127+
128+
public function testCachePsr6(): void
129+
{
130+
$cache = $this->createMock(CacheItemPoolInterface::class);
131+
132+
$builder = new HydratorBuilder();
133+
$builder->setCache($cache);
134+
135+
$hydrator = $builder->build();
136+
137+
$reflection = new ReflectionProperty(MetadataHydrator::class, 'metadataFactory');
138+
$factory = $reflection->getValue($hydrator);
139+
140+
self::assertInstanceOf(Psr6MetadataFactory::class, $factory);
141+
}
142+
143+
public function testCachePsr16(): void
144+
{
145+
$cache = $this->createMock(CacheInterface::class);
146+
147+
$builder = new HydratorBuilder();
148+
$builder->setCache($cache);
149+
150+
$hydrator = $builder->build();
151+
152+
$reflection = new ReflectionProperty(MetadataHydrator::class, 'metadataFactory');
153+
$factory = $reflection->getValue($hydrator);
154+
155+
self::assertInstanceOf(Psr16MetadataFactory::class, $factory);
156+
}
112157
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Tests\Unit\Metadata;
6+
7+
use Patchlevel\Hydrator\Metadata\ClassMetadata;
8+
use Patchlevel\Hydrator\Metadata\PropertyMetadata;
9+
use PHPUnit\Framework\Attributes\CoversClass;
10+
use PHPUnit\Framework\TestCase;
11+
use ReflectionClass;
12+
use ReflectionProperty;
13+
14+
#[CoversClass(ClassMetadata::class)]
15+
final class ClassMetadataTest extends TestCase
16+
{
17+
public function testPropertiesHashmap(): void
18+
{
19+
$object = new class {
20+
public string $foo;
21+
public string $bar;
22+
};
23+
24+
$reflection = new ReflectionClass($object);
25+
$fooReflection = new ReflectionProperty($object, 'foo');
26+
$barReflection = new ReflectionProperty($object, 'bar');
27+
28+
$fooMetadata = new PropertyMetadata($fooReflection, 'foo_field');
29+
$barMetadata = new PropertyMetadata($barReflection, 'bar_field');
30+
31+
$classMetadata = new ClassMetadata(
32+
$reflection,
33+
[$fooMetadata, $barMetadata],
34+
);
35+
36+
self::assertCount(2, $classMetadata->properties);
37+
self::assertArrayHasKey('foo', $classMetadata->properties);
38+
self::assertArrayHasKey('bar', $classMetadata->properties);
39+
self::assertSame($fooMetadata, $classMetadata->properties['foo']);
40+
self::assertSame($barMetadata, $classMetadata->properties['bar']);
41+
}
42+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Tests\Unit\Metadata;
6+
7+
use Patchlevel\Hydrator\Metadata\ClassMetadata;
8+
use Patchlevel\Hydrator\Metadata\EnrichingMetadataFactory;
9+
use Patchlevel\Hydrator\Metadata\MetadataEnricher;
10+
use Patchlevel\Hydrator\Metadata\MetadataFactory;
11+
use PHPUnit\Framework\Attributes\CoversClass;
12+
use PHPUnit\Framework\TestCase;
13+
use ReflectionClass;
14+
use stdClass;
15+
16+
#[CoversClass(EnrichingMetadataFactory::class)]
17+
final class EnrichingMetadataFactoryTest extends TestCase
18+
{
19+
public function testMetadata(): void
20+
{
21+
$classMetadata = new ClassMetadata(new ReflectionClass(stdClass::class));
22+
23+
$innerFactory = $this->createMock(MetadataFactory::class);
24+
$innerFactory->expects(self::once())
25+
->method('metadata')
26+
->with(stdClass::class)
27+
->willReturn($classMetadata);
28+
29+
$enricher1 = $this->createMock(MetadataEnricher::class);
30+
$enricher1->expects(self::once())
31+
->method('enrich')
32+
->with($classMetadata);
33+
34+
$enricher2 = $this->createMock(MetadataEnricher::class);
35+
$enricher2->expects(self::once())
36+
->method('enrich')
37+
->with($classMetadata);
38+
39+
$factory = new EnrichingMetadataFactory(
40+
$innerFactory,
41+
[$enricher1, $enricher2],
42+
);
43+
44+
$result = $factory->metadata(stdClass::class);
45+
46+
self::assertSame($classMetadata, $result);
47+
}
48+
}

0 commit comments

Comments
 (0)