@@ -122,6 +122,7 @@ public function resolve(): ?string
122122
123123 $ example = $ this ->property ->getAttr ('example ' );
124124 $ example = VarExporter ::export ($ example );
125+ $ example = preg_replace ('/\n/ ' , "\n " , $ example );
125126 return str_replace ('$faker-> ' , '$faker->optional(0.92, ' . $ example . ')-> ' , $ result );
126127 }
127128
@@ -284,7 +285,8 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st
284285 $ items = $ property ->items ;
285286
286287 if (!$ items ) {
287- return '[] ' ;
288+ // Required fields cannot use [] — Yii2's isEmpty() treats empty arrays as blank.
289+ return $ this ->attribute ->required ? $ this ->arbitraryArray () : '[] ' ;
288290 }
289291
290292 if ($ items instanceof Reference) {
@@ -299,8 +301,8 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st
299301 if ($ type === null ) {
300302 return $ this ->arbitraryArray ();
301303 }
302- $ aFaker = $ this ->aElementFaker ($ this ->property ->getProperty (), $ this ->attribute ->columnName );
303304 if (in_array ($ type , ['string ' , 'number ' , 'integer ' , 'boolean ' , 'array ' ])) {
305+ $ aFaker = $ this ->aElementFaker ($ this ->property ->getProperty (), $ this ->attribute ->columnName );
304306 return $ this ->wrapInArray ($ aFaker , $ uniqueItems , $ count );
305307 }
306308
@@ -316,6 +318,19 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st
316318 }
317319
318320 /**
321+ * Generates a PHP array literal string for an OpenAPI object property.
322+ * The output is embedded as PHP code in Faker fixture files, not as JSON.
323+ *
324+ * Flow: Faker assigns a PHP array to the model property → ActiveRecord passes it
325+ * to the DB driver → the driver JSON-encodes it before storing.
326+ * Result in DB:
327+ * PHP [] → json_encode([]) → [] (JSON array)
328+ * PHP ['key' => 'v'] → json_encode(['key' => 'v']) → {"key": "v"} (JSON object)
329+ *
330+ * A non-empty associative array correctly becomes a JSON object in the DB.
331+ * An empty PHP array always becomes [] in the DB, never {} — regardless of whether
332+ * the OpenAPI field is typed as "object" or "array". For test/faker data without
333+ * defined properties this is acceptable, as no schema is enforced.
319334 * @internal
320335 */
321336 public function fakeForObject (SpecObjectInterface $ items , int $ depth = 1 ): string
@@ -335,6 +350,9 @@ public function fakeForObject(SpecObjectInterface $items, int $depth = 1): strin
335350 $ result = $ this ->fakeForObject ($ prop , $ depth + 1 );
336351 } else {
337352 $ result = $ this ->aElementFaker (['items ' => $ prop ->getSerializableData ()], $ name );
353+ if (str_starts_with ($ result , 'array_map ' )) {
354+ $ result = $ this ->reindentArrayMapForObject ($ result , $ depth );
355+ }
338356 }
339357 $ parts [] = $ indent . '\'' . $ name . '\' => ' . $ result . ', ' ;
340358 }
@@ -344,6 +362,60 @@ public function fakeForObject(SpecObjectInterface $items, int $depth = 1): strin
344362 return $ props ;
345363 }
346364
365+ /**
366+ * Re-indents a compact wrapInArray() output string to match the correct depth inside fakeForObject().
367+ * wrapInArray() always uses hardcoded 12/8-space indentation; when its result is embedded as a
368+ * property value inside a fakeForObject() output at depth >= 1, the indentation must be adjusted.
369+ * For a nested array_map body the inner call is expanded to multi-line style via expandCompactArrayMap().
370+ */
371+ private function reindentArrayMapForObject (string $ code , int $ depth ): string
372+ {
373+ $ bodyIndent = str_repeat (' ' , $ depth + 4 );
374+ $ closeIndent = str_repeat (' ' , $ depth + 3 );
375+
376+ $ pat = '/^array_map\(function \(\) use \(\$faker, \$uniqueFaker\) \{\n (.*)\n \}, range\(1, (\d+)\)\)$/s ' ;
377+ if (!preg_match ($ pat , $ code , $ m )) {
378+ return $ code ;
379+ }
380+ [$ body , $ count ] = [$ m [1 ], $ m [2 ]];
381+
382+ if (str_starts_with ($ body , 'return array_map( ' )) {
383+ $ inner = substr ($ body , 7 , -1 ); // strip "return " prefix and trailing ";"
384+ $ expanded = $ this ->expandCompactArrayMap ($ inner , $ bodyIndent );
385+ return "array_map(function () use ( \$faker, \$uniqueFaker) { \n"
386+ . $ bodyIndent . "return {$ expanded }; \n"
387+ . $ closeIndent . "}, \n"
388+ . $ closeIndent . "range(1, {$ count })) " ;
389+ }
390+
391+ return "array_map(function () use ( \$faker, \$uniqueFaker) { \n"
392+ . $ bodyIndent . $ body . "\n"
393+ . $ closeIndent . "}, \n"
394+ . $ closeIndent . "range(1, {$ count })) " ;
395+ }
396+
397+ /**
398+ * Expands a compact wrapInArray() string (single-line function + range) into multi-line style,
399+ * using $baseIndent as the reference indentation level for the opening "array_map(" line.
400+ */
401+ private function expandCompactArrayMap (string $ code , string $ baseIndent ): string
402+ {
403+ $ pat = '/^array_map\(function \(\) use \(\$faker, \$uniqueFaker\) \{\n (.*)\n \}, range\(1, (\d+)\)\)$/s ' ;
404+ if (!preg_match ($ pat , $ code , $ m )) {
405+ return $ code ;
406+ }
407+ [$ body , $ count ] = [$ m [1 ], $ m [2 ]];
408+ $ funcIndent = $ baseIndent . ' ' ;
409+ $ innerIndent = $ baseIndent . ' ' ;
410+
411+ return "array_map( \n"
412+ . $ funcIndent . "function () use ( \$faker, \$uniqueFaker) { \n"
413+ . $ innerIndent . $ body . "\n"
414+ . $ funcIndent . "}, \n"
415+ . $ funcIndent . "range(1, {$ count }) \n"
416+ . $ baseIndent . ") " ;
417+ }
418+
347419 /**
348420 * This method must be only used incase of array
349421 * @param SpecObjectInterface $items
@@ -360,15 +432,28 @@ public function fakeForObject(SpecObjectInterface $items, int $depth = 1): strin
360432 public function handleOneOf (SpecObjectInterface $ items , int $ count ): string
361433 {
362434 $ result = '' ;
435+ $ indent = str_repeat (' ' , 12 );
363436 foreach ($ items ->oneOf as $ key => $ aDataType ) {
364437 /** @var Schema|Reference $aDataType */
365438
366439 $ inp = $ aDataType instanceof Reference ? $ aDataType : ['items ' => $ aDataType ->getSerializableData ()];
367440 $ aFaker = $ this ->aElementFaker ($ inp , $ this ->attribute ->columnName );
441+ /**
442+ * Each $dataTypeN gets its own line (12-space indent = wrapInArray body level).
443+ * wrapInArray output (array_map) gets +4 spaces on continuation lines (12→16, 8→12).
444+ * fakeForObject output (starts with "[") is left as-is — depth=1 already gives 16/12.
445+ * return goes on its own line.
446+ */
447+ if (str_contains ($ aFaker , PHP_EOL ) && !str_starts_with ($ aFaker , '[ ' )) {
448+ $ aFaker = str_replace (PHP_EOL , PHP_EOL . ' ' , $ aFaker );
449+ }
450+ if ($ result !== '' ) {
451+ $ result .= PHP_EOL . $ indent ;
452+ }
368453 $ result .= '$dataType ' . $ key . ' = ' . $ aFaker . '; ' ;
369454 }
370455 $ ct = count ($ items ->oneOf ) - 1 ;
371- $ result .= 'return ${"dataType".rand(0, ' . $ ct . ')} ' ;
456+ $ result .= PHP_EOL . $ indent . 'return ${"dataType".rand(0, ' . $ ct . ')} ' ;
372457 return $ result ;
373458 }
374459
0 commit comments