Skip to content

Commit 389d85d

Browse files
committed
Fix recursive relations
1 parent a2c8d95 commit 389d85d

File tree

2 files changed

+108
-15
lines changed

2 files changed

+108
-15
lines changed

src/Mapping/Metadata/ClassMetadata.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,20 @@ public function __construct(
2929
* Contains a list of class fields available for normalization
3030
* and denormalization.
3131
*
32+
* @readonly
33+
* @psalm-readonly-allow-private-mutation
3234
* @var array<non-empty-string, PropertyMetadata>
3335
*/
34-
public readonly array $properties = [],
36+
public array $properties = [],
3537
/**
3638
* Gets {@see DiscriminatorMetadata} information about a class
3739
* discriminator map, or returns {@see null} if no such metadata
3840
* has been registered in the {@see ClassMetadata} instance.
41+
*
42+
* @readonly
43+
* @psalm-readonly-allow-private-mutation
3944
*/
40-
public readonly ?DiscriminatorMetadata $discriminator = null,
45+
public ?DiscriminatorMetadata $discriminator = null,
4146
/**
4247
* Gets information about the normalization method of an object.
4348
*

src/Mapping/Provider/MetadataReaderProvider.php

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737

3838
final class MetadataReaderProvider implements ProviderInterface
3939
{
40+
/**
41+
* @var array<class-string, ClassMetadata<object>>
42+
*/
43+
private array $references = [];
44+
4045
public function __construct(
4146
private readonly ReaderInterface $reader = new ReflectionReader(),
4247
private ?ExpressionLanguage $expression = null,
@@ -50,37 +55,120 @@ private function now(): ?int
5055
return $now?->getTimestamp();
5156
}
5257

58+
/**
59+
* @template TArg of object
60+
*
61+
* @param \ReflectionClass<TArg> $class
62+
*
63+
* @return ClassMetadata<TArg>
64+
*
65+
* @throws \Throwable
66+
*/
5367
public function getClassMetadata(
5468
\ReflectionClass $class,
5569
TypeRepositoryInterface $types,
5670
TypeParserInterface $parser,
5771
): ClassMetadata {
58-
$info = $this->reader->read($class);
72+
if (\PHP_VERSION_ID >= 80400) {
73+
/** @var ClassMetadata<TArg> */
74+
return $this->toProxyClassMetadata($class, $types, $parser);
75+
}
5976

60-
return $this->toClassMetadata($info, $types, $parser);
77+
/** @var ClassMetadata<TArg> */
78+
return $this->toLazyInitializedClassMetadata($class, $types, $parser);
6179
}
6280

6381
/**
64-
* @template T of object
82+
* @template TArg of object
6583
*
66-
* @param ClassInfo<T> $class
84+
* @param \ReflectionClass<TArg> $class
6785
*
68-
* @return ClassMetadata<T>
86+
* @return ClassMetadata<TArg>
6987
* @throws \Throwable
7088
*/
71-
private function toClassMetadata(
72-
ClassInfo $class,
89+
private function toProxyClassMetadata(
90+
\ReflectionClass $class,
91+
TypeRepositoryInterface $types,
92+
TypeParserInterface $parser,
93+
): ClassMetadata {
94+
/** @var ClassMetadata<TArg> */
95+
return $this->references[$class->name] ??=
96+
(new \ReflectionClass(ClassMetadata::class))
97+
->newLazyProxy(function () use ($class, $types, $parser): ClassMetadata {
98+
$info = $this->reader->read($class);
99+
100+
$metadata = new ClassMetadata(
101+
name: $info->name,
102+
properties: $this->toPropertiesMetadata(
103+
parent: $info,
104+
properties: $info->properties,
105+
types: $types,
106+
parser: $parser,
107+
),
108+
discriminator: $this->toOptionalDiscriminator(
109+
parent: $info,
110+
info: $info->discriminator,
111+
types: $types,
112+
parser: $parser,
113+
),
114+
isNormalizeAsArray: $info->isNormalizeAsArray,
115+
typeErrorMessage: $info->typeErrorMessage,
116+
createdAt: $this->now(),
117+
);
118+
119+
unset($this->references[$class->name]);
120+
121+
return $metadata;
122+
});
123+
}
124+
125+
/**
126+
* @template TArg of object
127+
*
128+
* @param \ReflectionClass<TArg> $class
129+
*
130+
* @return ClassMetadata<TArg>
131+
* @throws \Throwable
132+
*/
133+
private function toLazyInitializedClassMetadata(
134+
\ReflectionClass $class,
73135
TypeRepositoryInterface $types,
74136
TypeParserInterface $parser,
75137
): ClassMetadata {
76-
return new ClassMetadata(
77-
name: $class->name,
78-
properties: $this->toPropertiesMetadata($class, $class->properties, $types, $parser),
79-
discriminator: $this->toOptionalDiscriminator($class, $class->discriminator, $types, $parser),
80-
isNormalizeAsArray: $class->isNormalizeAsArray,
81-
typeErrorMessage: $class->typeErrorMessage,
138+
if (isset($this->references[$class->name])) {
139+
/** @var ClassMetadata<TArg> */
140+
return $this->references[$class->name];
141+
}
142+
143+
$info = $this->reader->read($class);
144+
145+
$this->references[$class->name] = $metadata = new ClassMetadata(
146+
name: $info->name,
147+
isNormalizeAsArray: $info->isNormalizeAsArray,
148+
typeErrorMessage: $info->typeErrorMessage,
82149
createdAt: $this->now(),
83150
);
151+
152+
/** @phpstan-ignore-next-line : Allow readonly writing */
153+
$metadata->properties = $this->toPropertiesMetadata(
154+
parent: $info,
155+
properties: $info->properties,
156+
types: $types,
157+
parser: $parser,
158+
);
159+
160+
/** @phpstan-ignore-next-line : Allow readonly writing */
161+
$metadata->discriminator = $this->toOptionalDiscriminator(
162+
parent: $info,
163+
info: $info->discriminator,
164+
types: $types,
165+
parser: $parser,
166+
);
167+
168+
unset($this->references[$class->name]);
169+
170+
/** @var ClassMetadata<TArg> */
171+
return $metadata;
84172
}
85173

86174
/**

0 commit comments

Comments
 (0)