@@ -42,6 +42,8 @@ class StructuralElementDenormalizer implements DenormalizerInterface, Denormaliz
4242
4343 private const ALREADY_CALLED = 'STRUCTURAL_DENORMALIZER_ALREADY_CALLED ' ;
4444
45+ private const PARENT_ELEMENT = 'STRUCTURAL_DENORMALIZER_PARENT_ELEMENT ' ;
46+
4547 private array $ object_cache = [];
4648
4749 public function __construct (
@@ -89,32 +91,55 @@ public function denormalize($data, string $type, ?string $format = null, array $
8991
9092 $ context [self ::ALREADY_CALLED ][] = $ data ;
9193
92-
93- /** @var AbstractStructuralDBElement $deserialized_entity */
94- $ deserialized_entity = $ this ->denormalizer ->denormalize ($ data , $ type , $ format , $ context );
94+ //In the first step, denormalize without children
95+ $ context_without_children = $ context ;
96+ $ context_without_children ['groups ' ] = array_filter (
97+ $ context_without_children ['groups ' ] ?? [],
98+ static fn ($ group ) => $ group !== 'include_children ' ,
99+ );
100+ //Also unset any parent element, to avoid infinite loops. We will set the parent element in the next step, when we denormalize the children
101+ unset($ context_without_children [self ::PARENT_ELEMENT ]);
102+ /** @var AbstractStructuralDBElement $entity */
103+ $ entity = $ this ->denormalizer ->denormalize ($ data , $ type , $ format , $ context_without_children );
104+
105+ //Assign the parent element to the denormalized entity, so it can be used in the denormalization of the children (e.g. for path generation)
106+ if (isset ($ context [self ::PARENT_ELEMENT ]) && $ context [self ::PARENT_ELEMENT ] instanceof $ entity && $ entity ->getID () === null ) {
107+ $ entity ->setParent ($ context [self ::PARENT_ELEMENT ]);
108+ }
95109
96110 //Check if we already have the entity in the database (via path)
97111 /** @var StructuralDBElementRepository<T> $repo */
98112 $ repo = $ this ->entityManager ->getRepository ($ type );
113+ $ deserialized_entity = $ entity ;
99114
100115 $ path = $ deserialized_entity ->getFullPath (AbstractStructuralDBElement::PATH_DELIMITER_ARROW );
101116 $ db_elements = $ repo ->getEntityByPath ($ path , AbstractStructuralDBElement::PATH_DELIMITER_ARROW );
102117 if ($ db_elements !== []) {
103118 //We already have the entity in the database, so we can return it
104- return end ($ db_elements );
119+ $ entity = end ($ db_elements );
105120 }
106121
107122
108123 //Check if we have created the entity in this request before (so we don't create multiple entities for the same path)
109124 //Entities get saved in the cache by type and path
110125 //We use a different cache for this then the objects created by a string value (saved in repo). However, that should not be a problem
111- //unless the user data has mixed structure between json data and a string path
126+ //unless the user data has mixed structure between JSON data and a string path
112127 if (isset ($ this ->object_cache [$ type ][$ path ])) {
113- return $ this ->object_cache [$ type ][$ path ];
128+ $ entity = $ this ->object_cache [$ type ][$ path ];
129+ } else {
130+ //Save the entity in the cache
131+ $ this ->object_cache [$ type ][$ path ] = $ deserialized_entity ;
114132 }
115133
116- //Save the entity in the cache
117- $ this ->object_cache [$ type ][$ path ] = $ deserialized_entity ;
134+ //In the next step we can denormalize the children, and add our children to the entity.
135+ if (in_array ('include_children ' , $ context ['groups ' ], true ) && isset ($ data ['children ' ]) && is_array ($ data ['children ' ])) {
136+ foreach ($ data ['children ' ] as $ child_data ) {
137+ $ child_entity = $ this ->denormalize ($ child_data , $ type , $ format , array_merge ($ context , [self ::PARENT_ELEMENT => $ entity ]));
138+ if ($ child_entity !== null && !$ entity ->getChildren ()->contains ($ child_entity )) {
139+ $ entity ->addChild ($ child_entity );
140+ }
141+ }
142+ }
118143
119144 //We don't have the entity in the database, so we have to persist it
120145 $ this ->entityManager ->persist ($ deserialized_entity );
0 commit comments