Skip to content

Commit 6a2ad46

Browse files
committed
test deep clone
1 parent c5e1037 commit 6a2ad46

5 files changed

Lines changed: 241 additions & 3 deletions

File tree

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"psr/cache": "^2.0.0 || ^3.0.0",
2525
"psr/simple-cache": "^2.0.0 || ^3.0.0",
2626
"symfony/event-dispatcher": "^5.4.29 || ^6.4.0 || ^7.0.0 || ^8.0.0",
27+
"symfony/polyfill-deepclone": "^1.37",
2728
"symfony/type-info": "^7.3.0 || ^8.0.0"
2829
},
2930
"require-dev": {

composer.lock

Lines changed: 88 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/CoreExtension.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
namespace Patchlevel\Hydrator;
66

77
use Patchlevel\Hydrator\Guesser\BuiltInGuesser;
8+
use Patchlevel\Hydrator\Middleware\SymfonyTransformMiddleware;
89
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
910

1011
/** @experimental */
1112
final class CoreExtension implements Extension
1213
{
1314
public function configure(StackHydratorBuilder $builder): void
1415
{
15-
$builder->addMiddleware(new TransformMiddleware(), -64);
16+
$builder->addMiddleware(new SymfonyTransformMiddleware(), -64);
1617
$builder->addGuesser(new BuiltInGuesser(), -64);
1718
}
1819
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\Hydrator\Middleware;
6+
7+
use Patchlevel\Hydrator\CircularReference;
8+
use Patchlevel\Hydrator\DenormalizationFailure;
9+
use Patchlevel\Hydrator\HydratorWithContext;
10+
use Patchlevel\Hydrator\Metadata\ClassMetadata;
11+
use Patchlevel\Hydrator\NormalizationFailure;
12+
use Patchlevel\Hydrator\Normalizer\NormalizerWithContext;
13+
use Throwable;
14+
15+
use function array_key_exists;
16+
use function array_values;
17+
use function spl_object_id;
18+
19+
/** @experimental */
20+
final class SymfonyTransformMiddleware implements Middleware
21+
{
22+
/** @var array<int, class-string> */
23+
private array $callStack = [];
24+
25+
/**
26+
* @param ClassMetadata<T> $metadata
27+
* @param array<string, mixed> $data
28+
* @param array<string, mixed> $context
29+
*
30+
* @return T
31+
*
32+
* @template T of object
33+
*/
34+
public function hydrate(ClassMetadata $metadata, array $data, array $context, Stack $stack): object
35+
{
36+
$constructorParameters = null;
37+
38+
$vars = [];
39+
40+
foreach ($metadata->properties() as $propertyMetadata) {
41+
/*
42+
if (!array_key_exists($propertyMetadata->fieldName(), $data)) {
43+
if (!$propertyMetadata->reflection->isPromoted()) {
44+
continue;
45+
}
46+
47+
$constructorParameters ??= $metadata->promotedConstructorDefaults();
48+
49+
if (!array_key_exists($propertyMetadata->propertyName, $constructorParameters)) {
50+
continue;
51+
}
52+
53+
$vars[$propertyMetadata->propertyName] = $constructorParameters[$propertyMetadata->propertyName]->getDefaultValue();
54+
55+
$propertyMetadata->setValue(
56+
$object,
57+
$constructorParameters[$propertyMetadata->propertyName]->getDefaultValue(),
58+
);
59+
60+
continue;
61+
}
62+
*/
63+
64+
if ($propertyMetadata->normalizer) {
65+
try {
66+
if ($propertyMetadata->normalizer instanceof NormalizerWithContext) {
67+
/** @psalm-suppress MixedAssignment */
68+
$vars[$propertyMetadata->propertyName] = $propertyMetadata->normalizer->denormalize($data[$propertyMetadata->fieldName], $context);
69+
} else {
70+
/** @psalm-suppress MixedAssignment */
71+
$vars[$propertyMetadata->propertyName] = $propertyMetadata->normalizer->denormalize($data[$propertyMetadata->fieldName]);
72+
}
73+
} catch (Throwable $e) {
74+
throw new DenormalizationFailure(
75+
$metadata->className,
76+
$propertyMetadata->propertyName,
77+
$propertyMetadata->normalizer::class,
78+
$e,
79+
);
80+
}
81+
} else {
82+
$vars[$propertyMetadata->propertyName] = $data[$propertyMetadata->fieldName];
83+
}
84+
}
85+
86+
return deepclone_hydrate(
87+
$context[HydratorWithContext::OBJECT_TO_POPULATE] ?? $metadata->className(),
88+
$vars,
89+
);
90+
}
91+
92+
/**
93+
* @param array<string, mixed> $context
94+
*
95+
* @return array<string, mixed>
96+
*/
97+
public function extract(ClassMetadata $metadata, object $object, array $context, Stack $stack): array
98+
{
99+
$objectId = spl_object_id($object);
100+
101+
if (array_key_exists($objectId, $this->callStack)) {
102+
$references = array_values($this->callStack);
103+
$references[] = $object::class;
104+
105+
throw new CircularReference($references);
106+
}
107+
108+
$this->callStack[$objectId] = $object::class;
109+
110+
try {
111+
$data = [];
112+
113+
foreach ($metadata->properties as $propertyMetadata) {
114+
if ($propertyMetadata->normalizer) {
115+
try {
116+
if ($propertyMetadata->normalizer instanceof NormalizerWithContext) {
117+
/** @psalm-suppress MixedAssignment */
118+
$data[$propertyMetadata->fieldName] = $propertyMetadata->normalizer->normalize(
119+
$propertyMetadata->getValue($object),
120+
$context,
121+
);
122+
} else {
123+
/** @psalm-suppress MixedAssignment */
124+
$data[$propertyMetadata->fieldName] = $propertyMetadata->normalizer->normalize(
125+
$propertyMetadata->getValue($object),
126+
);
127+
}
128+
} catch (CircularReference $e) {
129+
throw $e;
130+
} catch (Throwable $e) {
131+
throw new NormalizationFailure(
132+
$object::class,
133+
$propertyMetadata->propertyName,
134+
$propertyMetadata->normalizer::class,
135+
$e,
136+
);
137+
}
138+
} else {
139+
$data[$propertyMetadata->fieldName] = $propertyMetadata->getValue($object);
140+
}
141+
}
142+
} finally {
143+
unset($this->callStack[$objectId]);
144+
}
145+
146+
return $data;
147+
}
148+
}

src/StackHydrator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Patchlevel\Hydrator\Metadata\MetadataFactory;
1111
use Patchlevel\Hydrator\Middleware\Middleware;
1212
use Patchlevel\Hydrator\Middleware\Stack;
13+
use Patchlevel\Hydrator\Middleware\SymfonyTransformMiddleware;
1314
use Patchlevel\Hydrator\Middleware\TransformMiddleware;
1415
use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer;
1516
use ReflectionClass;
@@ -27,7 +28,7 @@ final class StackHydrator implements HydratorWithContext
2728
/** @param list<Middleware> $middlewares */
2829
public function __construct(
2930
private readonly MetadataFactory $metadataFactory = new AttributeMetadataFactory(),
30-
private readonly array $middlewares = [new TransformMiddleware()],
31+
private readonly array $middlewares = [new SymfonyTransformMiddleware()],
3132
private readonly bool $defaultLazy = false,
3233
) {
3334
if ($middlewares === []) {

0 commit comments

Comments
 (0)