3737
3838final 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