Skip to content

Resolve: Invalid schema reference error #75

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 9 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/lib/AttributeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/migrations/BaseMigrationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 12 additions & 2 deletions src/lib/openapi/PropertySchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class PropertySchema

/** @var string $refPointer */
private $refPointer;
private $uri;

/** @var \cebe\yii2openapi\lib\openapi\ComponentSchema $refSchema */
private $refSchema;
Expand Down Expand Up @@ -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;
Expand All @@ -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()) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -300,8 +305,13 @@ public function getRefSchemaName():string
$pattern = strpos($this->refPointer, '/properties/') !== false ?
'~^'.self::REFERENCE_PATH.'(?<schemaName>.+)/properties/(?<propName>.+)$~'
: '~^'.self::REFERENCE_PATH.'(?<schemaName>.+)$~';
$separateFilePattern = '/((\.\/)*)(?<schemaName>.+)(\.)(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'];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions tests/specs/issue_fix/74_invalid_schema_reference_error/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

return [
'openApiPath' => '@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`
];
25 changes: 25 additions & 0 deletions tests/specs/issue_fix/74_invalid_schema_reference_error/index.yaml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/**
* Table for Invoice
*/
class m200000_000000_create_table_invoices extends \yii\db\Migration
{
public function up()
{
$this->createTable('{{%invoices}}', [
'id' => $this->primaryKey(),
'vat_rate' => 'enum("standard", "none") NOT NULL DEFAULT \'standard\'',
]);
}

public function down()
{
$this->dropTable('{{%invoices}}');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/**
* Table for Product
*/
class m200000_000001_create_table_products extends \yii\db\Migration
{
public function up()
{
$this->createTable('{{%products}}', [
'id' => $this->primaryKey(),
'vat_rate' => 'enum("standard", "none") NOT NULL DEFAULT \'standard\'',
]);
}

public function down()
{
$this->dropTable('{{%products}}');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

namespace app\models;

use Faker\Factory as FakerFactory;
use Faker\Generator;
use Faker\UniqueGenerator;

/**
* Base fake data generator
*/
abstract class BaseModelFaker
{
/**
* @var Generator
*/
protected $faker;
/**
* @var UniqueGenerator
*/
protected $uniqueFaker;

public function __construct()
{
$this->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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace app\models;

class Invoice extends \app\models\base\Invoice
{


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
namespace app\models;

use Faker\UniqueGenerator;

/**
* Fake data generator for Invoice
* @method static Invoice makeOne($attributes = [], ?UniqueGenerator $uniqueFaker = null);
* @method static Invoice saveOne($attributes = [], ?UniqueGenerator $uniqueFaker = null);
* @method static Invoice[] make(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null)
* @method static Invoice[] save(int $number, $commonAttributes = [], ?UniqueGenerator $uniqueFaker = null)
*/
class InvoiceFaker extends BaseModelFaker
{

/**
* @param array|callable $attributes
* @return Invoice|\yii\db\ActiveRecord
* @example
* $model = (new PostFaker())->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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace app\models;

class Product extends \app\models\base\Product
{


}

Loading
Loading