diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index a43aa8ba..cc41a6fb 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -498,9 +498,13 @@ protected function resolvePropertyRef(PropertySchema $property, Attribute $attri $attribute->setPhpType($fkProperty->guessPhpType()) ->setDbType($fkProperty->guessDbType(true)) ->setSize($fkProperty->getMaxLength()) - ->setDescription($fkProperty->getAttr('description')) + ->setDescription($fkProperty->getAttr('description', '')) ->setDefault($fkProperty->guessDefault()) ->setLimits($min, $max, $fkProperty->getMinLength()); + + if ($fkProperty->hasEnum()) { + $attribute->setEnumValues($fkProperty->getAttr('enum')); + } $this->attributes[$property->getName()] = $attribute->setFakerStub($this->guessFakerStub($attribute, $fkProperty)); } diff --git a/src/lib/migrations/BaseMigrationBuilder.php b/src/lib/migrations/BaseMigrationBuilder.php index 416311d9..ded9a7af 100644 --- a/src/lib/migrations/BaseMigrationBuilder.php +++ b/src/lib/migrations/BaseMigrationBuilder.php @@ -491,7 +491,7 @@ public function tmpSaveNewCol(string $tableAlias, \cebe\yii2openapi\db\ColumnSch Yii::$app->db->createCommand()->createTable($tmpTableName, $column)->execute(); if (ApiGenerator::isPostgres() && $columnSchema->comment) { - Yii::$app->db->createCommand("COMMENT ON COLUMN $tmpTableName.$columnSchema->name IS {$this->db->quoteValue($columnSchema->comment)}")->execute(); + Yii::$app->db->createCommand("COMMENT ON COLUMN $tmpTableName.\"$columnSchema->name\" IS {$this->db->quoteValue($columnSchema->comment)}")->execute(); } $table = Yii::$app->db->getTableSchema($tmpTableName); diff --git a/src/lib/openapi/PropertySchema.php b/src/lib/openapi/PropertySchema.php index 9ff64094..eade252e 100644 --- a/src/lib/openapi/PropertySchema.php +++ b/src/lib/openapi/PropertySchema.php @@ -70,6 +70,7 @@ class PropertySchema /** @var string $refPointer */ private $refPointer; + private $uri; /** @var \cebe\yii2openapi\lib\openapi\ComponentSchema $refSchema */ private $refSchema; @@ -170,6 +171,7 @@ private function initReference():void { $this->isReference = true; $this->refPointer = $this->property->getJsonReference()->getJsonPointer()->getPointer(); + $this->uri = $this->property->getJsonReference()->getDocumentUri(); $refSchemaName = $this->getRefSchemaName(); if ($this->isRefPointerToSelf()) { $this->refSchema = $this->schema; @@ -194,6 +196,7 @@ private function initItemsReference():void return; } $this->refPointer = $items->getJsonReference()->getJsonPointer()->getPointer(); + $this->uri = $items->getJsonReference()->getDocumentUri(); if ($this->isRefPointerToSelf()) { $this->refSchema = $this->schema; } elseif ($this->isRefPointerToSchema()) { @@ -264,7 +267,9 @@ public function getSelfTargetProperty():?PropertySchema public function isRefPointerToSchema():bool { - return $this->refPointer && strpos($this->refPointer, self::REFERENCE_PATH) === 0; + return $this->refPointer && + ((strpos($this->refPointer, self::REFERENCE_PATH) === 0) || + (str_ends_with($this->uri, '.yml')) || (str_ends_with($this->uri, '.yaml'))); } public function isRefPointerToSelf():bool @@ -300,8 +305,13 @@ public function getRefSchemaName():string $pattern = strpos($this->refPointer, '/properties/') !== false ? '~^'.self::REFERENCE_PATH.'(?.+)/properties/(?.+)$~' : '~^'.self::REFERENCE_PATH.'(?.+)$~'; + $separateFilePattern = '/((\.\/)*)(?.+)(\.)(yml|yaml)(.*)/'; # https://github.com/php-openapi/yii2-openapi/issues/74 if (!\preg_match($pattern, $this->refPointer, $matches)) { - throw new InvalidDefinitionException('Invalid schema reference'); + if (!\preg_match($separateFilePattern, $this->uri, $separateFilePatternMatches)) { + throw new InvalidDefinitionException('Invalid schema reference'); + } else { + return $separateFilePatternMatches['schemaName']; + } } return $matches['schemaName']; } diff --git a/tests/specs/issue_fix/74_invalid_schema_reference_error/Product.yaml b/tests/specs/issue_fix/74_invalid_schema_reference_error/Product.yaml new file mode 100644 index 00000000..ca81d464 --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/Product.yaml @@ -0,0 +1,18 @@ +# Product: +title: Product +x-table: products +type: object + +required: + - id + - vat_rate + +properties: + id: + type: integer + vat_rate: + type: string + enum: + - standard + - none + default: standard diff --git a/tests/specs/issue_fix/74_invalid_schema_reference_error/index.php b/tests/specs/issue_fix/74_invalid_schema_reference_error/index.php new file mode 100644 index 00000000..cbe2f6dc --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/index.php @@ -0,0 +1,13 @@ + '@specs/issue_fix/74_invalid_schema_reference_error/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/74_invalid_schema_reference_error/index.yaml b/tests/specs/issue_fix/74_invalid_schema_reference_error/index.yaml new file mode 100644 index 00000000..271c63d1 --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/index.yaml @@ -0,0 +1,25 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Invalid schema reference error \#74 +paths: + /: + get: + responses: + '200': + description: The information + +components: + schemas: + Invoice: + type: object + required: + - vat_rate + properties: + id: + type: integer + vat_rate: + # $ref: '#/components/schemas/Product/properties/vat_rate' # issue is not observed + $ref: './Product.yaml#/properties/vat_rate' # issue is observed + Product: + $ref: ./Product.yaml diff --git a/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/migrations_mysql_db/m200000_000000_create_table_invoices.php b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/migrations_mysql_db/m200000_000000_create_table_invoices.php new file mode 100644 index 00000000..1e21544d --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/migrations_mysql_db/m200000_000000_create_table_invoices.php @@ -0,0 +1,20 @@ +createTable('{{%invoices}}', [ + 'id' => $this->primaryKey(), + 'vat_rate' => 'enum("standard", "none") NOT NULL DEFAULT \'standard\'', + ]); + } + + public function down() + { + $this->dropTable('{{%invoices}}'); + } +} diff --git a/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/migrations_mysql_db/m200000_000001_create_table_products.php b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/migrations_mysql_db/m200000_000001_create_table_products.php new file mode 100644 index 00000000..fcde7ba8 --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/migrations_mysql_db/m200000_000001_create_table_products.php @@ -0,0 +1,20 @@ +createTable('{{%products}}', [ + 'id' => $this->primaryKey(), + 'vat_rate' => 'enum("standard", "none") NOT NULL DEFAULT \'standard\'', + ]); + } + + public function down() + { + $this->dropTable('{{%products}}'); + } +} diff --git a/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/BaseModelFaker.php b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/BaseModelFaker.php new file mode 100644 index 00000000..c367fbb4 --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/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/74_invalid_schema_reference_error/mysql/models/Invoice.php b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/Invoice.php new file mode 100644 index 00000000..43e37fd3 --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/Invoice.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 Invoice(); + //$model->id = $uniqueFaker->numberBetween(0, 1000000); + $model->vat_rate = $faker->randomElement(['standard','none']); + if (!is_callable($attributes)) { + $model->setAttributes($attributes, false); + } else { + $model = $attributes($model, $faker, $uniqueFaker); + } + return $model; + } +} diff --git a/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/Product.php b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/Product.php new file mode 100644 index 00000000..a8411efa --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/Product.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 Product(); + //$model->id = $uniqueFaker->numberBetween(0, 1000000); + $model->vat_rate = $faker->randomElement(['standard','none']); + if (!is_callable($attributes)) { + $model->setAttributes($attributes, false); + } else { + $model = $attributes($model, $faker, $uniqueFaker); + } + return $model; + } +} diff --git a/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/base/Invoice.php b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/base/Invoice.php new file mode 100644 index 00000000..d81c8ce3 --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/base/Invoice.php @@ -0,0 +1,35 @@ + [['vat_rate'], 'required'], + 'vat_rate_string' => [['vat_rate'], 'string'], + 'vat_rate_in' => [['vat_rate'], 'in', 'range' => [ + 'standard', + 'none', + ]], + 'vat_rate_default' => [['vat_rate'], 'default', 'value' => 'standard'], + ]; + } +} diff --git a/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/base/Product.php b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/base/Product.php new file mode 100644 index 00000000..184a6395 --- /dev/null +++ b/tests/specs/issue_fix/74_invalid_schema_reference_error/mysql/models/base/Product.php @@ -0,0 +1,35 @@ + [['vat_rate'], 'required'], + 'vat_rate_string' => [['vat_rate'], 'string'], + 'vat_rate_in' => [['vat_rate'], 'in', 'range' => [ + 'standard', + 'none', + ]], + 'vat_rate_default' => [['vat_rate'], 'default', 'value' => 'standard'], + ]; + } +} diff --git a/tests/unit/IssueFixTest.php b/tests/unit/IssueFixTest.php index f1b2bfe8..11134111 100644 --- a/tests/unit/IssueFixTest.php +++ b/tests/unit/IssueFixTest.php @@ -962,4 +962,19 @@ public function test63JustColumnNameRename() $this->checkFiles($actualFiles, $expectedFiles); Yii::$app->db->createCommand('DROP TABLE IF EXISTS {{%fruits}}')->execute(); } + + // https://github.com/php-openapi/yii2-openapi/issues/63 + public function test74InvalidSchemaReferenceError() + { + $testFile = Yii::getAlias("@specs/issue_fix/74_invalid_schema_reference_error/index.php"); + $this->runGenerator($testFile); + $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ + 'recursive' => true, + ]); + $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/74_invalid_schema_reference_error/mysql"), [ + 'recursive' => true, + ]); + $this->checkFiles($actualFiles, $expectedFiles); + $this->runActualMigrations(); + } }