diff --git a/src/lib/ValidationRulesBuilder.php b/src/lib/ValidationRulesBuilder.php index 746976b0..e696fc21 100644 --- a/src/lib/ValidationRulesBuilder.php +++ b/src/lib/ValidationRulesBuilder.php @@ -63,6 +63,10 @@ public function build():array } } foreach ($this->model->attributes as $attribute) { + // 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 ($attribute->columnName === 'id' || $attribute->propertyName === 'id') { + continue; + } $this->resolveAttributeRules($attribute); } @@ -181,6 +185,10 @@ 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 ($attribute->columnName === 'id' || $attribute->propertyName === 'id') { + continue; + } if ($attribute->defaultValue === null && $attribute->isRequired()) { $this->typeScope['required'][$attribute->columnName] = $attribute->columnName; } diff --git a/src/lib/migrations/BaseMigrationBuilder.php b/src/lib/migrations/BaseMigrationBuilder.php index 6a88fb48..ab49d0bf 100644 --- a/src/lib/migrations/BaseMigrationBuilder.php +++ b/src/lib/migrations/BaseMigrationBuilder.php @@ -15,7 +15,8 @@ use Yii; use yii\db\ColumnSchema; use yii\helpers\VarDumper; -use yii\db\{Connection, Expression}; +use yii\db\Connection; +use yii\db\Expression; abstract class BaseMigrationBuilder { @@ -478,9 +479,10 @@ public static function isEnumValuesChanged( return false; } - public function isDefaultValueChanged(ColumnSchema $current, - ColumnSchema $desired): bool - { + public function isDefaultValueChanged( + ColumnSchema $current, + ColumnSchema $desired + ): bool { // if the default value is object of \yii\db\Expression then default value is expression instead of constant. See https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html // in such case instead of comparing two objects, we should compare expression diff --git a/tests/DbTestCase.php b/tests/DbTestCase.php index 1013cea0..bb77194c 100644 --- a/tests/DbTestCase.php +++ b/tests/DbTestCase.php @@ -53,7 +53,7 @@ protected function tearDown() } } - protected function runGenerator($configFile, string $dbName) + protected function runGenerator($configFile, string $dbName = 'mysql') { $config = require $configFile; $config['migrationPath'] = "@app/migrations_{$dbName}_db/"; diff --git a/tests/specs/id_not_in_rules/app/models/BaseModelFaker.php b/tests/specs/id_not_in_rules/app/models/BaseModelFaker.php new file mode 100644 index 00000000..c367fbb4 --- /dev/null +++ b/tests/specs/id_not_in_rules/app/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/id_not_in_rules/app/models/Fruit.php b/tests/specs/id_not_in_rules/app/models/Fruit.php new file mode 100644 index 00000000..c74c53d9 --- /dev/null +++ b/tests/specs/id_not_in_rules/app/models/Fruit.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 Fruit(); + //$model->id = $uniqueFaker->numberBetween(0, 1000000); + $model->name = $faker->sentence; + if (!is_callable($attributes)) { + $model->setAttributes($attributes, false); + } else { + $model = $attributes($model, $faker, $uniqueFaker); + } + return $model; + } +} diff --git a/tests/specs/id_not_in_rules/app/models/base/Fruit.php b/tests/specs/id_not_in_rules/app/models/base/Fruit.php new file mode 100644 index 00000000..4b1fcd72 --- /dev/null +++ b/tests/specs/id_not_in_rules/app/models/base/Fruit.php @@ -0,0 +1,27 @@ + [['name'], 'trim'], + 'required' => [['name'], 'required'], + 'name_string' => [['name'], 'string'], + ]; + } +} diff --git a/tests/specs/id_not_in_rules/id_not_in_rules.php b/tests/specs/id_not_in_rules/id_not_in_rules.php new file mode 100644 index 00000000..73a9ccc7 --- /dev/null +++ b/tests/specs/id_not_in_rules/id_not_in_rules.php @@ -0,0 +1,13 @@ + '@specs/id_not_in_rules/id_not_in_rules.yaml', + 'generateUrls' => false, + 'generateModels' => true, + 'excludeModels' => [ + 'Error', + ], + 'generateControllers' => false, + 'generateMigrations' => false, + 'generateModelFaker' => true, +]; diff --git a/tests/specs/id_not_in_rules/id_not_in_rules.yaml b/tests/specs/id_not_in_rules/id_not_in_rules.yaml new file mode 100644 index 00000000..0d684370 --- /dev/null +++ b/tests/specs/id_not_in_rules/id_not_in_rules.yaml @@ -0,0 +1,26 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: ID not in rules test +paths: + /: + get: + summary: List + operationId: list + responses: + '200': + description: The information + +components: + schemas: + Fruit: + type: object + description: Test model for model code generation that should not contain id column in rules + required: + - id + - name + properties: + id: + type: integer + name: + type: string diff --git a/tests/unit/IdNotInRulesTest.php b/tests/unit/IdNotInRulesTest.php new file mode 100644 index 00000000..021b3ac6 --- /dev/null +++ b/tests/unit/IdNotInRulesTest.php @@ -0,0 +1,23 @@ +runGenerator($testFile); + $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ + 'recursive' => true, + ]); + $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/id_not_in_rules/app"), [ + 'recursive' => true, + ]); + $this->checkFiles($actualFiles, $expectedFiles); + } +} diff --git a/tests/unit/MultiDbFreshMigrationTest.php b/tests/unit/MultiDbFreshMigrationTest.php index 0a86726b..15ade7e2 100644 --- a/tests/unit/MultiDbFreshMigrationTest.php +++ b/tests/unit/MultiDbFreshMigrationTest.php @@ -71,7 +71,7 @@ protected function tearDown() } } - protected function runGenerator($configFile, string $dbName) + protected function runGenerator($configFile, string $dbName = 'mysql') { $config = require $configFile; $config['migrationPath'] = "@app/migrations_{$dbName}_db/"; diff --git a/tests/unit/MultiDbSecondaryMigrationTest.php b/tests/unit/MultiDbSecondaryMigrationTest.php index 5680cc75..2ae0b662 100644 --- a/tests/unit/MultiDbSecondaryMigrationTest.php +++ b/tests/unit/MultiDbSecondaryMigrationTest.php @@ -85,7 +85,7 @@ protected function tearDown() } } - protected function runGenerator($configFile, string $dbName) + protected function runGenerator($configFile, string $dbName = 'mysql') { $config = require $configFile; $config['migrationPath'] = "@app/migrations_{$dbName}_db/";