@@ -120,16 +120,37 @@ public function resolve(): ?string
120120 return null ;
121121 }
122122
123- if (!$ this ->property ->hasAttr ('example ' ) ||
123+ // No optional wrapping for required/non-nullable fields without an example (always generate a real value),
124+ // or for unique-items fields (optional fallback would break uniqueness).
125+ if (
126+ (($ this ->attribute ->isRequired () || $ this ->attribute ->nullable === false ) && !$ this ->property ->hasAttr ('example ' )) ||
124127 $ this ->property ->getAttr ('uniqueItems ' )
125128 ) {
129+ if ($ this ->attribute ->phpType === 'string ' && $ this ->attribute ->size ) {
130+ return 'substr( ' . $ result . ', 0, ' .$ this ->attribute ->size .') ' ;
131+ }
126132 return $ result ;
127133 }
128134
129135 $ example = $ this ->property ->getAttr ('example ' );
130136 $ example = VarExporter::export ($ example );
131137 $ example = preg_replace ('/\n/ ' , "\n " , $ example );
132- return str_replace ('$faker-> ' , '$faker->optional(0.92, ' . $ example . ')-> ' , $ result );
138+
139+ /**
140+ * $example must be the exact value that goes into the DB column, e.g. '2020-03-14 21:42:17'
141+ * for a datetime column — not a DateTime object or ISO string with timezone offset.
142+ * optional() without a default returns null on miss; all -> are made nullsafe so the whole
143+ * chain collapses to null, then ?? $example inserts the ready-to-store fallback value.
144+ *
145+ * Negative lookbehind prevents turning an existing ?-> into ??->
146+ */
147+ $ wrapped = str_replace ('$faker?-> ' , '$faker->optional(0.92)-> ' , preg_replace ('/(?<!\?)->/ ' , '?-> ' , $ result ));
148+
149+ if ($ this ->attribute ->phpType === 'string ' && $ this ->attribute ->size ) {
150+ return 'is_string($s = ' . $ wrapped . ') ? substr($s, 0, ' .$ this ->attribute ->size .') : ' . $ example ;
151+ }
152+
153+ return $ wrapped . ' ?? ' . $ example ;
133154 }
134155
135156 private function fakeForString (): ?string
@@ -161,8 +182,7 @@ private function fakeForString(): ?string
161182 return '$faker->title ' ;
162183 }
163184 if ($ this ->attribute ->primary || $ this ->attribute ->isReference ()) {
164- $ size = $ this ->attribute ->size ?? 255 ;
165- return 'substr($uniqueFaker->sha256, 0, ' . $ size . ') ' ;
185+ return '$uniqueFaker->sha256 ' ;
166186 }
167187
168188 $ patterns = [
@@ -197,22 +217,19 @@ private function fakeForString(): ?string
197217 '~(url|site|website|href)~i ' => '$faker->url ' ,
198218 '~(username|login)~i ' => '$faker->userName ' ,
199219 ];
200- $ size = $ this ->attribute ->size > 0 ? $ this ->attribute ->size : null ;
201220 foreach ($ patterns as $ pattern => $ fake ) {
202221 if (preg_match ($ pattern , $ this ->attribute ->columnName )) {
203- if ($ size ) {
204- return 'substr( ' . $ fake . ', 0, ' . $ size . ') ' ;
205- }
206222 return $ fake ;
207223 }
208224 }
209225
226+ $ size = $ this ->attribute ->size > 0 ? $ this ->attribute ->size : null ;
210227 if ($ size ) {
211228 $ method = 'text ' ;
212229 if ($ size < 5 ) {
213230 $ method = 'word ' ;
214231 }
215- return 'substr( $faker-> ' . $ method . '( ' . $ size . ' ), 0, ' . $ size . ') ' ;
232+ return '$faker-> ' . $ method . '( ' . $ size . ') ' ;
216233 }
217234 return '$faker->sentence ' ;
218235 }
@@ -389,10 +406,8 @@ private function reindentArrayMapForObject(string $code, int $depth): string
389406 }
390407 [$ body , $ count ] = [$ m [1 ], $ m [2 ]];
391408
392- // For nested array_map: wrapInArray places the inner return at 12 spaces and the inner closing
393- // brace at 8 spaces. We want the inner return at bodyIndent+4 and the inner brace at bodyIndent,
394- // so the shift is bodyIndent - 8. For simple (non-nested) bodies the shift is bodyIndent - 12.
395- $ shift = strlen ($ bodyIndent ) - (str_starts_with ($ body , 'return array_map( ' ) ? 8 : 12 );
409+ // wrapInArray shifts nested array_map bodies by 4 spaces, so all bodies now start at 12 spaces.
410+ $ shift = strlen ($ bodyIndent ) - 12 ;
396411 if ($ shift > 0 ) {
397412 $ body = preg_replace ('/\n/ ' , "\n" . str_repeat (' ' , $ shift ), $ body );
398413 }
@@ -443,8 +458,14 @@ public function handleOneOf(SpecObjectInterface $items, int $count): string
443458 public function wrapInArray (string $ aFaker , bool $ uniqueItems , int $ count , bool $ oneOf = false ): string
444459 {
445460 $ ret = $ oneOf ? '' : 'return ' ;
461+ $ inner = $ uniqueItems ? str_replace ('$faker-> ' , '$uniqueFaker-> ' , $ aFaker ) : $ aFaker ;
462+ // Only shift when the inner value is itself a nested array_map;
463+ // handleOneOf and fakeForObject results already carry correct indentation
464+ if (str_starts_with ($ inner , 'array_map( ' ) && str_contains ($ inner , PHP_EOL )) {
465+ $ inner = str_replace (PHP_EOL , PHP_EOL . ' ' , $ inner );
466+ }
446467 return 'array_map(function () use ($faker, $uniqueFaker) {
447- ' . $ ret . ( $ uniqueItems ? str_replace ( ' $faker-> ' , ' $uniqueFaker-> ' , $ aFaker ) : $ aFaker ) . ';
468+ ' . $ ret . $ inner . ';
448469 }, range(1, ' . $ count . ')) ' ;
449470 }
450471
0 commit comments