88use Patchlevel \Hydrator \DenormalizationFailure ;
99use Patchlevel \Hydrator \Metadata \ClassMetadata ;
1010use Patchlevel \Hydrator \NormalizationFailure ;
11- use Patchlevel \Hydrator \NormalizationMissing ;
12- use Patchlevel \Hydrator \Normalizer \HydratorAwareNormalizer ;
1311use Patchlevel \Hydrator \TypeMismatch ;
1412use ReflectionParameter ;
1513use Throwable ;
1614use TypeError ;
1715
1816use function array_key_exists ;
19- use function is_object ;
17+ use function array_values ;
18+ use function spl_object_id ;
2019
2120final class TransformMiddleware implements Middleware
2221{
22+ /** @var array<int, class-string> */
23+ private array $ callStack = [];
24+
2325 /**
2426 * @param ClassMetadata<T> $metadata
2527 * @param array<string, mixed> $data
@@ -34,43 +36,44 @@ public function hydrate(ClassMetadata $metadata, array $data, Stack $stack): obj
3436
3537 $ constructorParameters = null ;
3638
37- foreach ($ metadata ->properties () as $ propertyMetadata ) {
38- if (!array_key_exists ($ propertyMetadata ->fieldName () , $ data )) {
39- if (!$ propertyMetadata ->reflection () ->isPromoted ()) {
39+ foreach ($ metadata ->properties as $ propertyMetadata ) {
40+ if (!array_key_exists ($ propertyMetadata ->fieldName , $ data )) {
41+ if (!$ propertyMetadata ->reflection ->isPromoted ()) {
4042 continue ;
4143 }
4244
4345 if ($ constructorParameters === null ) {
4446 $ constructorParameters = $ this ->promotedConstructorParametersWithDefaultValue ($ metadata );
4547 }
4648
47- if (!array_key_exists ($ propertyMetadata ->propertyName () , $ constructorParameters )) {
49+ if (!array_key_exists ($ propertyMetadata ->propertyName , $ constructorParameters )) {
4850 continue ;
4951 }
5052
51- /** @psalm-suppress MixedAssignment */
52- $ defaultValue = $ constructorParameters [$ propertyMetadata ->propertyName ()]->getDefaultValue ();
53- $ propertyMetadata ->setValue ($ object , $ defaultValue );
53+ $ propertyMetadata ->setValue (
54+ $ object ,
55+ $ constructorParameters [$ propertyMetadata ->propertyName ]->getDefaultValue (),
56+ );
5457
5558 continue ;
5659 }
5760
58- $ normalizer = $ propertyMetadata ->normalizer () ;
61+ $ normalizer = $ propertyMetadata ->normalizer ;
5962
6063 if ($ normalizer ) {
6164 try {
6265 /** @psalm-suppress MixedAssignment */
63- $ value = $ normalizer ->denormalize ($ data [$ propertyMetadata ->fieldName () ]);
66+ $ value = $ normalizer ->denormalize ($ data [$ propertyMetadata ->fieldName ]);
6467 } catch (Throwable $ e ) {
6568 throw new DenormalizationFailure (
6669 $ metadata ->className (),
67- $ propertyMetadata ->propertyName () ,
70+ $ propertyMetadata ->propertyName ,
6871 $ normalizer ::class,
6972 $ e ,
7073 );
7174 }
7275 } else {
73- $ value = $ data [$ propertyMetadata ->fieldName () ];
76+ $ value = $ data [$ propertyMetadata ->fieldName ];
7477 }
7578
7679 try {
@@ -84,44 +87,49 @@ public function hydrate(ClassMetadata $metadata, array $data, Stack $stack): obj
8487 }
8588 }
8689
87- foreach ($ metadata ->postHydrateCallbacks () as $ callback ) {
88- $ callback ->invoke ($ object );
89- }
90-
9190 return $ object ;
9291 }
9392
9493 /** @return array<string, mixed> */
9594 public function extract (ClassMetadata $ metadata , object $ object , Stack $ stack ): array
9695 {
97- foreach ($ metadata ->preExtractCallbacks () as $ callback ) {
98- $ callback ->invoke ($ object );
99- }
96+ $ objectId = spl_object_id ($ object );
10097
101- $ data = [];
98+ if (array_key_exists ($ objectId , $ this ->callStack )) {
99+ $ references = array_values ($ this ->callStack );
100+ $ references [] = $ object ::class;
102101
103- foreach ( $ metadata -> properties () as $ propertyMetadata ) {
104- $ normalizer = $ propertyMetadata -> normalizer ();
102+ throw new CircularReference ( $ references );
103+ }
105104
106- if ($ normalizer ) {
107- try {
108- /** @psalm-suppress MixedAssignment */
109- $ data [$ propertyMetadata ->fieldName ()] = $ normalizer ->normalize (
110- $ propertyMetadata ->getValue ($ object ),
111- );
112- } catch (CircularReference $ e ) {
113- throw $ e ;
114- } catch (Throwable $ e ) {
115- throw new NormalizationFailure (
116- $ object ::class,
117- $ propertyMetadata ->propertyName (),
118- $ normalizer ::class,
119- $ e ,
120- );
105+ $ this ->callStack [$ objectId ] = $ object ::class;
106+
107+ try {
108+ $ data = [];
109+
110+ foreach ($ metadata ->properties as $ propertyMetadata ) {
111+ if ($ propertyMetadata ->normalizer ) {
112+ try {
113+ /** @psalm-suppress MixedAssignment */
114+ $ data [$ propertyMetadata ->fieldName ] = $ propertyMetadata ->normalizer ->normalize (
115+ $ propertyMetadata ->getValue ($ object ),
116+ );
117+ } catch (CircularReference $ e ) {
118+ throw $ e ;
119+ } catch (Throwable $ e ) {
120+ throw new NormalizationFailure (
121+ $ object ::class,
122+ $ propertyMetadata ->propertyName ,
123+ $ propertyMetadata ->normalizer ::class,
124+ $ e ,
125+ );
126+ }
127+ } else {
128+ $ data [$ propertyMetadata ->fieldName ] = $ propertyMetadata ->getValue ($ object );
121129 }
122- } else {
123- $ data [$ propertyMetadata ->fieldName ()] = $ propertyMetadata ->getValue ($ object );
124130 }
131+ } finally {
132+ unset($ this ->callStack [$ objectId ]);
125133 }
126134
127135 return $ data ;
0 commit comments