@@ -120,6 +120,46 @@ public function presentFieldValue(FieldDefinition $field, mixed $value): mixed
120120 return $ fieldType ->presentValue ($ value , $ field );
121121 }
122122
123+ /**
124+ * Keep stored values shaped by the given field definitions without API
125+ * presentation transforms. Use with context-filtered definitions (e.g.
126+ * visible_api) so model toArray() can omit hidden keys while staying raw.
127+ *
128+ * @param Collection<int, FieldDefinition> $fields
129+ * @param array<string, mixed> $values
130+ * @return array<string, mixed>
131+ */
132+ public function project (Collection $ fields , array $ values ): array
133+ {
134+ $ projected = [];
135+
136+ foreach ($ fields as $ field ) {
137+ if (! array_key_exists ($ field ->name , $ values )) {
138+ continue ;
139+ }
140+
141+ $ projected [$ field ->name ] = $ this ->projectFieldValue ($ field , $ values [$ field ->name ]);
142+ }
143+
144+ return $ projected ;
145+ }
146+
147+ public function projectFieldValue (FieldDefinition $ field , mixed $ value ): mixed
148+ {
149+ $ fieldType = $ this ->fieldTypeRegistry ->get ($ field ->type );
150+
151+ if ($ fieldType ->hasSubFields () && is_array ($ value )) {
152+ return match ($ field ->type ) {
153+ 'repeater ' => $ this ->projectRepeaterRows ($ field , $ value ),
154+ 'flexible_content ' => $ this ->projectFlexibleContentItems ($ field , $ value ),
155+ 'clone ' => $ this ->projectCompoundRow ($ this ->clonedFieldGroupResolver ->compoundChildren ($ field ), $ value ),
156+ default => $ this ->projectCompoundRow ($ field ->children , $ value ),
157+ };
158+ }
159+
160+ return $ value ;
161+ }
162+
123163 public function persistFieldValue (FieldDefinition $ field , mixed $ value ): mixed
124164 {
125165 $ fieldType = $ this ->fieldTypeRegistry ->get ($ field ->type );
@@ -250,6 +290,60 @@ protected function presentFlexibleContentItems(FieldDefinition $field, array $it
250290 }, $ items ));
251291 }
252292
293+ /**
294+ * @param Collection<int, FieldDefinition> $children
295+ * @param array<string, mixed> $row
296+ * @return array<string, mixed>
297+ */
298+ protected function projectCompoundRow (Collection $ children , array $ row ): array
299+ {
300+ $ projected = [];
301+
302+ foreach ($ children as $ child ) {
303+ if (! array_key_exists ($ child ->name , $ row )) {
304+ continue ;
305+ }
306+
307+ $ projected [$ child ->name ] = $ this ->projectFieldValue ($ child , $ row [$ child ->name ]);
308+ }
309+
310+ return $ projected ;
311+ }
312+
313+ /**
314+ * @param array<int, array<string, mixed>> $rows
315+ * @return list<array<string, mixed>>
316+ */
317+ protected function projectRepeaterRows (FieldDefinition $ field , array $ rows ): array
318+ {
319+ return array_values (array_map (
320+ fn (array $ row ): array => $ this ->projectCompoundRow ($ field ->children , $ row ),
321+ $ rows ,
322+ ));
323+ }
324+
325+ /**
326+ * @param array<int, array<string, mixed>> $items
327+ * @return list<array<string, mixed>>
328+ */
329+ protected function projectFlexibleContentItems (FieldDefinition $ field , array $ items ): array
330+ {
331+ $ layouts = $ field ->layouts ()->keyBy ('name ' );
332+
333+ return array_values (array_map (function (array $ item ) use ($ layouts ): array {
334+ $ type = (string ) ($ item ['type ' ] ?? '' );
335+ $ data = is_array ($ item ['data ' ] ?? null ) ? $ item ['data ' ] : [];
336+ $ layout = $ layouts ->get ($ type );
337+
338+ return [
339+ 'type ' => $ type ,
340+ 'data ' => $ layout !== null
341+ ? $ this ->projectCompoundRow ($ layout ->children , $ data )
342+ : $ data ,
343+ ];
344+ }, $ items ));
345+ }
346+
253347 /**
254348 * @param iterable<int, Model> $models
255349 */
0 commit comments