Skip to content

Commit 479d22d

Browse files
committed
inverse-relations test
1 parent 53dbeac commit 479d22d

70 files changed

Lines changed: 1047 additions & 233 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎src/lib/FakerStubResolver.php‎

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

‎tests/fixtures/blog.php‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,9 @@
117117
->setDescription('The User')
118118
->setFakerStub('$faker->randomElement(\app\models\User::find()->select("id")->column())'),
119119
'message' => (new Attribute('message', ['phpType' => 'array', 'dbType' => 'json', 'xDbType' => 'json']))
120-
->setRequired()->setDefault([])->setFakerStub('$faker->words()'),
120+
->setRequired()->setDefault([])->setXDescriptionIsComment(null)->setFakerStub('$faker->words()'),
121121
'meta_data' => (new Attribute('meta_data', ['phpType' => 'array', 'dbType' => 'json', 'xDbType' => 'json']))
122-
->setDefault([])->setFakerStub('array_map(function () use ($faker, $uniqueFaker) {
123-
return $faker->words();
124-
}, range(1, 4))'),
122+
->setDefault([])->setXDescriptionIsComment(null)->setFakerStub('[]'),
125123
'created_at' => (new Attribute('created_at',['phpType' => 'int', 'dbType' => 'integer']))
126124
->setRequired()->setFakerStub('$faker->unixTime'),
127125
],

‎tests/specs/blog/models/CommentFaker.php‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ public function generateModel($attributes = [])
3333
$model->post_id = $faker->randomElement(\app\models\Post::find()->select("id")->column());
3434
$model->author_id = $faker->randomElement(\app\models\User::find()->select("id")->column());
3535
$model->message = $faker->words();
36-
$model->meta_data = array_map(function () use ($faker, $uniqueFaker) {
37-
return $faker->words();
38-
}, range(1, 4));
36+
$model->meta_data = [];
3937
$model->created_at = $faker->unixTime;
4038
if (!is_callable($attributes)) {
4139
$model->setAttributes($attributes, false);

‎tests/specs/blog/models/base/Category.php‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,4 @@ public function getPosts()
3838
{
3939
return $this->hasMany(\app\models\Post::class, ['category_id' => 'id'])->inverseOf('category');
4040
}
41-
42-
# belongs to relation
43-
public function getPost()
44-
{
45-
return $this->hasOne(\app\models\Post::class, ['category_id' => 'id']);
46-
}
4741
}

‎tests/specs/blog/models/base/Post.php‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,4 @@ public function getComments()
6161
{
6262
return $this->hasMany(\app\models\Comment::class, ['post_id' => 'uid'])->inverseOf('post');
6363
}
64-
65-
# belongs to relation
66-
public function getComment()
67-
{
68-
return $this->hasOne(\app\models\Comment::class, ['post_id' => 'uid']);
69-
}
7064
}

‎tests/specs/blog/models/base/User.php‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* @property int $flags
1818
* @property string $created_at
1919
*
20+
* @property array|\app\models\Post[] $createdByPosts
21+
* @property array|\app\models\Comment[] $authorComments
2022
*/
2123
abstract class User extends \yii\db\ActiveRecord
2224
{
@@ -45,15 +47,15 @@ public function rules()
4547
];
4648
}
4749

48-
# belongs to relation
49-
public function getPost()
50+
# inverse relation
51+
public function getCreatedByPosts()
5052
{
51-
return $this->hasOne(\app\models\Post::class, ['created_by_id' => 'id']);
53+
return $this->hasMany(\app\models\Post::class, ['created_by_id' => 'id']);
5254
}
5355

54-
# belongs to relation
55-
public function getComment()
56+
# inverse relation
57+
public function getAuthorComments()
5658
{
57-
return $this->hasOne(\app\models\Comment::class, ['author_id' => 'id']);
59+
return $this->hasMany(\app\models\Comment::class, ['author_id' => 'id']);
5860
}
5961
}

‎tests/specs/blog_v2/migrations_mysql_db/m200000_000005_change_table_v2_comments.php‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ class m200000_000005_change_table_v2_comments extends \yii\db\Migration
77
{
88
public function up()
99
{
10-
$this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}');
1110
$this->dropForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}');
11+
$this->dropForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}');
1212
$this->addColumn('{{%v2_comments}}', 'user_id', $this->bigInteger()->null()->defaultValue(null)->after('post_id')->comment('The User'));
1313
$this->dropColumn('{{%v2_comments}}', 'author_id');
1414
$this->alterColumn('{{%v2_comments}}', 'message', $this->text()->notNull());
@@ -27,7 +27,7 @@ public function down()
2727
$this->alterColumn('{{%v2_comments}}', 'message', 'json NOT NULL');
2828
$this->addColumn('{{%v2_comments}}', 'author_id', $this->integer()->notNull()->after('post_id'));
2929
$this->dropColumn('{{%v2_comments}}', 'user_id');
30-
$this->addForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}', 'author_id', 'itt_v2_users', 'id');
3130
$this->addForeignKey('fk_v2_comments_post_id_v2_posts_uid', '{{%v2_comments}}', 'post_id', 'itt_v2_posts', 'uid');
31+
$this->addForeignKey('fk_v2_comments_author_id_v2_users_id', '{{%v2_comments}}', 'author_id', 'itt_v2_users', 'id');
3232
}
3333
}

‎tests/specs/blog_v2/models/base/Category.php‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,4 @@ public function getPosts()
3838
{
3939
return $this->hasMany(\app\models\Post::class, ['category_id' => 'id'])->inverseOf('category');
4040
}
41-
42-
# belongs to relation
43-
public function getPost()
44-
{
45-
return $this->hasOne(\app\models\Post::class, ['category_id' => 'id']);
46-
}
4741
}

‎tests/specs/blog_v2/models/base/Post.php‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,4 @@ public function getTags()
7373
return $this->hasMany(\app\models\Tag::class, ['id' => 'tag_id'])
7474
->viaTable('posts2tags', ['post_id' => 'id']);
7575
}
76-
77-
# belongs to relation
78-
public function getComment()
79-
{
80-
return $this->hasOne(\app\models\Comment::class, ['post_id' => 'id']);
81-
}
8276
}

‎tests/specs/blog_v2/models/base/User.php‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
* @property int $flags
1818
* @property string $created_at
1919
*
20+
* @property array|\app\models\Post[] $createdByPosts
21+
* @property array|\app\models\Comment[] $comments
2022
*/
2123
abstract class User extends \yii\db\ActiveRecord
2224
{
@@ -48,15 +50,15 @@ public function rules()
4850
];
4951
}
5052

51-
# belongs to relation
52-
public function getPost()
53+
# inverse relation
54+
public function getCreatedByPosts()
5355
{
54-
return $this->hasOne(\app\models\Post::class, ['created_by_id' => 'id']);
56+
return $this->hasMany(\app\models\Post::class, ['created_by_id' => 'id']);
5557
}
5658

57-
# belongs to relation
58-
public function getComment()
59+
# inverse relation
60+
public function getComments()
5961
{
60-
return $this->hasOne(\app\models\Comment::class, ['user_id' => 'id']);
62+
return $this->hasMany(\app\models\Comment::class, ['user_id' => 'id']);
6163
}
6264
}

0 commit comments

Comments
 (0)