Skip to content

Commit 4217459

Browse files
authoredMar 11, 2025··
Merge pull request #86 from php-openapi/23-consider-openapi-extension-x-no-relation-also-in-other-pertinent-place
Consider OpenAPI extension x-no-relation also in other pertinent place
2 parents 910e8f5 + 9f10d85 commit 4217459

File tree

18 files changed

+442
-30
lines changed

18 files changed

+442
-30
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ related objects, `x-no-relation` (type: boolean, default: false) is used.
350350

351351
This will not generate 'comments' column in database migrations. But it will generate `getComments()` relation in Yii model file.
352352

353-
In order to make it real database column, extension `x-no-relation` can be used.
353+
In order to make it real database column, OpenAPI extension `x-no-relation` can be used.
354354

355355
```yaml
356356
comments:

‎src/lib/ValidationRulesBuilder.php

+1-4
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,7 @@ private function prepareTypeScope():void
243243
if ($attribute->isReadOnly()) {
244244
continue;
245245
}
246-
// column/field/property with name `id` is considered as Primary Key by this library, and it is automatically handled by DB/Yii; so remove it from validation `rules()`
247-
if (in_array($attribute->columnName, ['id', $this->model->pkName]) ||
248-
in_array($attribute->propertyName, ['id', $this->model->pkName])
249-
) {
246+
if ($this->isIdColumn($attribute)) {
250247
continue;
251248
}
252249
if (/*$attribute->defaultValue === null &&*/ $attribute->isRequired()) {

‎src/lib/openapi/ComponentSchema.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
use cebe\yii2openapi\lib\SchemaToDatabase;
1515
use Generator;
1616
use Yii;
17-
use yii\helpers\Inflector;
18-
use yii\helpers\StringHelper;
1917
use function in_array;
2018

2119
class ComponentSchema
@@ -106,7 +104,9 @@ public function isRequiredProperty(string $propName):bool
106104

107105
public function isNonDb():bool
108106
{
109-
return isset($this->schema->{CustomSpecAttr::TABLE}) && $this->schema->{CustomSpecAttr::TABLE} === false;
107+
return
108+
isset($this->schema->{CustomSpecAttr::TABLE}) &&
109+
$this->schema->{CustomSpecAttr::TABLE} === false;
110110
}
111111

112112
public function resolveTableName(string $schemaName):string

‎src/lib/openapi/PropertySchema.php

+6
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ public function __construct(SpecObjectInterface $property, string $name, Compone
145145
$property = $this->property;
146146
}
147147

148+
// don't go reference part if `x-no-relation` is true
149+
if ($this->getAttr(CustomSpecAttr::NO_RELATION)) {
150+
return;
151+
}
152+
148153
if ($property instanceof Reference) {
149154
$this->initReference();
150155
} elseif (
@@ -497,6 +502,7 @@ public function guessDbType($forReference = false):string
497502
}
498503
return YiiDbSchema::TYPE_TEXT;
499504
case 'object':
505+
case 'array':
500506
{
501507
return YiiDbSchema::TYPE_JSON;
502508
}

‎tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000001_create_table_pets.php

+15-15
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,21 @@ public function up()
1111
'id' => $this->primaryKey(),
1212
'name' => $this->text()->notNull(),
1313
'age' => $this->integer()->null()->defaultValue(null),
14-
'tags' => $this->text()->null(),
15-
'tags_arbit' => $this->text()->null(),
16-
'number_arr' => $this->text()->null(),
17-
'number_arr_min_uniq' => $this->text()->null(),
18-
'int_arr' => $this->text()->null(),
19-
'int_arr_min_uniq' => $this->text()->null(),
20-
'bool_arr' => $this->text()->null(),
21-
'arr_arr_int' => $this->text()->null(),
22-
'arr_arr_str' => $this->text()->null(),
23-
'arr_arr_arr_str' => $this->text()->null(),
24-
'arr_of_obj' => $this->text()->null(),
25-
'user_ref_obj_arr' => $this->string()->null()->defaultValue(null),
26-
'one_of_arr' => $this->text()->null(),
27-
'one_of_arr_complex' => $this->text()->null(),
28-
'one_of_from_multi_ref_arr' => $this->text()->null(),
14+
'tags' => 'json NOT NULL',
15+
'tags_arbit' => 'json NOT NULL',
16+
'number_arr' => 'json NOT NULL',
17+
'number_arr_min_uniq' => 'json NOT NULL',
18+
'int_arr' => 'json NOT NULL',
19+
'int_arr_min_uniq' => 'json NOT NULL',
20+
'bool_arr' => 'json NOT NULL',
21+
'arr_arr_int' => 'json NOT NULL',
22+
'arr_arr_str' => 'json NOT NULL',
23+
'arr_arr_arr_str' => 'json NOT NULL',
24+
'arr_of_obj' => 'json NOT NULL',
25+
'user_ref_obj_arr' => 'json NOT NULL',
26+
'one_of_arr' => 'json NOT NULL',
27+
'one_of_arr_complex' => 'json NOT NULL',
28+
'one_of_from_multi_ref_arr' => 'json NOT NULL',
2929
]);
3030
}
3131

‎tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Pet.php

+1-7
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@
2323
* @property array $arr_arr_str
2424
* @property array $arr_arr_arr_str
2525
* @property array $arr_of_obj
26-
* @property User[] $user_ref_obj_arr
26+
* @property array $user_ref_obj_arr
2727
* @property array $one_of_arr
2828
* @property array $one_of_arr_complex
2929
* @property array $one_of_from_multi_ref_arr
3030
*
3131
* @property array|\app\models\User[] $userRefObjArrNormal
32-
* @property array|\app\models\User[] $userRefObjArr
3332
*/
3433
abstract class Pet extends \yii\db\ActiveRecord
3534
{
@@ -53,9 +52,4 @@ public function getUserRefObjArrNormal()
5352
{
5453
return $this->hasMany(\app\models\User::class, ['pet_id' => 'id'])->inverseOf('pet');
5554
}
56-
57-
public function getUserRefObjArr()
58-
{
59-
return $this->hasMany(\app\models\User::class, ['pet_id' => 'id'])->inverseOf('pet');
60-
}
6155
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
return [
4+
'openApiPath' => '@specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.yaml',
5+
'generateUrls' => false,
6+
'generateModels' => true,
7+
'excludeModels' => [
8+
'Error',
9+
],
10+
'generateControllers' => false,
11+
'generateMigrations' => true,
12+
'generateModelFaker' => true, // `generateModels` must be `true` in order to use `generateModelFaker` as `true`
13+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
openapi: 3.0.3
2+
3+
info:
4+
title: '#23'
5+
version: 1.0.0
6+
7+
paths:
8+
/:
9+
get:
10+
responses:
11+
'200':
12+
description: The Response
13+
14+
15+
16+
components:
17+
schemas:
18+
Payments:
19+
properties:
20+
id:
21+
type: integer
22+
currency:
23+
type: string
24+
samples:
25+
type: array
26+
x-no-relation: true
27+
items:
28+
$ref: '#/components/schemas/Sample'
29+
30+
Sample:
31+
properties:
32+
id:
33+
type: integer
34+
message:
35+
type: string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/**
4+
* Table for Payments
5+
*/
6+
class m200000_000000_create_table_payments extends \yii\db\Migration
7+
{
8+
public function up()
9+
{
10+
$this->createTable('{{%payments}}', [
11+
'id' => $this->primaryKey(),
12+
'currency' => $this->text()->null(),
13+
'samples' => 'json NOT NULL',
14+
]);
15+
}
16+
17+
public function down()
18+
{
19+
$this->dropTable('{{%payments}}');
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/**
4+
* Table for Sample
5+
*/
6+
class m200000_000001_create_table_samples extends \yii\db\Migration
7+
{
8+
public function up()
9+
{
10+
$this->createTable('{{%samples}}', [
11+
'id' => $this->primaryKey(),
12+
'message' => $this->text()->null(),
13+
]);
14+
}
15+
16+
public function down()
17+
{
18+
$this->dropTable('{{%samples}}');
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
namespace app\models;
4+
5+
use Faker\Factory as FakerFactory;
6+
use Faker\Generator;
7+
use Faker\UniqueGenerator;
8+
9+
/**
10+
* Base fake data generator
11+
*/
12+
abstract class BaseModelFaker
13+
{
14+
/**
15+
* @var Generator
16+
*/
17+
protected $faker;
18+
/**
19+
* @var UniqueGenerator
20+
*/
21+
protected $uniqueFaker;
22+
23+
public function __construct()
24+
{
25+
$this->faker = FakerFactory::create(str_replace('-', '_', \Yii::$app->language));
26+
$this->uniqueFaker = new UniqueGenerator($this->faker);
27+
}
28+
29+
abstract public function generateModel($attributes = []);
30+
31+
public function getFaker():Generator
32+
{
33+
return $this->faker;
34+
}
35+
36+
public function getUniqueFaker():UniqueGenerator
37+
{
38+
return $this->uniqueFaker;
39+
}
40+
41+
public function setFaker(Generator $faker):void
42+
{
43+
$this->faker = $faker;
44+
}
45+
46+
public function setUniqueFaker(UniqueGenerator $faker):void
47+
{
48+
$this->uniqueFaker = $faker;
49+
}
50+
51+
/**
52+
* Generate and return model
53+
* @param array|callable $attributes
54+
* @param UniqueGenerator|null $uniqueFaker
55+
* @return \yii\db\ActiveRecord
56+
* @example MyFaker::makeOne(['user_id' => 1, 'title' => 'foo']);
57+
* @example MyFaker::makeOne( function($model, $faker) {
58+
* $model->scenario = 'create';
59+
* $model->setAttributes(['user_id' => 1, 'title' => $faker->sentence]);
60+
* return $model;
61+
* });
62+
*/
63+
public static function makeOne($attributes = [], ?UniqueGenerator $uniqueFaker = null)
64+
{
65+
$fakeBuilder = new static();
66+
if ($uniqueFaker !== null) {
67+
$fakeBuilder->setUniqueFaker($uniqueFaker);
68+
}
69+
$model = $fakeBuilder->generateModel($attributes);
70+
return $model;
71+
}
72+
73+
/**
74+
* Generate, save and return model
75+
* @param array|callable $attributes
76+
* @param UniqueGenerator|null $uniqueFaker
77+
* @return \yii\db\ActiveRecord
78+
* @example MyFaker::saveOne(['user_id' => 1, 'title' => 'foo']);
79+
* @example MyFaker::saveOne( function($model, $faker) {
80+
* $model->scenario = 'create';
81+
* $model->setAttributes(['user_id' => 1, 'title' => $faker->sentence]);
82+
* return $model;
83+
* });
84+
*/
85+
public static function saveOne($attributes = [], ?UniqueGenerator $uniqueFaker = null)
86+
{
87+
$model = static::makeOne($attributes, $uniqueFaker);
88+
$model->save();
89+
return $model;
90+
}
91+
92+
/**
93+
* Generate and return multiple models
94+
* @param int $number
95+
* @param array|callable $commonAttributes
96+
* @return \yii\db\ActiveRecord[]|array
97+
* @example TaskFaker::make(5, ['project_id'=>1, 'user_id' => 2]);
98+
* @example TaskFaker::make(5, function($model, $faker, $uniqueFaker) {
99+
* $model->setAttributes(['name' => $uniqueFaker->username, 'state'=>$faker->boolean(20)]);
100+
* return $model;
101+
* });
102+
*/
103+
public static function make(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null):array
104+
{
105+
if ($number < 1) {
106+
return [];
107+
}
108+
$fakeBuilder = new static();
109+
if ($uniqueFaker !== null) {
110+
$fakeBuilder->setUniqueFaker($uniqueFaker);
111+
}
112+
return array_map(function () use ($commonAttributes, $fakeBuilder) {
113+
$model = $fakeBuilder->generateModel($commonAttributes);
114+
return $model;
115+
}, range(0, $number -1));
116+
}
117+
118+
/**
119+
* Generate, save and return multiple models
120+
* @param int $number
121+
* @param array|callable $commonAttributes
122+
* @return \yii\db\ActiveRecord[]|array
123+
* @example TaskFaker::save(5, ['project_id'=>1, 'user_id' => 2]);
124+
* @example TaskFaker::save(5, function($model, $faker, $uniqueFaker) {
125+
* $model->setAttributes(['name' => $uniqueFaker->username, 'state'=>$faker->boolean(20)]);
126+
* return $model;
127+
* });
128+
*/
129+
public static function save(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null):array
130+
{
131+
if ($number < 1) {
132+
return [];
133+
}
134+
$fakeBuilder = new static();
135+
if ($uniqueFaker !== null) {
136+
$fakeBuilder->setUniqueFaker($uniqueFaker);
137+
}
138+
return array_map(function () use ($commonAttributes, $fakeBuilder) {
139+
$model = $fakeBuilder->generateModel($commonAttributes);
140+
$model->save();
141+
return $model;
142+
}, range(0, $number -1));
143+
}
144+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace app\models;
4+
5+
class Payments extends \app\models\base\Payments
6+
{
7+
8+
9+
}
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
namespace app\models;
3+
4+
use Faker\UniqueGenerator;
5+
6+
/**
7+
* Fake data generator for Payments
8+
* @method static Payments makeOne($attributes = [], ?UniqueGenerator $uniqueFaker = null);
9+
* @method static Payments saveOne($attributes = [], ?UniqueGenerator $uniqueFaker = null);
10+
* @method static Payments[] make(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null)
11+
* @method static Payments[] save(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null)
12+
*/
13+
class PaymentsFaker extends BaseModelFaker
14+
{
15+
16+
/**
17+
* @param array|callable $attributes
18+
* @return Payments|\yii\db\ActiveRecord
19+
* @example
20+
* $model = (new PostFaker())->generateModels(['author_id' => 1]);
21+
* $model = (new PostFaker())->generateModels(function($model, $faker, $uniqueFaker) {
22+
* $model->scenario = 'create';
23+
* $model->author_id = 1;
24+
* return $model;
25+
* });
26+
**/
27+
public function generateModel($attributes = [])
28+
{
29+
$faker = $this->faker;
30+
$uniqueFaker = $this->uniqueFaker;
31+
$model = new Payments();
32+
//$model->id = $uniqueFaker->numberBetween(0, 1000000);
33+
$model->currency = $faker->currencyCode;
34+
$model->samples = array_map(function () use ($faker, $uniqueFaker) {
35+
return (new SampleFaker)->generateModel()->attributes;
36+
}, range(1, 4));
37+
if (!is_callable($attributes)) {
38+
$model->setAttributes($attributes, false);
39+
} else {
40+
$model = $attributes($model, $faker, $uniqueFaker);
41+
}
42+
return $model;
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace app\models;
4+
5+
class Sample extends \app\models\base\Sample
6+
{
7+
8+
9+
}
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
namespace app\models;
3+
4+
use Faker\UniqueGenerator;
5+
6+
/**
7+
* Fake data generator for Sample
8+
* @method static Sample makeOne($attributes = [], ?UniqueGenerator $uniqueFaker = null);
9+
* @method static Sample saveOne($attributes = [], ?UniqueGenerator $uniqueFaker = null);
10+
* @method static Sample[] make(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null)
11+
* @method static Sample[] save(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null)
12+
*/
13+
class SampleFaker extends BaseModelFaker
14+
{
15+
16+
/**
17+
* @param array|callable $attributes
18+
* @return Sample|\yii\db\ActiveRecord
19+
* @example
20+
* $model = (new PostFaker())->generateModels(['author_id' => 1]);
21+
* $model = (new PostFaker())->generateModels(function($model, $faker, $uniqueFaker) {
22+
* $model->scenario = 'create';
23+
* $model->author_id = 1;
24+
* return $model;
25+
* });
26+
**/
27+
public function generateModel($attributes = [])
28+
{
29+
$faker = $this->faker;
30+
$uniqueFaker = $this->uniqueFaker;
31+
$model = new Sample();
32+
//$model->id = $uniqueFaker->numberBetween(0, 1000000);
33+
$model->message = $faker->sentence;
34+
if (!is_callable($attributes)) {
35+
$model->setAttributes($attributes, false);
36+
} else {
37+
$model = $attributes($model, $faker, $uniqueFaker);
38+
}
39+
return $model;
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* This file is generated by Gii, do not change manually!
5+
*/
6+
7+
namespace app\models\base;
8+
9+
/**
10+
* This is the model class for table "payments".
11+
*
12+
* @property int $id
13+
* @property string $currency
14+
* @property array $samples
15+
*
16+
*/
17+
abstract class Payments extends \yii\db\ActiveRecord
18+
{
19+
public static function tableName()
20+
{
21+
return '{{%payments}}';
22+
}
23+
24+
public function rules()
25+
{
26+
return [
27+
'trim' => [['currency'], 'trim'],
28+
'currency_string' => [['currency'], 'string'],
29+
'safe' => [['samples'], 'safe'],
30+
];
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/**
4+
* This file is generated by Gii, do not change manually!
5+
*/
6+
7+
namespace app\models\base;
8+
9+
/**
10+
* This is the model class for table "samples".
11+
*
12+
* @property int $id
13+
* @property string $message
14+
*
15+
*/
16+
abstract class Sample extends \yii\db\ActiveRecord
17+
{
18+
public static function tableName()
19+
{
20+
return '{{%samples}}';
21+
}
22+
23+
public function rules()
24+
{
25+
return [
26+
'trim' => [['message'], 'trim'],
27+
'message_string' => [['message'], 'string'],
28+
];
29+
}
30+
}

‎tests/unit/IssueFixTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -1014,4 +1014,19 @@ public function test22BugRulesRequiredIsGeneratedBeforeDefault()
10141014
]);
10151015
$this->checkFiles($actualFiles, $expectedFiles);
10161016
}
1017+
1018+
// https://github.com/php-openapi/yii2-openapi/issues/23
1019+
public function test23ConsiderOpenapiExtensionXNoRelationAlsoInOtherPertinentPlace()
1020+
{
1021+
$testFile = Yii::getAlias("@specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.php");
1022+
$this->runGenerator($testFile);
1023+
$actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [
1024+
'recursive' => true,
1025+
]);
1026+
$expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql"), [
1027+
'recursive' => true,
1028+
]);
1029+
$this->checkFiles($actualFiles, $expectedFiles);
1030+
$this->runActualMigrations();
1031+
}
10171032
}

0 commit comments

Comments
 (0)
Please sign in to comment.