diff --git a/README.md b/README.md index f68df469..7d1c4c8a 100644 --- a/README.md +++ b/README.md @@ -350,7 +350,7 @@ related objects, `x-no-relation` (type: boolean, default: false) is used. This will not generate 'comments' column in database migrations. But it will generate `getComments()` relation in Yii model file. -In order to make it real database column, extension `x-no-relation` can be used. +In order to make it real database column, OpenAPI extension `x-no-relation` can be used. ```yaml comments: diff --git a/src/lib/ValidationRulesBuilder.php b/src/lib/ValidationRulesBuilder.php index 33a89949..33347314 100644 --- a/src/lib/ValidationRulesBuilder.php +++ b/src/lib/ValidationRulesBuilder.php @@ -243,10 +243,7 @@ private function prepareTypeScope():void if ($attribute->isReadOnly()) { continue; } - // 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()` - if (in_array($attribute->columnName, ['id', $this->model->pkName]) || - in_array($attribute->propertyName, ['id', $this->model->pkName]) - ) { + if ($this->isIdColumn($attribute)) { continue; } if (/*$attribute->defaultValue === null &&*/ $attribute->isRequired()) { diff --git a/src/lib/openapi/ComponentSchema.php b/src/lib/openapi/ComponentSchema.php index 809f0958..9386bf2a 100644 --- a/src/lib/openapi/ComponentSchema.php +++ b/src/lib/openapi/ComponentSchema.php @@ -14,8 +14,6 @@ use cebe\yii2openapi\lib\SchemaToDatabase; use Generator; use Yii; -use yii\helpers\Inflector; -use yii\helpers\StringHelper; use function in_array; class ComponentSchema @@ -106,7 +104,9 @@ public function isRequiredProperty(string $propName):bool public function isNonDb():bool { - return isset($this->schema->{CustomSpecAttr::TABLE}) && $this->schema->{CustomSpecAttr::TABLE} === false; + return + isset($this->schema->{CustomSpecAttr::TABLE}) && + $this->schema->{CustomSpecAttr::TABLE} === false; } public function resolveTableName(string $schemaName):string diff --git a/src/lib/openapi/PropertySchema.php b/src/lib/openapi/PropertySchema.php index eade252e..56d6df7a 100644 --- a/src/lib/openapi/PropertySchema.php +++ b/src/lib/openapi/PropertySchema.php @@ -145,6 +145,11 @@ public function __construct(SpecObjectInterface $property, string $name, Compone $property = $this->property; } + // don't go reference part if `x-no-relation` is true + if ($this->getAttr(CustomSpecAttr::NO_RELATION)) { + return; + } + if ($property instanceof Reference) { $this->initReference(); } elseif ( @@ -497,6 +502,7 @@ public function guessDbType($forReference = false):string } return YiiDbSchema::TYPE_TEXT; case 'object': + case 'array': { return YiiDbSchema::TYPE_JSON; } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000001_create_table_pets.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000001_create_table_pets.php index 01df98f3..3099e6fe 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000001_create_table_pets.php +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000001_create_table_pets.php @@ -11,21 +11,21 @@ public function up() 'id' => $this->primaryKey(), 'name' => $this->text()->notNull(), 'age' => $this->integer()->null()->defaultValue(null), - 'tags' => $this->text()->null(), - 'tags_arbit' => $this->text()->null(), - 'number_arr' => $this->text()->null(), - 'number_arr_min_uniq' => $this->text()->null(), - 'int_arr' => $this->text()->null(), - 'int_arr_min_uniq' => $this->text()->null(), - 'bool_arr' => $this->text()->null(), - 'arr_arr_int' => $this->text()->null(), - 'arr_arr_str' => $this->text()->null(), - 'arr_arr_arr_str' => $this->text()->null(), - 'arr_of_obj' => $this->text()->null(), - 'user_ref_obj_arr' => $this->string()->null()->defaultValue(null), - 'one_of_arr' => $this->text()->null(), - 'one_of_arr_complex' => $this->text()->null(), - 'one_of_from_multi_ref_arr' => $this->text()->null(), + 'tags' => 'json NOT NULL', + 'tags_arbit' => 'json NOT NULL', + 'number_arr' => 'json NOT NULL', + 'number_arr_min_uniq' => 'json NOT NULL', + 'int_arr' => 'json NOT NULL', + 'int_arr_min_uniq' => 'json NOT NULL', + 'bool_arr' => 'json NOT NULL', + 'arr_arr_int' => 'json NOT NULL', + 'arr_arr_str' => 'json NOT NULL', + 'arr_arr_arr_str' => 'json NOT NULL', + 'arr_of_obj' => 'json NOT NULL', + 'user_ref_obj_arr' => 'json NOT NULL', + 'one_of_arr' => 'json NOT NULL', + 'one_of_arr_complex' => 'json NOT NULL', + 'one_of_from_multi_ref_arr' => 'json NOT NULL', ]); } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Pet.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Pet.php index c0621fd6..ae87e515 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Pet.php +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Pet.php @@ -23,13 +23,12 @@ * @property array $arr_arr_str * @property array $arr_arr_arr_str * @property array $arr_of_obj - * @property User[] $user_ref_obj_arr + * @property array $user_ref_obj_arr * @property array $one_of_arr * @property array $one_of_arr_complex * @property array $one_of_from_multi_ref_arr * * @property array|\app\models\User[] $userRefObjArrNormal - * @property array|\app\models\User[] $userRefObjArr */ abstract class Pet extends \yii\db\ActiveRecord { @@ -53,9 +52,4 @@ public function getUserRefObjArrNormal() { return $this->hasMany(\app\models\User::class, ['pet_id' => 'id'])->inverseOf('pet'); } - - public function getUserRefObjArr() - { - return $this->hasMany(\app\models\User::class, ['pet_id' => 'id'])->inverseOf('pet'); - } } diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.php b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.php new file mode 100644 index 00000000..d10773f1 --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.php @@ -0,0 +1,13 @@ + '@specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.yaml', + 'generateUrls' => false, + 'generateModels' => true, + 'excludeModels' => [ + 'Error', + ], + 'generateControllers' => false, + 'generateMigrations' => true, + 'generateModelFaker' => true, // `generateModels` must be `true` in order to use `generateModelFaker` as `true` +]; diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.yaml b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.yaml new file mode 100644 index 00000000..669f6239 --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.yaml @@ -0,0 +1,35 @@ +openapi: 3.0.3 + +info: + title: '#23' + version: 1.0.0 + +paths: + /: + get: + responses: + '200': + description: The Response + + + +components: + schemas: + Payments: + properties: + id: + type: integer + currency: + type: string + samples: + type: array + x-no-relation: true + items: + $ref: '#/components/schemas/Sample' + + Sample: + properties: + id: + type: integer + message: + type: string \ No newline at end of file diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/migrations_mysql_db/m200000_000000_create_table_payments.php b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/migrations_mysql_db/m200000_000000_create_table_payments.php new file mode 100644 index 00000000..5579f4e7 --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/migrations_mysql_db/m200000_000000_create_table_payments.php @@ -0,0 +1,21 @@ +createTable('{{%payments}}', [ + 'id' => $this->primaryKey(), + 'currency' => $this->text()->null(), + 'samples' => 'json NOT NULL', + ]); + } + + public function down() + { + $this->dropTable('{{%payments}}'); + } +} diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/migrations_mysql_db/m200000_000001_create_table_samples.php b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/migrations_mysql_db/m200000_000001_create_table_samples.php new file mode 100644 index 00000000..b5b3a86c --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/migrations_mysql_db/m200000_000001_create_table_samples.php @@ -0,0 +1,20 @@ +createTable('{{%samples}}', [ + 'id' => $this->primaryKey(), + 'message' => $this->text()->null(), + ]); + } + + public function down() + { + $this->dropTable('{{%samples}}'); + } +} diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/BaseModelFaker.php b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/BaseModelFaker.php new file mode 100644 index 00000000..c367fbb4 --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/BaseModelFaker.php @@ -0,0 +1,144 @@ +faker = FakerFactory::create(str_replace('-', '_', \Yii::$app->language)); + $this->uniqueFaker = new UniqueGenerator($this->faker); + } + + abstract public function generateModel($attributes = []); + + public function getFaker():Generator + { + return $this->faker; + } + + public function getUniqueFaker():UniqueGenerator + { + return $this->uniqueFaker; + } + + public function setFaker(Generator $faker):void + { + $this->faker = $faker; + } + + public function setUniqueFaker(UniqueGenerator $faker):void + { + $this->uniqueFaker = $faker; + } + + /** + * Generate and return model + * @param array|callable $attributes + * @param UniqueGenerator|null $uniqueFaker + * @return \yii\db\ActiveRecord + * @example MyFaker::makeOne(['user_id' => 1, 'title' => 'foo']); + * @example MyFaker::makeOne( function($model, $faker) { + * $model->scenario = 'create'; + * $model->setAttributes(['user_id' => 1, 'title' => $faker->sentence]); + * return $model; + * }); + */ + public static function makeOne($attributes = [], ?UniqueGenerator $uniqueFaker = null) + { + $fakeBuilder = new static(); + if ($uniqueFaker !== null) { + $fakeBuilder->setUniqueFaker($uniqueFaker); + } + $model = $fakeBuilder->generateModel($attributes); + return $model; + } + + /** + * Generate, save and return model + * @param array|callable $attributes + * @param UniqueGenerator|null $uniqueFaker + * @return \yii\db\ActiveRecord + * @example MyFaker::saveOne(['user_id' => 1, 'title' => 'foo']); + * @example MyFaker::saveOne( function($model, $faker) { + * $model->scenario = 'create'; + * $model->setAttributes(['user_id' => 1, 'title' => $faker->sentence]); + * return $model; + * }); + */ + public static function saveOne($attributes = [], ?UniqueGenerator $uniqueFaker = null) + { + $model = static::makeOne($attributes, $uniqueFaker); + $model->save(); + return $model; + } + + /** + * Generate and return multiple models + * @param int $number + * @param array|callable $commonAttributes + * @return \yii\db\ActiveRecord[]|array + * @example TaskFaker::make(5, ['project_id'=>1, 'user_id' => 2]); + * @example TaskFaker::make(5, function($model, $faker, $uniqueFaker) { + * $model->setAttributes(['name' => $uniqueFaker->username, 'state'=>$faker->boolean(20)]); + * return $model; + * }); + */ + public static function make(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null):array + { + if ($number < 1) { + return []; + } + $fakeBuilder = new static(); + if ($uniqueFaker !== null) { + $fakeBuilder->setUniqueFaker($uniqueFaker); + } + return array_map(function () use ($commonAttributes, $fakeBuilder) { + $model = $fakeBuilder->generateModel($commonAttributes); + return $model; + }, range(0, $number -1)); + } + + /** + * Generate, save and return multiple models + * @param int $number + * @param array|callable $commonAttributes + * @return \yii\db\ActiveRecord[]|array + * @example TaskFaker::save(5, ['project_id'=>1, 'user_id' => 2]); + * @example TaskFaker::save(5, function($model, $faker, $uniqueFaker) { + * $model->setAttributes(['name' => $uniqueFaker->username, 'state'=>$faker->boolean(20)]); + * return $model; + * }); + */ + public static function save(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null):array + { + if ($number < 1) { + return []; + } + $fakeBuilder = new static(); + if ($uniqueFaker !== null) { + $fakeBuilder->setUniqueFaker($uniqueFaker); + } + return array_map(function () use ($commonAttributes, $fakeBuilder) { + $model = $fakeBuilder->generateModel($commonAttributes); + $model->save(); + return $model; + }, range(0, $number -1)); + } +} diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/Payments.php b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/Payments.php new file mode 100644 index 00000000..ea2262fe --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/Payments.php @@ -0,0 +1,10 @@ +generateModels(['author_id' => 1]); + * $model = (new PostFaker())->generateModels(function($model, $faker, $uniqueFaker) { + * $model->scenario = 'create'; + * $model->author_id = 1; + * return $model; + * }); + **/ + public function generateModel($attributes = []) + { + $faker = $this->faker; + $uniqueFaker = $this->uniqueFaker; + $model = new Payments(); + //$model->id = $uniqueFaker->numberBetween(0, 1000000); + $model->currency = $faker->currencyCode; + $model->samples = array_map(function () use ($faker, $uniqueFaker) { + return (new SampleFaker)->generateModel()->attributes; + }, range(1, 4)); + if (!is_callable($attributes)) { + $model->setAttributes($attributes, false); + } else { + $model = $attributes($model, $faker, $uniqueFaker); + } + return $model; + } +} diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/Sample.php b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/Sample.php new file mode 100644 index 00000000..4e8b4547 --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/Sample.php @@ -0,0 +1,10 @@ +generateModels(['author_id' => 1]); + * $model = (new PostFaker())->generateModels(function($model, $faker, $uniqueFaker) { + * $model->scenario = 'create'; + * $model->author_id = 1; + * return $model; + * }); + **/ + public function generateModel($attributes = []) + { + $faker = $this->faker; + $uniqueFaker = $this->uniqueFaker; + $model = new Sample(); + //$model->id = $uniqueFaker->numberBetween(0, 1000000); + $model->message = $faker->sentence; + if (!is_callable($attributes)) { + $model->setAttributes($attributes, false); + } else { + $model = $attributes($model, $faker, $uniqueFaker); + } + return $model; + } +} diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/base/Payments.php b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/base/Payments.php new file mode 100644 index 00000000..6e797741 --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/base/Payments.php @@ -0,0 +1,32 @@ + [['currency'], 'trim'], + 'currency_string' => [['currency'], 'string'], + 'safe' => [['samples'], 'safe'], + ]; + } +} diff --git a/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/base/Sample.php b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/base/Sample.php new file mode 100644 index 00000000..656ff1eb --- /dev/null +++ b/tests/specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql/models/base/Sample.php @@ -0,0 +1,30 @@ + [['message'], 'trim'], + 'message_string' => [['message'], 'string'], + ]; + } +} diff --git a/tests/unit/IssueFixTest.php b/tests/unit/IssueFixTest.php index ad3ff8fd..9d20ae4d 100644 --- a/tests/unit/IssueFixTest.php +++ b/tests/unit/IssueFixTest.php @@ -1014,4 +1014,19 @@ public function test22BugRulesRequiredIsGeneratedBeforeDefault() ]); $this->checkFiles($actualFiles, $expectedFiles); } + + // https://github.com/php-openapi/yii2-openapi/issues/23 + public function test23ConsiderOpenapiExtensionXNoRelationAlsoInOtherPertinentPlace() + { + $testFile = Yii::getAlias("@specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/index.php"); + $this->runGenerator($testFile); + $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ + 'recursive' => true, + ]); + $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/23_consider_openapi_extension_x_no_relation_also_in_other_pertinent_place/mysql"), [ + 'recursive' => true, + ]); + $this->checkFiles($actualFiles, $expectedFiles); + $this->runActualMigrations(); + } }