From 2f9aa2e8cf39845acfac08c901084b89efefee9f Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Thu, 1 Aug 2024 17:20:32 +0530 Subject: [PATCH 01/28] Initial commit to create a PR --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1cd2090b..3bb79307 100644 --- a/README.md +++ b/README.md @@ -568,3 +568,4 @@ Professional support, consulting as well as software development services are av https://www.cebe.cc/en/contact Development of this library is sponsored by [cebe.:cloud: "Your Professional Deployment Platform"](https://cebe.cloud). + From fa7c240ab72236ba8cbeb8f1dd28be1e72d737cc Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Fri, 2 Aug 2024 20:39:02 +0530 Subject: [PATCH 02/28] Implement for multiple data types --- Makefile | 3 ++ README.md | 1 - composer.json | 3 +- src/lib/FakerStubResolver.php | 30 ++++++++++---- tests/specs/blog_v2/models/CommentFaker.php | 2 +- .../index.php | 13 +++++++ .../index.yaml | 39 +++++++++++++++++++ tests/unit/IssueFixTest.php | 14 +++++++ 8 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml diff --git a/Makefile b/Makefile index 6d1a22dc..abc62f03 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,9 @@ up: cli: docker-compose exec --user=$(UID) php bash +cli_root: + docker-compose exec --user="root" php bash + cli_mysql: docker-compose exec --user=$(UID) mysql bash diff --git a/README.md b/README.md index 3bb79307..1cd2090b 100644 --- a/README.md +++ b/README.md @@ -568,4 +568,3 @@ Professional support, consulting as well as software development services are av https://www.cebe.cc/en/contact Development of this library is sponsored by [cebe.:cloud: "Your Professional Deployment Platform"](https://cebe.cloud). - diff --git a/composer.json b/composer.json index 735e5f07..f17b6050 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ "laminas/laminas-code": ">=3.4 <=4.13", "php-openapi/yii2-fractal": "^1.0.0", "fakerphp/faker": "^1.9", - "sam-it/yii2-mariadb": "^2.0" + "sam-it/yii2-mariadb": "^2.0", + "symfony/var-exporter": "^5.4" }, "require-dev": { "cebe/indent": "*", diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index bd11bc2a..60132c7c 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -12,6 +12,7 @@ use cebe\yii2openapi\lib\items\Attribute; use cebe\yii2openapi\lib\openapi\PropertySchema; use yii\helpers\VarDumper; +use Symfony\Component\VarExporter\VarExporter; use function str_replace; use const PHP_EOL; @@ -61,7 +62,7 @@ public function resolve():?string return null; } - // column name ends with `_id` + // column name ends with `_id`/FK if (substr($this->attribute->columnName, -3) === '_id' || !empty($this->attribute->fkColName)) { $config = $this->config; if (!$config) { @@ -76,20 +77,34 @@ public function resolve():?string $limits = $this->attribute->limits; switch ($this->attribute->phpType) { case 'bool': - return '$faker->boolean'; + $result = '$faker->boolean'; + break; case 'int': case 'integer': - return $this->fakeForInt($limits['min'], $limits['max']); + $result = $this->fakeForInt($limits['min'], $limits['max']); + break; case 'string': - return $this->fakeForString(); + $result = $this->fakeForString(); + break; case 'float': case 'double': - return $this->fakeForFloat($limits['min'], $limits['max']); + $result = $this->fakeForFloat($limits['min'], $limits['max']); + break; case 'array': - return $this->fakeForArray(); + $result = $this->fakeForArray(); + break; default: return null; } + if (! $this->property->hasAttr('example')) { + return $result; + } + if (stripos($result, 'uniqueFaker') !== false) { + return $result; + } + $example = $this->property->getAttr('example'); + $example = VarExporter::export($example); + return str_replace('$faker->', '$faker->optional(0.92, '.$example.')->', $result); } private function fakeForString():?string @@ -167,8 +182,6 @@ private function fakeForString():?string } } - // TODO maybe also consider OpenAPI examples here - if ($size) { $method = 'text'; if ($size < 5) { @@ -227,6 +240,7 @@ private function fakeForFloat(?int $min, ?int $max):?string private function fakeForArray():string { +// return '$faker->words()'; // TODO if ($this->attribute->required) { return '["a" => "b"]'; } diff --git a/tests/specs/blog_v2/models/CommentFaker.php b/tests/specs/blog_v2/models/CommentFaker.php index 968d1a10..68535e74 100644 --- a/tests/specs/blog_v2/models/CommentFaker.php +++ b/tests/specs/blog_v2/models/CommentFaker.php @@ -33,7 +33,7 @@ public function generateModel($attributes = []) $model->post_id = $faker->randomElement(\app\models\Post::find()->select("id")->column()); $model->user_id = $faker->randomElement(\app\models\User::find()->select("id")->column()); $model->message = $faker->sentence; - $model->meta_data = substr($faker->text(300), 0, 300); + $model->meta_data = substr($faker->optional(0.92, 'type==\'ticket\' && status==\'closed\'')->text(300), 0, 300); $model->created_at = $faker->dateTimeThisYear('now', 'UTC')->format('Y-m-d H:i:s'); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php new file mode 100644 index 00000000..6afa241e --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php @@ -0,0 +1,13 @@ + '@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml', + 'generateUrls' => false, + 'generateModels' => true, +// 'excludeModels' => [ +// 'Error', +// ], + 'generateControllers' => false, + 'generateMigrations' => false, + 'generateModelFaker' => true, // `generateModels` must be `true` in order to use `generateModelFaker` as `true` +]; diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml new file mode 100644 index 00000000..4a72e48a --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -0,0 +1,39 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Consider OpenAPI spec examples in faker code generation https://github.com/php-openapi/yii2-openapi/issues/20 + +paths: + /pet: + get: + summary: get a pet + operationId: aPet + responses: + 200: + description: A pet + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + name: + type: string + example: cat +# example: ['long-tail', 'short-tail', 'black', 'white'] + age: + type: integer + example: 2 + tags: + type: array + items: + type: string + example: ['long-tail', 'short-tail', 'black', 'white'] diff --git a/tests/unit/IssueFixTest.php b/tests/unit/IssueFixTest.php index b6c7abdb..b6a4481a 100644 --- a/tests/unit/IssueFixTest.php +++ b/tests/unit/IssueFixTest.php @@ -360,4 +360,18 @@ public function test158BugGiiapiGeneratedRulesEnumWithTrim() ]); $this->checkFiles($actualFiles, $expectedFiles); } + + // https://github.com/php-openapi/yii2-openapi/issues/20 + public function test20ConsiderOpenApiSpecExamplesInFakeCodeGeneration() + { + $testFile = Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php"); + $this->runGenerator($testFile); + $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ + 'recursive' => true, + ]); + $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql"), [ + 'recursive' => true, + ]); +// $this->checkFiles($actualFiles, $expectedFiles); // TODO + } } From c0405e81e210b01d67dce046a8e4421020218941 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Tue, 6 Aug 2024 18:47:40 +0530 Subject: [PATCH 03/28] Implement faker generation of array - WIP --- src/lib/FakerStubResolver.php | 60 +++++++++++++++++-- .../index.yaml | 38 +++++++++++- tests/unit/IssueFixTest.php | 12 ++-- 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 60132c7c..c215e3e7 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -11,8 +11,9 @@ use cebe\yii2openapi\lib\items\Attribute; use cebe\yii2openapi\lib\openapi\PropertySchema; -use yii\helpers\VarDumper; use Symfony\Component\VarExporter\VarExporter; +use yii\helpers\Json; +use yii\helpers\VarDumper; use function str_replace; use const PHP_EOL; @@ -238,12 +239,61 @@ private function fakeForFloat(?int $min, ?int $max):?string return '$faker->randomFloat()'; } - private function fakeForArray():string + private function fakeForArray(): string { -// return '$faker->words()'; // TODO - if ($this->attribute->required) { - return '["a" => "b"]'; + // TODO consider example of OpenAPI spec + $property = Json::decode(Json::encode($this->property->getProperty()->getSerializableData())); + + $arbitrary = false; + $uniqueItems = false; + $type = null; + $count = 4; # let's set a number to default number of elements + + if (isset($property['items'])) { + $items = $property['items']; + if ($items === []) { + $arbitrary = true; + } + if (isset($items['type'])) { + $type = $items['type']; + } + } + + if (isset($property['minItems'])) { + $minItems = (int)$property['minItems']; + $count = $minItems; + } + + if (isset($property['maxItems'])) { + $maxItems = (int)$property['maxItems']; + if ($maxItems < $count) { + $count = $maxItems; + } } + + if (isset($property['uniqueItems'])) { + $uniqueItems = (bool)$property['uniqueItems']; + } + + if ($arbitrary || $type === 'string') { + return ($uniqueItems ? '$uniqueFaker' : '$faker') . '->words(' . $count . ')'; + } + + if (in_array($type, ['number', 'integer'])) { + return 'array_fill(0, ' . ($count) . ', ' . ($uniqueItems ? '$uniqueFaker' : '$faker') . '->randomNumber())'; + } + + if ($type == 'boolean') { + return 'array_fill(0, ' . ($count) . ', ' . ($uniqueItems ? '$uniqueFaker' : '$faker') . '->boolean())'; + } + + // TODO more complex type array/object; also consider $ref; may be recursively; may use `oneOf` + +// return '$faker->words()'; // TODO implement faker for array; also consider min, max, unique + +// if ($this->attribute->required) { +// return '["a" => "b"]'; // TODO this is incorrect, array schema should be checked first +// } return '[]'; } } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 4a72e48a..325a04f2 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -36,4 +36,40 @@ components: type: array items: type: string - example: ['long-tail', 'short-tail', 'black', 'white'] + tags_arbit: + type: array + items: { } # array of arbitrary types e.g. [ "hello", -2, true, [5.7], {"id": 5} ] + minItems: 6 + maxItems: 10 + uniqueItems: true + # type: string + # example: ['long-tail', 'short-tail', 'black', 'white'] + number_arr: + type: array + items: + type: number + + number_arr_min_uniq: + type: array + items: + type: number + minItems: 6 + uniqueItems: true + + int_arr: + type: array + items: + type: integer + + int_arr_min_uniq: + type: array + items: + type: integer + minItems: 7 + uniqueItems: true + + bool_arr: + type: array + items: + type: boolean + diff --git a/tests/unit/IssueFixTest.php b/tests/unit/IssueFixTest.php index b6a4481a..197a9eca 100644 --- a/tests/unit/IssueFixTest.php +++ b/tests/unit/IssueFixTest.php @@ -366,12 +366,12 @@ public function test20ConsiderOpenApiSpecExamplesInFakeCodeGeneration() { $testFile = Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php"); $this->runGenerator($testFile); - $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ - 'recursive' => true, - ]); - $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql"), [ - 'recursive' => true, - ]); +// $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ +// 'recursive' => true, +// ]); +// $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql"), [ +// 'recursive' => true, +// ]); // $this->checkFiles($actualFiles, $expectedFiles); // TODO } } From 701d6126ea542a87eb9ac0098ad1980ad40ad066 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Thu, 8 Aug 2024 18:43:21 +0530 Subject: [PATCH 04/28] WIP 2 --- src/lib/FakerStubResolver.php | 59 ++++++++++++------- .../index.yaml | 45 +++++++++++++- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index c215e3e7..1089d27f 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -9,10 +9,10 @@ /** @noinspection PhpUndefinedFieldInspection */ namespace cebe\yii2openapi\lib; +use cebe\openapi\SpecObjectInterface; use cebe\yii2openapi\lib\items\Attribute; use cebe\yii2openapi\lib\openapi\PropertySchema; use Symfony\Component\VarExporter\VarExporter; -use yii\helpers\Json; use yii\helpers\VarDumper; use function str_replace; use const PHP_EOL; @@ -92,7 +92,7 @@ public function resolve():?string $result = $this->fakeForFloat($limits['min'], $limits['max']); break; case 'array': - $result = $this->fakeForArray(); + $result = $this->fakeForArray($this->property->getProperty()); break; default: return null; @@ -239,40 +239,39 @@ private function fakeForFloat(?int $min, ?int $max):?string return '$faker->randomFloat()'; } - private function fakeForArray(): string + private function fakeForArray(SpecObjectInterface $property): string { // TODO consider example of OpenAPI spec - $property = Json::decode(Json::encode($this->property->getProperty()->getSerializableData())); - $arbitrary = false; $uniqueItems = false; $type = null; $count = 4; # let's set a number to default number of elements - if (isset($property['items'])) { - $items = $property['items']; - if ($items === []) { + $items = $property->items; + + if ($items) { + $type = $items->type; + if ($items->type === null) { $arbitrary = true; } - if (isset($items['type'])) { - $type = $items['type']; - } + } else { + $arbitrary = true; } - if (isset($property['minItems'])) { - $minItems = (int)$property['minItems']; + if ($property->minItems) { + $minItems = $property->minItems; $count = $minItems; } - if (isset($property['maxItems'])) { - $maxItems = (int)$property['maxItems']; + if ($property->maxItems) { + $maxItems = $property->maxItems; if ($maxItems < $count) { $count = $maxItems; } } - if (isset($property['uniqueItems'])) { - $uniqueItems = (bool)$property['uniqueItems']; + if (isset($property->uniqueItems)) { + $uniqueItems = $property->uniqueItems; } if ($arbitrary || $type === 'string') { @@ -280,13 +279,33 @@ private function fakeForArray(): string } if (in_array($type, ['number', 'integer'])) { - return 'array_fill(0, ' . ($count) . ', ' . ($uniqueItems ? '$uniqueFaker' : '$faker') . '->randomNumber())'; + return 'array_map(function () use ($faker, $uniqueFaker) { + return ' . ($uniqueItems ? '$uniqueFaker' : '$faker') . '->randomNumber(); + }, range(1, ' . $count . '))'; } - if ($type == 'boolean') { - return 'array_fill(0, ' . ($count) . ', ' . ($uniqueItems ? '$uniqueFaker' : '$faker') . '->boolean())'; + if ($type === 'boolean') { + return 'array_map(function () use ($faker, $uniqueFaker) { + return ' . ($uniqueItems ? '$uniqueFaker' : '$faker') . '->boolean(); + }, range(1, ' . $count . '))'; } + if ($type === 'array') { # array or nested arrays + return 'array_map(function () use ($faker, $uniqueFaker) { + return ' . $this->fakeForArray($items) . '; + }, range(1, ' . $count . '))'; + } +// +// if ($type === 'object') { +// $props = []; +// foreach ($items['properties'] ?? [] as $name => $prop) { +// $props[$name] = $this->fakeForArray($prop); +// } +// return 'array_map(function () use ($faker, $uniqueFaker) { +// return ' .VarExporter::export($props[$name]). '; +// }, range(1, '.$count.'))'; +// } + // TODO more complex type array/object; also consider $ref; may be recursively; may use `oneOf` // return '$faker->words()'; // TODO implement faker for array; also consider min, max, unique diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 325a04f2..83b4a75c 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -1,7 +1,7 @@ openapi: "3.0.0" info: version: 1.0.0 - title: Consider OpenAPI spec examples in faker code generation https://github.com/php-openapi/yii2-openapi/issues/20 + title: Consider OpenAPI spec examples in faker code generation https://github.com/php-openapi/yii2-openapi/issues/20. And also generate faker for arrays paths: /pet: @@ -18,6 +18,12 @@ paths: components: schemas: + User: + properties: + id: + type: integer + name: + type: string Pet: required: - id @@ -73,3 +79,40 @@ components: items: type: boolean + arr_arr_int: # [ [1, 2], [3, 4], [5, 6, 7] ] + type: array + items: + type: array + items: + type: integer + + arr_arr_str: + type: array + items: + type: array + items: + type: string + + arr_arr_arr_str: + type: array + items: + type: array + items: + type: array + items: + type: string + + arr_of_obj: + type: array + items: + type: object + properties: + id: + type: integer +# +# ref_obj_arr: +# type: array +# items: +# $ref: '#/components/schemas/User' + +# oneOf From 5b87057864e7590c4a02b0b2c04d178abc03a4cc Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Thu, 8 Aug 2024 20:03:30 +0530 Subject: [PATCH 05/28] Implement for object and obj with array & nested object - WIP --- src/lib/FakerStubResolver.php | 30 ++++++++++++------- .../index.yaml | 30 ++++++++++++++++++- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 1089d27f..6d7df142 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -11,6 +11,8 @@ use cebe\openapi\SpecObjectInterface; use cebe\yii2openapi\lib\items\Attribute; +use cebe\yii2openapi\lib\items\JunctionSchemas; +use cebe\yii2openapi\lib\openapi\ComponentSchema; use cebe\yii2openapi\lib\openapi\PropertySchema; use Symfony\Component\VarExporter\VarExporter; use yii\helpers\VarDumper; @@ -251,7 +253,7 @@ private function fakeForArray(SpecObjectInterface $property): string if ($items) { $type = $items->type; - if ($items->type === null) { + if ($type === null) { $arbitrary = true; } } else { @@ -295,16 +297,22 @@ private function fakeForArray(SpecObjectInterface $property): string return ' . $this->fakeForArray($items) . '; }, range(1, ' . $count . '))'; } -// -// if ($type === 'object') { -// $props = []; -// foreach ($items['properties'] ?? [] as $name => $prop) { -// $props[$name] = $this->fakeForArray($prop); -// } -// return 'array_map(function () use ($faker, $uniqueFaker) { -// return ' .VarExporter::export($props[$name]). '; -// }, range(1, '.$count.'))'; -// } + + if ($type === 'object') { + $props = []; + $cs = new ComponentSchema($items, 'somename'); + $dbModels = (new AttributeResolver('somename', $cs, new JunctionSchemas([])))->resolve(); + + foreach ($items->properties ?? [] as $name => $prop) { + $ps = new PropertySchema($prop, $name, $cs); + $attr = $dbModels->attributes[$name]; + $props[$name] = (new static($attr, $ps))->resolve(); + } + $props = str_replace(["' => '", '\',' . PHP_EOL], ["' => ", ',' . PHP_EOL], VarExporter::export($props)); + return 'array_map(function () use ($faker, $uniqueFaker) { + return ' . $props . '; + }, range(1, ' . $count . '))'; + } // TODO more complex type array/object; also consider $ref; may be recursively; may use `oneOf` diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 83b4a75c..fa917155 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -104,13 +104,41 @@ components: arr_of_obj: type: array + minItems: 3 items: type: object properties: id: type: integer + name: + type: string + age: + type: integer + minimum: 0 + maximum: 200 + tags: + type: array + items: + type: string + uniqueItems: true + + # arr_arr_int_2: # [ [1, 2], [3, 4], [5, 6, 7] ] + # type: array + # items: + # type: array + # items: + # type: integer + + appearance: + type: object + properties: + height: + type: integer + weight: + type: integer + # -# ref_obj_arr: +# user_ref_obj_arr: # type: array # items: # $ref: '#/components/schemas/User' From 46b8d48944bfec20d489ceb8ddf3e0e233953170 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Fri, 9 Aug 2024 19:09:11 +0530 Subject: [PATCH 06/28] Implement for object and obj with nested array & nested object --- src/lib/FakerStubResolver.php | 56 ++++++++++++++----- .../index.yaml | 13 +++-- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 6d7df142..9c6b2715 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -9,6 +9,8 @@ /** @noinspection PhpUndefinedFieldInspection */ namespace cebe\yii2openapi\lib; +use cebe\openapi\spec\Reference; +use cebe\openapi\spec\Schema; use cebe\openapi\SpecObjectInterface; use cebe\yii2openapi\lib\items\Attribute; use cebe\yii2openapi\lib\items\JunctionSchemas; @@ -249,6 +251,7 @@ private function fakeForArray(SpecObjectInterface $property): string $type = null; $count = 4; # let's set a number to default number of elements + /** @var Schema|Reference|null $items */ $items = $property->items; if ($items) { @@ -299,19 +302,7 @@ private function fakeForArray(SpecObjectInterface $property): string } if ($type === 'object') { - $props = []; - $cs = new ComponentSchema($items, 'somename'); - $dbModels = (new AttributeResolver('somename', $cs, new JunctionSchemas([])))->resolve(); - - foreach ($items->properties ?? [] as $name => $prop) { - $ps = new PropertySchema($prop, $name, $cs); - $attr = $dbModels->attributes[$name]; - $props[$name] = (new static($attr, $ps))->resolve(); - } - $props = str_replace(["' => '", '\',' . PHP_EOL], ["' => ", ',' . PHP_EOL], VarExporter::export($props)); - return 'array_map(function () use ($faker, $uniqueFaker) { - return ' . $props . '; - }, range(1, ' . $count . '))'; + return $this->handleObject($items, $count); } // TODO more complex type array/object; also consider $ref; may be recursively; may use `oneOf` @@ -323,4 +314,43 @@ private function fakeForArray(SpecObjectInterface $property): string // } return '[]'; } + + /** + * @param $items Schema|Reference|null + * @param $count int + * @return string + * @throws \cebe\openapi\exceptions\UnresolvableReferenceException + * @throws \yii\base\InvalidConfigException + * @throws exceptions\InvalidDefinitionException + * @internal + */ + public function handleObject($items, $count, $nested = false): string + { + $props = '[' . PHP_EOL; + $cs = new ComponentSchema($items, 'unnamed'); + $dbModels = (new AttributeResolver('unnamed', $cs, new JunctionSchemas([])))->resolve(); + + foreach ($items->properties ?? [] as $name => $prop) { + /** @var SpecObjectInterface $prop */ + + if ($prop->properties) { // object + $result = $this->handleObject($prop, $count, true); + } else { + $ps = new PropertySchema($prop, $name, $cs); + $attr = $dbModels->attributes[$name]; + $result = (string)((new static($attr, $ps))->resolve()); + } + + $props .= '\'' . $name . '\' => ' . $result . ',' . PHP_EOL; + } + $props .= ']'; + + if ($nested) { + return $props; + } + + return 'array_map(function () use ($faker, $uniqueFaker) { + return ' . $props . '; + }, range(1, ' . $count . '))'; + } } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index fa917155..73100844 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -122,18 +122,19 @@ components: type: string uniqueItems: true - # arr_arr_int_2: # [ [1, 2], [3, 4], [5, 6, 7] ] - # type: array - # items: - # type: array - # items: - # type: integer + arr_arr_int_2: # [ [1, 2], [3, 4], [5, 6, 7] ] + type: array + items: + type: array + items: + type: integer appearance: type: object properties: height: type: integer + maximum: 20 weight: type: integer From c1bc83169ee69ae43ccebdd53d2b630f86f23ed2 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Fri, 9 Aug 2024 20:53:20 +0530 Subject: [PATCH 07/28] Implement faker generation for array of referenced component schema --- src/lib/AttributeResolver.php | 7 ++-- src/lib/FakerStubResolver.php | 40 +++++++++---------- src/lib/openapi/PropertySchema.php | 18 ++++----- .../index.yaml | 12 +++--- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index 0063d8df..b6725f4e 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -7,8 +7,6 @@ namespace cebe\yii2openapi\lib; -use cebe\yii2openapi\lib\Config; -use cebe\yii2openapi\lib\CustomSpecAttr; use cebe\yii2openapi\lib\exceptions\InvalidDefinitionException; use cebe\yii2openapi\lib\items\Attribute; use cebe\yii2openapi\lib\items\AttributeRelation; @@ -22,7 +20,6 @@ use Yii; use yii\helpers\Inflector; use yii\helpers\StringHelper; -use yii\helpers\VarDumper; use function explode; use function strpos; use function strtolower; @@ -342,6 +339,10 @@ protected function resolveProperty( return; } $attribute->setPhpType($relatedClassName . '[]'); + + $this->attributes[$property->getName()] = + $attribute->setFakerStub($this->guessFakerStub($attribute, $property)); // TODO + $this->relations[$property->getName()] = Yii::createObject( AttributeRelation::class, diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 9c6b2715..5b19f712 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -80,27 +80,22 @@ public function resolve():?string } $limits = $this->attribute->limits; - switch ($this->attribute->phpType) { - case 'bool': - $result = '$faker->boolean'; - break; - case 'int': - case 'integer': - $result = $this->fakeForInt($limits['min'], $limits['max']); - break; - case 'string': - $result = $this->fakeForString(); - break; - case 'float': - case 'double': - $result = $this->fakeForFloat($limits['min'], $limits['max']); - break; - case 'array': - $result = $this->fakeForArray($this->property->getProperty()); - break; - default: - return null; + + if ($this->attribute->phpType === 'bool') { + $result = '$faker->boolean'; + } elseif (in_array($this->attribute->phpType, ['int', 'integer'])) { + $result = $this->fakeForInt($limits['min'], $limits['max']); + } elseif ($this->attribute->phpType === 'string') { + $result = $this->fakeForString(); + } elseif (in_array($this->attribute->phpType, ['float', 'double'])) { + $result = $this->fakeForFloat($limits['min'], $limits['max']); + } elseif ($this->attribute->phpType === 'array' || + substr($this->attribute->phpType, -2) === '[]') { + $result = $this->fakeForArray($this->property->getProperty()); + } else { + return null; } + if (! $this->property->hasAttr('example')) { return $result; } @@ -255,6 +250,11 @@ private function fakeForArray(SpecObjectInterface $property): string $items = $property->items; if ($items) { + if ($items instanceof Reference) { + $class = str_replace('#/components/schemas/', '', $items->getReference()); + $class .= 'Faker'; + return '(new ' . $class . ')->generateModel()->attributes'; + } $type = $items->type; if ($type === null) { $arbitrary = true; diff --git a/src/lib/openapi/PropertySchema.php b/src/lib/openapi/PropertySchema.php index 6b27def1..7d001e13 100644 --- a/src/lib/openapi/PropertySchema.php +++ b/src/lib/openapi/PropertySchema.php @@ -7,29 +7,27 @@ namespace cebe\yii2openapi\lib\openapi; -use yii\db\ColumnSchema; -use cebe\yii2openapi\generator\ApiGenerator; -use yii\db\mysql\Schema as MySqlSchema; -use SamIT\Yii2\MariaDb\Schema as MariaDbSchema; -use yii\db\pgsql\Schema as PgSqlSchema; -use cebe\yii2openapi\lib\items\Attribute; -use yii\base\NotSupportedException; use BadMethodCallException; use cebe\openapi\ReferenceContext; use cebe\openapi\spec\Reference; use cebe\openapi\SpecObjectInterface; +use cebe\yii2openapi\generator\ApiGenerator; use cebe\yii2openapi\lib\CustomSpecAttr; use cebe\yii2openapi\lib\exceptions\InvalidDefinitionException; +use cebe\yii2openapi\lib\traits\ForeignKeyConstraints; +use SamIT\Yii2\MariaDb\Schema as MariaDbSchema; use Throwable; use Yii; +use yii\base\NotSupportedException; +use yii\db\ColumnSchema; +use yii\db\mysql\Schema as MySqlSchema; +use yii\db\pgsql\Schema as PgSqlSchema; use yii\db\Schema as YiiDbSchema; use yii\helpers\Inflector; use yii\helpers\Json; use yii\helpers\StringHelper; -use yii\helpers\VarDumper; use function is_int; use function strpos; -use cebe\yii2openapi\lib\traits\ForeignKeyConstraints; class PropertySchema { @@ -410,8 +408,6 @@ public function guessPhpType():string return 'bool'; case 'number': // can be double and float return $this->getAttr('format') === 'double' ? 'double' : 'float'; -// case 'array': -// return $property->type; default: return $this->getAttr('type', 'string'); } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 73100844..558b9f42 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -137,11 +137,13 @@ components: maximum: 20 weight: type: integer + email: + type: string + format: email -# -# user_ref_obj_arr: -# type: array -# items: -# $ref: '#/components/schemas/User' + user_ref_obj_arr: + type: array + items: + $ref: '#/components/schemas/User' # oneOf From 2f08c41b7f16338d55a41a701684ed731e594b61 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Tue, 13 Aug 2024 16:44:57 +0530 Subject: [PATCH 08/28] Implement oneOf and refactor - WIP --- src/lib/FakerStubResolver.php | 79 ++++++++++++++++--- .../index.yaml | 14 ++++ 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 5b19f712..0aa9dd0c 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -238,34 +238,36 @@ private function fakeForFloat(?int $min, ?int $max):?string return '$faker->randomFloat()'; } - private function fakeForArray(SpecObjectInterface $property): string + private function fakeForArray(SpecObjectInterface $property, int $count = 4): string { // TODO consider example of OpenAPI spec $arbitrary = false; $uniqueItems = false; $type = null; - $count = 4; # let's set a number to default number of elements +// $count = 4; # let's set a number to default number of elements /** @var Schema|Reference|null $items */ - $items = $property->items; + $items = $property->items ?? $property; # later is used in `oneOf` if ($items) { if ($items instanceof Reference) { $class = str_replace('#/components/schemas/', '', $items->getReference()); $class .= 'Faker'; return '(new ' . $class . ')->generateModel()->attributes'; - } - $type = $items->type; - if ($type === null) { - $arbitrary = true; + } elseif (!empty($items->oneOf)) { + return $this->handleOneOf($items, $count); + } else { + $type = $items->type; + if ($type === null) { + $arbitrary = true; + } } } else { $arbitrary = true; } if ($property->minItems) { - $minItems = $property->minItems; - $count = $minItems; + $count = $property->minItems; } if ($property->maxItems) { @@ -297,7 +299,7 @@ private function fakeForArray(SpecObjectInterface $property): string if ($type === 'array') { # array or nested arrays return 'array_map(function () use ($faker, $uniqueFaker) { - return ' . $this->fakeForArray($items) . '; + return ' . $this->{__FUNCTION__}($items) . '; }, range(1, ' . $count . '))'; } @@ -305,6 +307,7 @@ private function fakeForArray(SpecObjectInterface $property): string return $this->handleObject($items, $count); } + // TODO more complex type array/object; also consider $ref; may be recursively; may use `oneOf` // return '$faker->words()'; // TODO implement faker for array; also consider min, max, unique @@ -334,7 +337,7 @@ public function handleObject($items, $count, $nested = false): string /** @var SpecObjectInterface $prop */ if ($prop->properties) { // object - $result = $this->handleObject($prop, $count, true); + $result = $this->{__FUNCTION__}($prop, $count, true); } else { $ps = new PropertySchema($prop, $name, $cs); $attr = $dbModels->attributes[$name]; @@ -353,4 +356,58 @@ public function handleObject($items, $count, $nested = false): string return ' . $props . '; }, range(1, ' . $count . '))'; } + + /** + * @param $items + * @param $count + * @return string + * @internal + */ + public function handleOneOf($items, $count): string + { + $fakerForADataType = []; + $result = 'array_map(function () use ($faker, $uniqueFaker) {'; + foreach ($items->oneOf as $key => $aDataType) { + /** @var Schema|Reference $aDataType */ +// $fakerForADataType[] = $this->{__FUNCTION__}($aDataType); + $a1 = $this->fakeForArray($aDataType, 1); + $result .= '$dataType' . $key . ' = ' . $a1 . ';'; + } + $ct = count($items->oneOf) - 1; + $result .= 'return ${"dataType".rand(0, ' . $ct . ')};'; +// $items = $items->oneOf[$rand]; + +// $dataType'..' +// return ""; + $result .= '}, range(1, ' . $count . '))'; + return $result; + } + + public function aString() + { + } + + public function aNumber() + { + } + + public function aInteger() + { + } + + public function aBoolean() + { + } + + public function aArray() + { + } + + public function aObject() + { + } + + public function aRefObj() + { + } } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 558b9f42..cce26484 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -140,10 +140,24 @@ components: email: type: string format: email + nested_obj: + type: object + properties: + id: + type: integer user_ref_obj_arr: type: array items: $ref: '#/components/schemas/User' + one_of_arr: + type: array # ["foo", 5, -2, "bar"] + maxItems: 8 + items: + oneOf: + - type: string + - type: integer + + # oneOf From 9f83e78622b829878c484cc18bb03530700bb86b Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Tue, 13 Aug 2024 19:54:26 +0530 Subject: [PATCH 09/28] Implement oneOf and refactor - WIP 2 --- src/lib/FakerStubResolver.php | 183 +++++++++--------- .../index.yaml | 1 - 2 files changed, 89 insertions(+), 95 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 0aa9dd0c..8a7a9b19 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -7,16 +7,23 @@ /** @noinspection InterfacesAsConstructorDependenciesInspection */ /** @noinspection PhpUndefinedFieldInspection */ + namespace cebe\yii2openapi\lib; +use cebe\openapi\exceptions\TypeErrorException; +use cebe\openapi\exceptions\UnresolvableReferenceException; use cebe\openapi\spec\Reference; use cebe\openapi\spec\Schema; use cebe\openapi\SpecObjectInterface; +use cebe\yii2openapi\lib\exceptions\InvalidDefinitionException; use cebe\yii2openapi\lib\items\Attribute; use cebe\yii2openapi\lib\items\JunctionSchemas; use cebe\yii2openapi\lib\openapi\ComponentSchema; use cebe\yii2openapi\lib\openapi\PropertySchema; +use Symfony\Component\VarExporter\Exception\ExceptionInterface; use Symfony\Component\VarExporter\VarExporter; +use yii\base\InvalidConfigException; +use yii\helpers\Json; use yii\helpers\VarDumper; use function str_replace; use const PHP_EOL; @@ -28,18 +35,12 @@ class FakerStubResolver { public const MAX_INT = 1000000; - /** - * @var \cebe\yii2openapi\lib\items\Attribute - */ - private $attribute; - /** - * @var \cebe\yii2openapi\lib\openapi\PropertySchema - */ - private $property; + private Attribute $attribute; - /** @var Config */ - private $config; + private PropertySchema $property; + + private ?Config $config; public function __construct(Attribute $attribute, PropertySchema $property, ?Config $config = null) { @@ -48,7 +49,14 @@ public function __construct(Attribute $attribute, PropertySchema $property, ?Con $this->config = $config; } - public function resolve():?string + /** + * @throws InvalidConfigException + * @throws TypeErrorException + * @throws UnresolvableReferenceException + * @throws InvalidDefinitionException + * @throws ExceptionInterface + */ + public function resolve(): ?string { if ($this->property->xFaker === false) { $this->attribute->setFakerStub(null); @@ -74,9 +82,9 @@ public function resolve():?string $config = new Config; } $mn = $config->modelNamespace; - return '$faker->randomElement(\\'.$mn - . ($mn ? '\\' : '') - . ucfirst($this->attribute->reference).'::find()->select("id")->column())'; + return '$faker->randomElement(\\' . $mn + . ($mn ? '\\' : '') + . ucfirst($this->attribute->reference) . '::find()->select("id")->column())'; } $limits = $this->attribute->limits; @@ -96,7 +104,7 @@ public function resolve():?string return null; } - if (! $this->property->hasAttr('example')) { + if (!$this->property->hasAttr('example')) { return $result; } if (stripos($result, 'uniqueFaker') !== false) { @@ -104,10 +112,10 @@ public function resolve():?string } $example = $this->property->getAttr('example'); $example = VarExporter::export($example); - return str_replace('$faker->', '$faker->optional(0.92, '.$example.')->', $result); + return str_replace('$faker->', '$faker->optional(0.92, ' . $example . ')->', $result); } - private function fakeForString():?string + private function fakeForString(): ?string { $formats = [ 'date' => '$faker->dateTimeThisCentury->format(\'Y-m-d\')', @@ -127,7 +135,7 @@ private function fakeForString():?string } $enum = $this->property->getAttr('enum'); if (!empty($enum) && is_array($enum)) { - $items = str_replace([PHP_EOL, ' ',',]'], ['', '', ']'], VarDumper::export($enum)); + $items = str_replace([PHP_EOL, ' ', ',]'], ['', '', ']'], VarDumper::export($enum)); return '$faker->randomElement(' . $items . ')'; } if ($this->attribute->columnName === 'title' @@ -172,7 +180,7 @@ private function fakeForString():?string '~(url|site|website|href)~i' => '$faker->url', '~(username|login)~i' => '$faker->userName', ]; - $size = $this->attribute->size > 0 ? $this->attribute->size: null; + $size = $this->attribute->size > 0 ? $this->attribute->size : null; foreach ($patterns as $pattern => $fake) { if (preg_match($pattern, $this->attribute->columnName)) { if ($size) { @@ -187,27 +195,27 @@ private function fakeForString():?string if ($size < 5) { $method = 'word'; } - return 'substr($faker->'.$method.'(' . $size . '), 0, ' . $size . ')'; + return 'substr($faker->' . $method . '(' . $size . '), 0, ' . $size . ')'; } return '$faker->sentence'; } - private function fakeForInt(?int $min, ?int $max):?string + private function fakeForInt(?int $min, ?int $max): ?string { $fakerVariable = 'faker'; if (preg_match('~_?id$~', $this->attribute->columnName)) { $fakerVariable = 'uniqueFaker'; } if ($min !== null && $max !== null) { - return "\${$fakerVariable}->numberBetween($min, $max)"; + return "\$$fakerVariable->numberBetween($min, $max)"; } if ($min !== null) { - return "\${$fakerVariable}->numberBetween($min, ".self::MAX_INT.")"; + return "\$$fakerVariable->numberBetween($min, " . self::MAX_INT . ")"; } if ($max !== null) { - return "\${$fakerVariable}->numberBetween(0, $max)"; + return "\$$fakerVariable->numberBetween(0, $max)"; } $patterns = [ @@ -221,10 +229,10 @@ private function fakeForInt(?int $min, ?int $max):?string return $fake; } } - return "\${$fakerVariable}->numberBetween(0, ".self::MAX_INT.")"; + return "\$$fakerVariable->numberBetween(0, " . self::MAX_INT . ")"; } - private function fakeForFloat(?int $min, ?int $max):?string + private function fakeForFloat(?int $min, ?int $max): ?string { if ($min !== null && $max !== null) { return "\$faker->randomFloat(null, $min, $max)"; @@ -238,22 +246,49 @@ private function fakeForFloat(?int $min, ?int $max):?string return '$faker->randomFloat()'; } + /** + * @throws InvalidConfigException + * @throws TypeErrorException + * @throws UnresolvableReferenceException + * @throws InvalidDefinitionException|ExceptionInterface + */ private function fakeForArray(SpecObjectInterface $property, int $count = 4): string { - // TODO consider example of OpenAPI spec - $arbitrary = false; $uniqueItems = false; + $arbitrary = false; $type = null; + if ($property->minItems) { + $count = $property->minItems; + } + if ($property->maxItems) { + $maxItems = $property->maxItems; + if ($maxItems < $count) { + $count = $maxItems; + } + } + if (isset($property->uniqueItems)) { + $uniqueItems = $property->uniqueItems; + } + + // TODO consider example of OpenAPI spec + // $count = 4; # let's set a number to default number of elements /** @var Schema|Reference|null $items */ $items = $property->items ?? $property; # later is used in `oneOf` + $aElementData = Json::decode(Json::encode($this->property->getProperty()->getSerializableData())); + $compoSchemaArr = [ + 'properties' => [ + 'unnamedProp' => $aElementData['items'] + ] + ]; + if ($items) { if ($items instanceof Reference) { $class = str_replace('#/components/schemas/', '', $items->getReference()); $class .= 'Faker'; - return '(new ' . $class . ')->generateModel()->attributes'; + return $this->wrapAsArray('(new ' . $class . ')->generateModel()->attributes', false, $count); } elseif (!empty($items->oneOf)) { return $this->handleOneOf($items, $count); } else { @@ -261,46 +296,24 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st if ($type === null) { $arbitrary = true; } + $cs = new ComponentSchema(new Schema($compoSchemaArr), 'UnnamedCompo'); + $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([])))->resolve(); + $aElementFaker = (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp')))->resolve(); } } else { $arbitrary = true; } - if ($property->minItems) { - $count = $property->minItems; + if ($arbitrary) { + return '$faker->words()'; } - if ($property->maxItems) { - $maxItems = $property->maxItems; - if ($maxItems < $count) { - $count = $maxItems; - } - } - - if (isset($property->uniqueItems)) { - $uniqueItems = $property->uniqueItems; - } - - if ($arbitrary || $type === 'string') { - return ($uniqueItems ? '$uniqueFaker' : '$faker') . '->words(' . $count . ')'; - } - - if (in_array($type, ['number', 'integer'])) { - return 'array_map(function () use ($faker, $uniqueFaker) { - return ' . ($uniqueItems ? '$uniqueFaker' : '$faker') . '->randomNumber(); - }, range(1, ' . $count . '))'; - } - - if ($type === 'boolean') { - return 'array_map(function () use ($faker, $uniqueFaker) { - return ' . ($uniqueItems ? '$uniqueFaker' : '$faker') . '->boolean(); - }, range(1, ' . $count . '))'; + if (in_array($type, ['string', 'number', 'integer', 'boolean'])) { + return $this->wrapAsArray($aElementFaker, $uniqueItems, $count); } if ($type === 'array') { # array or nested arrays - return 'array_map(function () use ($faker, $uniqueFaker) { - return ' . $this->{__FUNCTION__}($items) . '; - }, range(1, ' . $count . '))'; + return $this->{__FUNCTION__}($items); } if ($type === 'object') { @@ -321,13 +334,16 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st /** * @param $items Schema|Reference|null * @param $count int + * @param bool $nested * @return string - * @throws \cebe\openapi\exceptions\UnresolvableReferenceException - * @throws \yii\base\InvalidConfigException - * @throws exceptions\InvalidDefinitionException + * @throws ExceptionInterface + * @throws InvalidConfigException + * @throws InvalidDefinitionException + * @throws TypeErrorException + * @throws UnresolvableReferenceException * @internal */ - public function handleObject($items, $count, $nested = false): string + public function handleObject(Schema $items, int $count, bool $nested = false): string { $props = '[' . PHP_EOL; $cs = new ComponentSchema($items, 'unnamed'); @@ -361,53 +377,32 @@ public function handleObject($items, $count, $nested = false): string * @param $items * @param $count * @return string + * @throws ExceptionInterface + * @throws InvalidConfigException + * @throws InvalidDefinitionException + * @throws TypeErrorException + * @throws UnresolvableReferenceException * @internal */ public function handleOneOf($items, $count): string { - $fakerForADataType = []; $result = 'array_map(function () use ($faker, $uniqueFaker) {'; foreach ($items->oneOf as $key => $aDataType) { /** @var Schema|Reference $aDataType */ -// $fakerForADataType[] = $this->{__FUNCTION__}($aDataType); + $a1 = $this->fakeForArray($aDataType, 1); $result .= '$dataType' . $key . ' = ' . $a1 . ';'; } $ct = count($items->oneOf) - 1; $result .= 'return ${"dataType".rand(0, ' . $ct . ')};'; -// $items = $items->oneOf[$rand]; - -// $dataType'..' -// return ""; $result .= '}, range(1, ' . $count . '))'; return $result; } - public function aString() - { - } - - public function aNumber() - { - } - - public function aInteger() - { - } - - public function aBoolean() - { - } - - public function aArray() - { - } - - public function aObject() - { - } - - public function aRefObj() + public function wrapAsArray($aElementFaker, $uniqueItems, $count): string { + return 'array_map(function () use ($faker, $uniqueFaker) { + return ' . ($uniqueItems ? str_replace('$faker->', '$uniqueFaker->', $aElementFaker) : $aElementFaker) . '; + }, range(1, ' . $count . '))'; } } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index cce26484..782068b9 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -121,7 +121,6 @@ components: items: type: string uniqueItems: true - arr_arr_int_2: # [ [1, 2], [3, 4], [5, 6, 7] ] type: array items: From b3c660bf9cfe9297484c399d7cbd37fcb99f0997 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Tue, 13 Aug 2024 21:14:29 +0530 Subject: [PATCH 10/28] Implement oneOf and refactor - WIP 3 --- src/lib/FakerStubResolver.php | 75 ++++++++++--------- .../index.yaml | 2 +- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 8a7a9b19..2f4cf444 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -207,15 +207,15 @@ private function fakeForInt(?int $min, ?int $max): ?string $fakerVariable = 'uniqueFaker'; } if ($min !== null && $max !== null) { - return "\$$fakerVariable->numberBetween($min, $max)"; + return "\${$fakerVariable}->numberBetween($min, $max)"; } if ($min !== null) { - return "\$$fakerVariable->numberBetween($min, " . self::MAX_INT . ")"; + return "\${$fakerVariable}->numberBetween($min, " . self::MAX_INT . ")"; } if ($max !== null) { - return "\$$fakerVariable->numberBetween(0, $max)"; + return "\${$fakerVariable}->numberBetween(0, $max)"; } $patterns = [ @@ -229,7 +229,7 @@ private function fakeForInt(?int $min, ?int $max): ?string return $fake; } } - return "\$$fakerVariable->numberBetween(0, " . self::MAX_INT . ")"; + return "\${$fakerVariable}->numberBetween(0, " . self::MAX_INT . ")"; } private function fakeForFloat(?int $min, ?int $max): ?string @@ -247,6 +247,7 @@ private function fakeForFloat(?int $min, ?int $max): ?string } /** + * @param int $count let's set a number to default number of elements * @throws InvalidConfigException * @throws TypeErrorException * @throws UnresolvableReferenceException @@ -255,8 +256,6 @@ private function fakeForFloat(?int $min, ?int $max): ?string private function fakeForArray(SpecObjectInterface $property, int $count = 4): string { $uniqueItems = false; - $arbitrary = false; - $type = null; if ($property->minItems) { $count = $property->minItems; } @@ -272,41 +271,26 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st // TODO consider example of OpenAPI spec -// $count = 4; # let's set a number to default number of elements - /** @var Schema|Reference|null $items */ - $items = $property->items ?? $property; # later is used in `oneOf` + $items = $property->items; # later is used only in `oneOf` - $aElementData = Json::decode(Json::encode($this->property->getProperty()->getSerializableData())); - $compoSchemaArr = [ - 'properties' => [ - 'unnamedProp' => $aElementData['items'] - ] - ]; + if (!$items) { + return $this->arbitraryArray(); + } - if ($items) { - if ($items instanceof Reference) { - $class = str_replace('#/components/schemas/', '', $items->getReference()); - $class .= 'Faker'; - return $this->wrapAsArray('(new ' . $class . ')->generateModel()->attributes', false, $count); - } elseif (!empty($items->oneOf)) { - return $this->handleOneOf($items, $count); - } else { - $type = $items->type; - if ($type === null) { - $arbitrary = true; - } - $cs = new ComponentSchema(new Schema($compoSchemaArr), 'UnnamedCompo'); - $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([])))->resolve(); - $aElementFaker = (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp')))->resolve(); - } - } else { - $arbitrary = true; + if ($items instanceof Reference) { + $class = str_replace('#/components/schemas/', '', $items->getReference()); + $class .= 'Faker'; + return $this->wrapAsArray('(new ' . $class . ')->generateModel()->attributes', false, $count); + } elseif (!empty($items->oneOf)) { + return $this->handleOneOf($items, $count); } - if ($arbitrary) { - return '$faker->words()'; + $type = $items->type; + if ($type === null) { + return $this->arbitraryArray(); } + $aElementFaker = $this->aElementFaker(); if (in_array($type, ['string', 'number', 'integer', 'boolean'])) { return $this->wrapAsArray($aElementFaker, $uniqueItems, $count); @@ -390,7 +374,8 @@ public function handleOneOf($items, $count): string foreach ($items->oneOf as $key => $aDataType) { /** @var Schema|Reference $aDataType */ - $a1 = $this->fakeForArray($aDataType, 1); +// $a1 = $this->fakeForArray($aDataType, 1); + $a1 = $this->aElementFaker(); $result .= '$dataType' . $key . ' = ' . $a1 . ';'; } $ct = count($items->oneOf) - 1; @@ -405,4 +390,22 @@ public function wrapAsArray($aElementFaker, $uniqueItems, $count): string return ' . ($uniqueItems ? str_replace('$faker->', '$uniqueFaker->', $aElementFaker) : $aElementFaker) . '; }, range(1, ' . $count . '))'; } + + public function arbitraryArray(): string + { + return '$faker->words()'; + } + + public function aElementFaker(): ?string + { + $aElementData = Json::decode(Json::encode($this->property->getProperty()->getSerializableData())); + $compoSchemaData = [ + 'properties' => [ + 'unnamedProp' => $aElementData['items'] + ] + ]; + $cs = new ComponentSchema(new Schema($compoSchemaData), 'UnnamedCompo'); + $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([])))->resolve(); + return (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp')))->resolve(); + } } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 782068b9..bfc113af 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -155,8 +155,8 @@ components: maxItems: 8 items: oneOf: - - type: string - type: integer + - type: string # oneOf From 1e7cea619e206e4c0929c302227fdfbdd07ed5a5 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Tue, 13 Aug 2024 21:24:55 +0530 Subject: [PATCH 11/28] Complex oneOf - WIP --- src/lib/FakerStubResolver.php | 12 ++++++------ .../index.yaml | 11 ++++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 2f4cf444..b280927c 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -272,7 +272,7 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st // TODO consider example of OpenAPI spec /** @var Schema|Reference|null $items */ - $items = $property->items; # later is used only in `oneOf` + $items = $property->items; if (!$items) { return $this->arbitraryArray(); @@ -290,7 +290,7 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st if ($type === null) { return $this->arbitraryArray(); } - $aElementFaker = $this->aElementFaker(); + $aElementFaker = $this->aElementFaker($this->property->getProperty()->getSerializableData()); if (in_array($type, ['string', 'number', 'integer', 'boolean'])) { return $this->wrapAsArray($aElementFaker, $uniqueItems, $count); @@ -375,7 +375,7 @@ public function handleOneOf($items, $count): string /** @var Schema|Reference $aDataType */ // $a1 = $this->fakeForArray($aDataType, 1); - $a1 = $this->aElementFaker(); + $a1 = $this->aElementFaker($aDataType->getSerializableData()); $result .= '$dataType' . $key . ' = ' . $a1 . ';'; } $ct = count($items->oneOf) - 1; @@ -396,12 +396,12 @@ public function arbitraryArray(): string return '$faker->words()'; } - public function aElementFaker(): ?string + public function aElementFaker($data): ?string { - $aElementData = Json::decode(Json::encode($this->property->getProperty()->getSerializableData())); + $aElementData = Json::decode(Json::encode($data)); $compoSchemaData = [ 'properties' => [ - 'unnamedProp' => $aElementData['items'] + 'unnamedProp' => $aElementData['items'] ?? $aElementData # later is used only in `oneOf` ] ]; $cs = new ComponentSchema(new Schema($compoSchemaData), 'UnnamedCompo'); diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index bfc113af..9369ecbb 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -34,7 +34,7 @@ components: name: type: string example: cat -# example: ['long-tail', 'short-tail', 'black', 'white'] + # example: ['long-tail', 'short-tail', 'black', 'white'] age: type: integer example: 2 @@ -157,6 +157,15 @@ components: oneOf: - type: integer - type: string + - type: boolean + - type: array + - type: array + items: + type: string + - type: object + properties: + id: + type: integer # oneOf From 14b2146e556c7d585b85b61534cd55677dbd86e6 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Wed, 14 Aug 2024 20:16:30 +0530 Subject: [PATCH 12/28] Implement complex oneOf + fix error 'Creating default object from empty value in PHP' --- composer.json | 4 +- src/lib/FakerStubResolver.php | 62 ++++++++----------- .../index.yaml | 13 ++++ 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/composer.json b/composer.json index f17b6050..fecf3eca 100644 --- a/composer.json +++ b/composer.json @@ -19,14 +19,14 @@ }, "require": { "php": "^7.4 || ^8.0", - "cebe/php-openapi": "^1.5.0", "yiisoft/yii2": "~2.0.48", "yiisoft/yii2-gii": "~2.0.0 | ~2.1.0 | ~2.2.0| ~2.3.0", "laminas/laminas-code": ">=3.4 <=4.13", "php-openapi/yii2-fractal": "^1.0.0", "fakerphp/faker": "^1.9", "sam-it/yii2-mariadb": "^2.0", - "symfony/var-exporter": "^5.4" + "symfony/var-exporter": "^5.4", + "cebe/php-openapi": "^1.7" }, "require-dev": { "cebe/indent": "*", diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index b280927c..9be115af 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -12,6 +12,7 @@ use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\exceptions\UnresolvableReferenceException; +use cebe\openapi\ReferenceContext; use cebe\openapi\spec\Reference; use cebe\openapi\spec\Schema; use cebe\openapi\SpecObjectInterface; @@ -22,6 +23,7 @@ use cebe\yii2openapi\lib\openapi\PropertySchema; use Symfony\Component\VarExporter\Exception\ExceptionInterface; use Symfony\Component\VarExporter\VarExporter; +use Yii; use yii\base\InvalidConfigException; use yii\helpers\Json; use yii\helpers\VarDumper; @@ -100,6 +102,8 @@ public function resolve(): ?string } elseif ($this->attribute->phpType === 'array' || substr($this->attribute->phpType, -2) === '[]') { $result = $this->fakeForArray($this->property->getProperty()); + } elseif ($this->attribute->phpType === 'object') { + $result = $this->fakeForObject($this->property->getProperty()); } else { return null; } @@ -265,7 +269,7 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st $count = $maxItems; } } - if (isset($property->uniqueItems)) { + if (!empty($property->uniqueItems)) { $uniqueItems = $property->uniqueItems; } @@ -281,7 +285,7 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st if ($items instanceof Reference) { $class = str_replace('#/components/schemas/', '', $items->getReference()); $class .= 'Faker'; - return $this->wrapAsArray('(new ' . $class . ')->generateModel()->attributes', false, $count); + return $this->wrapInArray('(new ' . $class . ')->generateModel()->attributes', false, $count); } elseif (!empty($items->oneOf)) { return $this->handleOneOf($items, $count); } @@ -293,7 +297,7 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st $aElementFaker = $this->aElementFaker($this->property->getProperty()->getSerializableData()); if (in_array($type, ['string', 'number', 'integer', 'boolean'])) { - return $this->wrapAsArray($aElementFaker, $uniqueItems, $count); + return $this->wrapInArray($aElementFaker, $uniqueItems, $count); } if ($type === 'array') { # array or nested arrays @@ -301,7 +305,8 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st } if ($type === 'object') { - return $this->handleObject($items, $count); + $result = $this->fakeForObject($items, $count); + return $this->wrapInArray($result, $uniqueItems, $count); } @@ -316,18 +321,9 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st } /** - * @param $items Schema|Reference|null - * @param $count int - * @param bool $nested - * @return string - * @throws ExceptionInterface - * @throws InvalidConfigException - * @throws InvalidDefinitionException - * @throws TypeErrorException - * @throws UnresolvableReferenceException * @internal */ - public function handleObject(Schema $items, int $count, bool $nested = false): string + public function fakeForObject(SpecObjectInterface $items): string { $props = '[' . PHP_EOL; $cs = new ComponentSchema($items, 'unnamed'); @@ -337,35 +333,24 @@ public function handleObject(Schema $items, int $count, bool $nested = false): s /** @var SpecObjectInterface $prop */ if ($prop->properties) { // object - $result = $this->{__FUNCTION__}($prop, $count, true); + $result = $this->{__FUNCTION__}($prop); } else { $ps = new PropertySchema($prop, $name, $cs); $attr = $dbModels->attributes[$name]; - $result = (string)((new static($attr, $ps))->resolve()); + $result = (string)((new static($attr, $ps, $this->config))->resolve()); } $props .= '\'' . $name . '\' => ' . $result . ',' . PHP_EOL; } $props .= ']'; - if ($nested) { - return $props; - } - - return 'array_map(function () use ($faker, $uniqueFaker) { - return ' . $props . '; - }, range(1, ' . $count . '))'; + return $props; } /** * @param $items * @param $count * @return string - * @throws ExceptionInterface - * @throws InvalidConfigException - * @throws InvalidDefinitionException - * @throws TypeErrorException - * @throws UnresolvableReferenceException * @internal */ public function handleOneOf($items, $count): string @@ -374,8 +359,7 @@ public function handleOneOf($items, $count): string foreach ($items->oneOf as $key => $aDataType) { /** @var Schema|Reference $aDataType */ -// $a1 = $this->fakeForArray($aDataType, 1); - $a1 = $this->aElementFaker($aDataType->getSerializableData()); + $a1 = $this->aElementFaker(['items' => $aDataType->getSerializableData()]); $result .= '$dataType' . $key . ' = ' . $a1 . ';'; } $ct = count($items->oneOf) - 1; @@ -384,7 +368,7 @@ public function handleOneOf($items, $count): string return $result; } - public function wrapAsArray($aElementFaker, $uniqueItems, $count): string + public function wrapInArray($aElementFaker, $uniqueItems, $count): string { return 'array_map(function () use ($faker, $uniqueFaker) { return ' . ($uniqueItems ? str_replace('$faker->', '$uniqueFaker->', $aElementFaker) : $aElementFaker) . '; @@ -401,11 +385,19 @@ public function aElementFaker($data): ?string $aElementData = Json::decode(Json::encode($data)); $compoSchemaData = [ 'properties' => [ - 'unnamedProp' => $aElementData['items'] ?? $aElementData # later is used only in `oneOf` + 'unnamedProp' => $aElementData['items'] ] ]; - $cs = new ComponentSchema(new Schema($compoSchemaData), 'UnnamedCompo'); - $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([])))->resolve(); - return (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp')))->resolve(); + + $schema = new Schema($compoSchemaData); + + if ($this->config) { + $rc = new ReferenceContext($this->config->getOpenApi(), Yii::getAlias($this->config->openApiPath)); + $schema->setReferenceContext($rc); + } + + $cs = new ComponentSchema($schema, 'UnnamedCompo'); + $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([]), $this->config))->resolve(); + return (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp'), $this->config))->resolve(); } } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 9369ecbb..08a93030 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -153,6 +153,15 @@ components: one_of_arr: type: array # ["foo", 5, -2, "bar"] maxItems: 8 + items: + oneOf: + - type: integer + - type: string + - type: boolean + + one_of_arr_complex: + type: array + maxItems: 8 items: oneOf: - type: integer @@ -166,6 +175,10 @@ components: properties: id: type: integer + - type: array + items: + $ref: '#/components/schemas/User' # oneOf +# TODO count is not working in some cases \ No newline at end of file From 4e6bc039be3d1b7772393b147b40b6518d7a2760 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Wed, 14 Aug 2024 20:35:35 +0530 Subject: [PATCH 13/28] Fix failing test for x_db_type --- .../app/models/mariafaker/AlldbdatatypeFaker.php | 8 ++++---- .../app/models/mariafaker/EditcolumnFaker.php | 6 +++--- .../maria/app/models/mariafaker/NewcolumnFaker.php | 4 ++-- .../maria/app/models/mariafaker/PristineFaker.php | 2 +- .../mysql/app/models/AlldbdatatypeFaker.php | 8 ++++---- .../mysql/app/models/EditcolumnFaker.php | 6 +++--- .../mysql/app/models/NewcolumnFaker.php | 4 ++-- .../mysql/app/models/PristineFaker.php | 2 +- .../app/models/pgsqlfaker/AlldbdatatypeFaker.php | 14 +++++++------- .../app/models/pgsqlfaker/EditcolumnFaker.php | 8 ++++---- .../pgsql/app/models/pgsqlfaker/NewcolumnFaker.php | 8 ++++---- .../pgsql/app/models/pgsqlfaker/PristineFaker.php | 2 +- 12 files changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/AlldbdatatypeFaker.php b/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/AlldbdatatypeFaker.php index 32934ebe..8a4a3594 100644 --- a/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/AlldbdatatypeFaker.php +++ b/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/AlldbdatatypeFaker.php @@ -66,11 +66,11 @@ public function generateModel($attributes = []) $model->datetime_col = $faker->dateTimeThisYear('now', 'UTC')->format('Y-m-d H:i:s'); $model->timestamp_col = $faker->dateTimeThisYear('now', 'UTC')->format('Y-m-d H:i:s'); $model->year_col = $faker->year; - $model->json_col = []; - $model->json_col_def = []; - $model->json_col_def_2 = []; + $model->json_col = $faker->words(); + $model->json_col_def = $faker->words(); + $model->json_col_def_2 = $faker->words(); $model->text_def = $faker->sentence; - $model->json_def = []; + $model->json_def = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/EditcolumnFaker.php b/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/EditcolumnFaker.php index efe765e5..1f183cd5 100644 --- a/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/EditcolumnFaker.php +++ b/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/EditcolumnFaker.php @@ -38,10 +38,10 @@ public function generateModel($attributes = []) $model->dec_col = $faker->randomFloat(); $model->str_col_def = substr($faker->word(3), 0, 3); $model->json_col = $faker->sentence; - $model->json_col_2 = ["a" => "b"]; + $model->json_col_2 = $faker->words(); $model->numeric_col = $faker->randomFloat(); - $model->json_col_def_n = []; - $model->json_col_def_n_2 = []; + $model->json_col_def_n = $faker->words(); + $model->json_col_def_n_2 = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/NewcolumnFaker.php b/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/NewcolumnFaker.php index a60800cd..0532b9c1 100644 --- a/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/NewcolumnFaker.php +++ b/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/NewcolumnFaker.php @@ -34,10 +34,10 @@ public function generateModel($attributes = []) $model->name = substr($faker->text(255), 0, 255); $model->last_name = $faker->sentence; $model->dec_col = $faker->randomFloat(); - $model->json_col = []; + $model->json_col = $faker->words(); $model->varchar_col = substr($faker->text(5), 0, 5); $model->numeric_col = $faker->randomFloat(); - $model->json_col_def_n = []; + $model->json_col_def_n = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/PristineFaker.php b/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/PristineFaker.php index 2fdf01e5..293dc2df 100644 --- a/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/PristineFaker.php +++ b/tests/specs/x_db_type/rules_and_more/maria/app/models/mariafaker/PristineFaker.php @@ -37,7 +37,7 @@ public function generateModel($attributes = []) $model->col_5 = $faker->randomFloat(); $model->col_6 = $faker->randomFloat(); $model->col_7 = $faker->randomFloat(); - $model->col_8 = []; + $model->col_8 = $faker->words(); $model->col_9 = substr($faker->text(9), 0, 9); $model->col_10 = substr($faker->text(10), 0, 10); $model->col_11 = $faker->sentence; diff --git a/tests/specs/x_db_type/rules_and_more/mysql/app/models/AlldbdatatypeFaker.php b/tests/specs/x_db_type/rules_and_more/mysql/app/models/AlldbdatatypeFaker.php index 9c3e308b..795f4344 100644 --- a/tests/specs/x_db_type/rules_and_more/mysql/app/models/AlldbdatatypeFaker.php +++ b/tests/specs/x_db_type/rules_and_more/mysql/app/models/AlldbdatatypeFaker.php @@ -65,11 +65,11 @@ public function generateModel($attributes = []) $model->datetime_col = $faker->dateTimeThisYear('now', 'UTC')->format('Y-m-d H:i:s'); $model->timestamp_col = $faker->dateTimeThisYear('now', 'UTC')->format('Y-m-d H:i:s'); $model->year_col = $faker->year; - $model->json_col = []; - $model->json_col_def = []; - $model->json_col_def_2 = []; + $model->json_col = $faker->words(); + $model->json_col_def = $faker->words(); + $model->json_col_def_2 = $faker->words(); $model->text_def = $faker->sentence; - $model->json_def = []; + $model->json_def = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/x_db_type/rules_and_more/mysql/app/models/EditcolumnFaker.php b/tests/specs/x_db_type/rules_and_more/mysql/app/models/EditcolumnFaker.php index 2f60a4e7..6a14b7de 100644 --- a/tests/specs/x_db_type/rules_and_more/mysql/app/models/EditcolumnFaker.php +++ b/tests/specs/x_db_type/rules_and_more/mysql/app/models/EditcolumnFaker.php @@ -37,10 +37,10 @@ public function generateModel($attributes = []) $model->dec_col = $faker->randomFloat(); $model->str_col_def = substr($faker->word(3), 0, 3); $model->json_col = $faker->sentence; - $model->json_col_2 = ["a" => "b"]; + $model->json_col_2 = $faker->words(); $model->numeric_col = $faker->randomFloat(); - $model->json_col_def_n = []; - $model->json_col_def_n_2 = []; + $model->json_col_def_n = $faker->words(); + $model->json_col_def_n_2 = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/x_db_type/rules_and_more/mysql/app/models/NewcolumnFaker.php b/tests/specs/x_db_type/rules_and_more/mysql/app/models/NewcolumnFaker.php index 87763eb3..fe3319dd 100644 --- a/tests/specs/x_db_type/rules_and_more/mysql/app/models/NewcolumnFaker.php +++ b/tests/specs/x_db_type/rules_and_more/mysql/app/models/NewcolumnFaker.php @@ -33,10 +33,10 @@ public function generateModel($attributes = []) $model->name = substr($faker->text(255), 0, 255); $model->last_name = $faker->sentence; $model->dec_col = $faker->randomFloat(); - $model->json_col = []; + $model->json_col = $faker->words(); $model->varchar_col = substr($faker->text(5), 0, 5); $model->numeric_col = $faker->randomFloat(); - $model->json_col_def_n = []; + $model->json_col_def_n = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/x_db_type/rules_and_more/mysql/app/models/PristineFaker.php b/tests/specs/x_db_type/rules_and_more/mysql/app/models/PristineFaker.php index 41b186d9..ecd7b315 100644 --- a/tests/specs/x_db_type/rules_and_more/mysql/app/models/PristineFaker.php +++ b/tests/specs/x_db_type/rules_and_more/mysql/app/models/PristineFaker.php @@ -36,7 +36,7 @@ public function generateModel($attributes = []) $model->col_5 = $faker->randomFloat(); $model->col_6 = $faker->randomFloat(); $model->col_7 = $faker->randomFloat(); - $model->col_8 = []; + $model->col_8 = $faker->words(); $model->col_9 = substr($faker->text(9), 0, 9); $model->col_10 = substr($faker->text(10), 0, 10); $model->col_11 = $faker->sentence; diff --git a/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/AlldbdatatypeFaker.php b/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/AlldbdatatypeFaker.php index e2af0af0..70c2016c 100644 --- a/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/AlldbdatatypeFaker.php +++ b/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/AlldbdatatypeFaker.php @@ -34,7 +34,7 @@ public function generateModel($attributes = []) $model->string_col = $faker->sentence; $model->varchar_col = $faker->sentence; $model->text_col = $faker->sentence; - $model->text_col_array = []; + $model->text_col_array = $faker->words(); $model->varchar_4_col = substr($faker->word(4), 0, 4); $model->varchar_5_col = substr($faker->text(5), 0, 5); $model->char_4_col = substr($faker->word(4), 0, 4); @@ -90,13 +90,13 @@ public function generateModel($attributes = []) $model->character_n = substr($faker->text(12), 0, 12); $model->character_varying = $faker->sentence; $model->character_varying_n = substr($faker->text(12), 0, 12); - $model->json_col = []; - $model->jsonb_col = []; - $model->json_col_def = []; - $model->json_col_def_2 = []; + $model->json_col = $faker->words(); + $model->jsonb_col = $faker->words(); + $model->json_col_def = $faker->words(); + $model->json_col_def_2 = $faker->words(); $model->text_def = $faker->sentence; - $model->json_def = []; - $model->jsonb_def = []; + $model->json_def = $faker->words(); + $model->jsonb_def = $faker->words(); $model->cidr_col = $faker->sentence; $model->circle_col = $faker->sentence; $model->date_col_z = $faker->dateTimeThisCentury->format('Y-m-d'); diff --git a/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/EditcolumnFaker.php b/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/EditcolumnFaker.php index af6d1d77..e32c6d82 100644 --- a/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/EditcolumnFaker.php +++ b/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/EditcolumnFaker.php @@ -38,11 +38,11 @@ public function generateModel($attributes = []) $model->dec_col = $faker->randomFloat(); $model->str_col_def = $faker->sentence; $model->json_col = $faker->sentence; - $model->json_col_2 = ["a" => "b"]; + $model->json_col_2 = $faker->words(); $model->numeric_col = $faker->randomFloat(); - $model->json_col_def_n = []; - $model->json_col_def_n_2 = []; - $model->text_col_array = []; + $model->json_col_def_n = $faker->words(); + $model->json_col_def_n_2 = $faker->words(); + $model->text_col_array = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/NewcolumnFaker.php b/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/NewcolumnFaker.php index 1b41dae7..64c32ef1 100644 --- a/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/NewcolumnFaker.php +++ b/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/NewcolumnFaker.php @@ -35,12 +35,12 @@ public function generateModel($attributes = []) $model->first_name = $faker->sentence; $model->last_name = $faker->sentence; $model->dec_col = $faker->randomFloat(); - $model->json_col = []; + $model->json_col = $faker->words(); $model->varchar_col = $faker->sentence; $model->numeric_col = $faker->randomFloat(); - $model->json_col_def_n = []; - $model->json_col_def_n_2 = []; - $model->text_col_array = []; + $model->json_col_def_n = $faker->words(); + $model->json_col_def_n_2 = $faker->words(); + $model->text_col_array = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/PristineFaker.php b/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/PristineFaker.php index aac33ab7..ab484826 100644 --- a/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/PristineFaker.php +++ b/tests/specs/x_db_type/rules_and_more/pgsql/app/models/pgsqlfaker/PristineFaker.php @@ -37,7 +37,7 @@ public function generateModel($attributes = []) $model->col_5 = $faker->randomFloat(); $model->col_6 = $faker->randomFloat(); $model->col_7 = $faker->randomFloat(); - $model->col_8 = []; + $model->col_8 = $faker->words(); $model->col_9 = $faker->sentence; $model->col_10 = $faker->sentence; $model->col_11 = $faker->sentence; From b2c21d1818fee3e9c4e9b9f966474c6d9c52eb92 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Wed, 14 Aug 2024 20:39:54 +0530 Subject: [PATCH 14/28] Fix bug --- src/lib/AttributeResolver.php | 6 ++---- src/lib/FakerStubResolver.php | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index b6725f4e..b069f419 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -259,7 +259,8 @@ protected function resolveProperty( ->setSize($fkProperty->getMaxLength()) ->setDescription($property->getRefSchema()->getDescription()) ->setDefault($fkProperty->guessDefault()) - ->setLimits($min, $max, $fkProperty->getMinLength()); + ->setLimits($min, $max, $fkProperty->getMinLength()) + ->setFakerStub($this->guessFakerStub($attribute, $property)); $relation = Yii::createObject( AttributeRelation::class, @@ -340,9 +341,6 @@ protected function resolveProperty( } $attribute->setPhpType($relatedClassName . '[]'); - $this->attributes[$property->getName()] = - $attribute->setFakerStub($this->guessFakerStub($attribute, $property)); // TODO - $this->relations[$property->getName()] = Yii::createObject( AttributeRelation::class, diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 9be115af..6642efcc 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -390,7 +390,6 @@ public function aElementFaker($data): ?string ]; $schema = new Schema($compoSchemaData); - if ($this->config) { $rc = new ReferenceContext($this->config->getOpenApi(), Yii::getAlias($this->config->openApiPath)); $schema->setReferenceContext($rc); From 52f2320b329db8f5d2254739457c6e11ef51ec68 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Wed, 14 Aug 2024 21:05:47 +0530 Subject: [PATCH 15/28] Fix failing test --- src/lib/FakerStubResolver.php | 7 ++++++- tests/fixtures/blog.php | 6 ++++-- tests/specs/blog/models/CommentFaker.php | 6 ++++-- tests/specs/menu/models/MenuFaker.php | 4 ++-- tests/specs/postgres_custom/models/CustomFaker.php | 8 ++++---- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 6642efcc..2b831453 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -325,11 +325,15 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st */ public function fakeForObject(SpecObjectInterface $items): string { + if (!$items->properties) { + return $this->arbitraryArray(); + } + $props = '[' . PHP_EOL; $cs = new ComponentSchema($items, 'unnamed'); $dbModels = (new AttributeResolver('unnamed', $cs, new JunctionSchemas([])))->resolve(); - foreach ($items->properties ?? [] as $name => $prop) { + foreach ($items->properties as $name => $prop) { /** @var SpecObjectInterface $prop */ if ($prop->properties) { // object @@ -342,6 +346,7 @@ public function fakeForObject(SpecObjectInterface $items): string $props .= '\'' . $name . '\' => ' . $result . ',' . PHP_EOL; } + $props .= ']'; return $props; diff --git a/tests/fixtures/blog.php b/tests/fixtures/blog.php index 74b04000..3c24df46 100644 --- a/tests/fixtures/blog.php +++ b/tests/fixtures/blog.php @@ -117,9 +117,11 @@ ->setDescription('The User') ->setFakerStub('$faker->randomElement(\app\models\User::find()->select("id")->column())'), 'message' => (new Attribute('message', ['phpType' => 'array', 'dbType' => 'json', 'xDbType' => 'json'])) - ->setRequired()->setDefault([])->setFakerStub('["a" => "b"]'), + ->setRequired()->setDefault([])->setFakerStub('$faker->words()'), 'meta_data' => (new Attribute('meta_data', ['phpType' => 'array', 'dbType' => 'json', 'xDbType' => 'json'])) - ->setDefault([])->setFakerStub('[]'), + ->setDefault([])->setFakerStub('array_map(function () use ($faker, $uniqueFaker) { + return $faker->words(); + }, range(1, 4))'), 'created_at' => (new Attribute('created_at',['phpType' => 'int', 'dbType' => 'integer'])) ->setRequired()->setFakerStub('$faker->unixTime'), ], diff --git a/tests/specs/blog/models/CommentFaker.php b/tests/specs/blog/models/CommentFaker.php index 368d541c..723562d8 100644 --- a/tests/specs/blog/models/CommentFaker.php +++ b/tests/specs/blog/models/CommentFaker.php @@ -32,8 +32,10 @@ public function generateModel($attributes = []) //$model->id = $uniqueFaker->numberBetween(0, 1000000); $model->post_id = $faker->randomElement(\app\models\Post::find()->select("id")->column()); $model->author_id = $faker->randomElement(\app\models\User::find()->select("id")->column()); - $model->message = ["a" => "b"]; - $model->meta_data = []; + $model->message = $faker->words(); + $model->meta_data = array_map(function () use ($faker, $uniqueFaker) { + return $faker->words(); + }, range(1, 4)); $model->created_at = $faker->unixTime; if (!is_callable($attributes)) { $model->setAttributes($attributes, false); diff --git a/tests/specs/menu/models/MenuFaker.php b/tests/specs/menu/models/MenuFaker.php index 64e82b3b..32b0f388 100644 --- a/tests/specs/menu/models/MenuFaker.php +++ b/tests/specs/menu/models/MenuFaker.php @@ -32,8 +32,8 @@ public function generateModel($attributes = []) //$model->id = $uniqueFaker->numberBetween(0, 1000000); $model->name = substr($faker->text(100), 0, 100); $model->parent_id = $faker->randomElement(\app\models\Menu::find()->select("id")->column()); - $model->args = []; - $model->kwargs = []; + $model->args = $faker->words(); + $model->kwargs = $faker->words(); if (!is_callable($attributes)) { $model->setAttributes($attributes, false); } else { diff --git a/tests/specs/postgres_custom/models/CustomFaker.php b/tests/specs/postgres_custom/models/CustomFaker.php index 3732716d..cc1b2cd0 100644 --- a/tests/specs/postgres_custom/models/CustomFaker.php +++ b/tests/specs/postgres_custom/models/CustomFaker.php @@ -31,10 +31,10 @@ public function generateModel($attributes = []) $model = new Custom(); //$model->id = $uniqueFaker->numberBetween(0, 1000000); $model->num = $faker->numberBetween(0, 1000000); - $model->json1 = []; - $model->json2 = []; - $model->json3 = []; - $model->json4 = []; + $model->json1 = $faker->words(); + $model->json2 = $faker->words(); + $model->json3 = $faker->words(); + $model->json4 = $faker->words(); $model->status = $faker->randomElement(['active','draft']); $model->status_x = $faker->randomElement(['active','draft']); if (!is_callable($attributes)) { From b03726521bcced70d09d42632a0a5ed626773e4d Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Thu, 15 Aug 2024 16:24:14 +0530 Subject: [PATCH 16/28] Fix failing test --- tests/fixtures/non-db.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/non-db.php b/tests/fixtures/non-db.php index 220a486d..04714844 100644 --- a/tests/fixtures/non-db.php +++ b/tests/fixtures/non-db.php @@ -21,7 +21,7 @@ 'catsCount' => (new Attribute('catsCount', ['phpType' => 'int', 'dbType' => 'integer'])), 'summary' => (new Attribute('summary', ['phpType' => 'string', 'dbType' => 'text'])), 'parentPet' => (new Attribute('parentPet', ['phpType' => 'int', 'dbType' => 'bigint'])) - ->asReference('Pet')->setDescription('A Pet'), + ->asReference('Pet')->setDescription('A Pet')->setFakerStub('$faker->randomElement(\app\models\Pet::find()->select("id")->column())'), ], 'relations' => [ 'parentPet' => new AttributeRelation('parentPet', 'pets', 'Pet', 'hasOne', ['id' => 'parentPet_id']), From 091007395cd5dffce14971a4e7ae901b27c32efb Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Thu, 15 Aug 2024 17:36:59 +0530 Subject: [PATCH 17/28] Fix errors --- src/lib/AttributeResolver.php | 3 ++- src/lib/FakerStubResolver.php | 18 ++++++++++++++---- .../index.yaml | 4 +--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index b069f419..1d7f54a7 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -225,7 +225,8 @@ protected function resolveProperty( ->setXDbDefaultExpression($property->getAttr(CustomSpecAttr::DB_DEFAULT_EXPRESSION)) ->setNullable($nullableValue) ->setIsPrimary($property->isPrimaryKey()) - ->setForeignKeyColumnName($property->fkColName); + ->setForeignKeyColumnName($property->fkColName) + ->setFakerStub($this->guessFakerStub($attribute, $property)); if ($property->isReference()) { if ($property->isVirtual()) { throw new InvalidDefinitionException('References not supported for virtual attributes'); diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 2b831453..9cf1b6b4 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -336,7 +336,7 @@ public function fakeForObject(SpecObjectInterface $items): string foreach ($items->properties as $name => $prop) { /** @var SpecObjectInterface $prop */ - if ($prop->properties) { // object + if ($prop->properties) { // nested object $result = $this->{__FUNCTION__}($prop); } else { $ps = new PropertySchema($prop, $name, $cs); @@ -387,7 +387,7 @@ public function arbitraryArray(): string public function aElementFaker($data): ?string { - $aElementData = Json::decode(Json::encode($data)); + $aElementData = Json::decode(Json::encode($data)); // object of stdClass -> array $compoSchemaData = [ 'properties' => [ 'unnamedProp' => $aElementData['items'] @@ -395,13 +395,23 @@ public function aElementFaker($data): ?string ]; $schema = new Schema($compoSchemaData); + $cs = new ComponentSchema($schema, 'UnnamedCompo'); if ($this->config) { $rc = new ReferenceContext($this->config->getOpenApi(), Yii::getAlias($this->config->openApiPath)); $schema->setReferenceContext($rc); } - - $cs = new ComponentSchema($schema, 'UnnamedCompo'); $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([]), $this->config))->resolve(); + + foreach ($schema->properties as $name => $prop) { + if($prop->items instanceof Reference) { + $dbModels->attributes[$name] = new Attribute($name, [ + 'phpType' => 'array', + 'dbType' => 'array', + 'reference' => $prop->items->getReference(), + ]); + } + } + return (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp'), $this->config))->resolve(); } } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 08a93030..85794ee0 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -34,7 +34,6 @@ components: name: type: string example: cat - # example: ['long-tail', 'short-tail', 'black', 'white'] age: type: integer example: 2 @@ -48,8 +47,7 @@ components: minItems: 6 maxItems: 10 uniqueItems: true - # type: string - # example: ['long-tail', 'short-tail', 'black', 'white'] + # example: ['long-tail', 'short-tail', 'black', 'white'] number_arr: type: array items: From f0c5cd3808da8b6aafbe3637f3153464df3163e1 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Thu, 15 Aug 2024 21:09:09 +0530 Subject: [PATCH 18/28] Fix errors 2 + implement custom attribute `x-no-relation` --- src/lib/AttributeResolver.php | 6 ++++++ src/lib/FakerStubResolver.php | 21 +++++++++++-------- tests/specs/blog_v2.yaml | 1 + .../index.php | 2 +- .../index.yaml | 11 +++++++++- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index 1d7f54a7..2a620d51 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -217,7 +217,13 @@ protected function resolveProperty( $nullableValue = $property->getProperty()->getSerializableData()->nullable ?? null; } $attribute = Yii::createObject(Attribute::class, [$property->getName()]); + + if (!empty($property->getAttr('x-no-relation'))) { // TODO custom attr + $this->attributes[$property->getName()] = $attribute->setFakerStub($this->guessFakerStub($attribute, $property)); + } + $attribute->setRequired($isRequired) + ->setPhpType($property->guessPhpType()) ->setDescription($property->getAttr('description', '')) ->setReadOnly($property->isReadonly()) ->setDefault($property->guessDefault()) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 9cf1b6b4..c069042e 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -393,6 +393,9 @@ public function aElementFaker($data): ?string 'unnamedProp' => $aElementData['items'] ] ]; + if (!empty($compoSchemaData['properties']['unnamedProp']['items']['$ref'])) { // TODO + $compoSchemaData['properties']['unnamedProp']['x-no-relation'] = true; + } $schema = new Schema($compoSchemaData); $cs = new ComponentSchema($schema, 'UnnamedCompo'); @@ -402,15 +405,15 @@ public function aElementFaker($data): ?string } $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([]), $this->config))->resolve(); - foreach ($schema->properties as $name => $prop) { - if($prop->items instanceof Reference) { - $dbModels->attributes[$name] = new Attribute($name, [ - 'phpType' => 'array', - 'dbType' => 'array', - 'reference' => $prop->items->getReference(), - ]); - } - } +// foreach ($schema->properties as $name => $prop) { +// if($prop->items instanceof Reference) { +// $dbModels->attributes[$name] = new Attribute($name, [ +// 'phpType' => 'array', +// 'dbType' => 'array', +// 'reference' => $prop->items->getReference(), +// ]); +// } +// } return (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp'), $this->config))->resolve(); } diff --git a/tests/specs/blog_v2.yaml b/tests/specs/blog_v2.yaml index 68d09367..e4ab5a48 100644 --- a/tests/specs/blog_v2.yaml +++ b/tests/specs/blog_v2.yaml @@ -349,6 +349,7 @@ components: $ref: "#/components/schemas/User" comments: type: array + # x-no-relation: true items: $ref: "#/components/schemas/Comment" tags: diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php index 6afa241e..14397d3e 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php @@ -8,6 +8,6 @@ // 'Error', // ], 'generateControllers' => false, - 'generateMigrations' => false, + 'generateMigrations' => true, 'generateModelFaker' => true, // `generateModels` must be `true` in order to use `generateModelFaker` as `true` ]; diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 85794ee0..1879c3dd 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -143,8 +143,17 @@ components: id: type: integer - user_ref_obj_arr: + user_ref_obj_arr_normal: # faker for this won't be generated type: array + maxItems: 3 + # x-no-relation: true + items: + $ref: '#/components/schemas/User' + + user_ref_obj_arr: # special + type: array + maxItems: 3 + x-no-relation: true items: $ref: '#/components/schemas/User' From de75ca7007c546abe865244c5a85abaf5511a527 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Fri, 16 Aug 2024 10:12:38 +0530 Subject: [PATCH 19/28] Fix failing test in PHP >= 8.1 --- src/lib/FakerStubResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index c069042e..394673a2 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -86,7 +86,7 @@ public function resolve(): ?string $mn = $config->modelNamespace; return '$faker->randomElement(\\' . $mn . ($mn ? '\\' : '') - . ucfirst($this->attribute->reference) . '::find()->select("id")->column())'; + . ucfirst((string) $this->attribute->reference) . '::find()->select("id")->column())'; } $limits = $this->attribute->limits; From 6ab30cc264e8fe0c1240ed3587f171a399f5bc42 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Fri, 16 Aug 2024 11:18:59 +0530 Subject: [PATCH 20/28] Fix count issue for nested array --- src/lib/AttributeResolver.php | 1 - src/lib/FakerStubResolver.php | 48 ++++++++++--------- .../index.yaml | 3 ++ 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index 2a620d51..d1fe4678 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -347,7 +347,6 @@ protected function resolveProperty( return; } $attribute->setPhpType($relatedClassName . '[]'); - $this->relations[$property->getName()] = Yii::createObject( AttributeRelation::class, diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 394673a2..08298e52 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -10,6 +10,7 @@ namespace cebe\yii2openapi\lib; +use cebe\openapi\exceptions\IOException; use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\exceptions\UnresolvableReferenceException; use cebe\openapi\ReferenceContext; @@ -256,6 +257,7 @@ private function fakeForFloat(?int $min, ?int $max): ?string * @throws TypeErrorException * @throws UnresolvableReferenceException * @throws InvalidDefinitionException|ExceptionInterface + * @throws IOException */ private function fakeForArray(SpecObjectInterface $property, int $count = 4): string { @@ -294,22 +296,17 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st if ($type === null) { return $this->arbitraryArray(); } - $aElementFaker = $this->aElementFaker($this->property->getProperty()->getSerializableData()); + $aFaker = $this->aElementFaker($this->property->getProperty()->getSerializableData()); - if (in_array($type, ['string', 'number', 'integer', 'boolean'])) { - return $this->wrapInArray($aElementFaker, $uniqueItems, $count); - } - - if ($type === 'array') { # array or nested arrays - return $this->{__FUNCTION__}($items); + if (in_array($type, ['string', 'number', 'integer', 'boolean', 'array'])) { + return $this->wrapInArray($aFaker, $uniqueItems, $count); } if ($type === 'object') { - $result = $this->fakeForObject($items, $count); + $result = $this->fakeForObject($items); return $this->wrapInArray($result, $uniqueItems, $count); } - // TODO more complex type array/object; also consider $ref; may be recursively; may use `oneOf` // return '$faker->words()'; // TODO implement faker for array; also consider min, max, unique @@ -373,10 +370,10 @@ public function handleOneOf($items, $count): string return $result; } - public function wrapInArray($aElementFaker, $uniqueItems, $count): string + public function wrapInArray($aFaker, $uniqueItems, $count): string { return 'array_map(function () use ($faker, $uniqueFaker) { - return ' . ($uniqueItems ? str_replace('$faker->', '$uniqueFaker->', $aElementFaker) : $aElementFaker) . '; + return ' . ($uniqueItems ? str_replace('$faker->', '$uniqueFaker->', $aFaker) : $aFaker) . '; }, range(1, ' . $count . '))'; } @@ -385,6 +382,19 @@ public function arbitraryArray(): string return '$faker->words()'; } + /** + * This method is only for `fakeForArray()` or methods only used inside `fakeForArray()`. If needed to use outside `fakeForArray()` context then some changes might be required. + * Also see OpenAPI extension `x-no-relation` in README.md + * @param $data + * @return string|null + * @throws ExceptionInterface + * @throws InvalidConfigException + * @throws InvalidDefinitionException + * @throws TypeErrorException + * @throws UnresolvableReferenceException + * @throws IOException + * @internal + */ public function aElementFaker($data): ?string { $aElementData = Json::decode(Json::encode($data)); // object of stdClass -> array @@ -393,7 +403,11 @@ public function aElementFaker($data): ?string 'unnamedProp' => $aElementData['items'] ] ]; - if (!empty($compoSchemaData['properties']['unnamedProp']['items']['$ref'])) { // TODO + + // This condition is only for properties with type = array + // If you intend to use this method from out of `fakeForArray()` context then below condition should be changed depending on your use case + // Also see OpenAPI extension `x-no-relation` in README.md + if (!empty($compoSchemaData['properties']['unnamedProp']['items']['$ref'])) { $compoSchemaData['properties']['unnamedProp']['x-no-relation'] = true; } @@ -405,16 +419,6 @@ public function aElementFaker($data): ?string } $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([]), $this->config))->resolve(); -// foreach ($schema->properties as $name => $prop) { -// if($prop->items instanceof Reference) { -// $dbModels->attributes[$name] = new Attribute($name, [ -// 'phpType' => 'array', -// 'dbType' => 'array', -// 'reference' => $prop->items->getReference(), -// ]); -// } -// } - return (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp'), $this->config))->resolve(); } } diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 1879c3dd..6fb95e77 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -93,10 +93,13 @@ components: arr_arr_arr_str: type: array + minItems: 3 items: type: array + minItems: 4 items: type: array + minItems: 5 items: type: string From 960cc43095d69c4960bf05a7fcd35940d3a54357 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Fri, 16 Aug 2024 16:35:04 +0530 Subject: [PATCH 21/28] Add typehint to fn args --- src/lib/CustomSpecAttr.php | 5 +++++ src/lib/FakerStubResolver.php | 8 ++++---- .../index.yaml | 4 ++-- tests/unit/IssueFixTest.php | 6 ++++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/lib/CustomSpecAttr.php b/src/lib/CustomSpecAttr.php index 7e02a85d..ba37d272 100644 --- a/src/lib/CustomSpecAttr.php +++ b/src/lib/CustomSpecAttr.php @@ -40,4 +40,9 @@ class CustomSpecAttr * Foreign key column name. See README for usage docs */ public const FK_COLUMN_NAME = 'x-fk-column-name'; + + /** + * Foreign key column name. See README for usage docs + */ +// public const FK_COLUMN_NAME = 'x-fk-column-name'; } diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 08298e52..3a769438 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -355,7 +355,7 @@ public function fakeForObject(SpecObjectInterface $items): string * @return string * @internal */ - public function handleOneOf($items, $count): string + public function handleOneOf(SpecObjectInterface $items, int $count): string { $result = 'array_map(function () use ($faker, $uniqueFaker) {'; foreach ($items->oneOf as $key => $aDataType) { @@ -370,7 +370,7 @@ public function handleOneOf($items, $count): string return $result; } - public function wrapInArray($aFaker, $uniqueItems, $count): string + public function wrapInArray(string $aFaker, bool $uniqueItems, int $count): string { return 'array_map(function () use ($faker, $uniqueFaker) { return ' . ($uniqueItems ? str_replace('$faker->', '$uniqueFaker->', $aFaker) : $aFaker) . '; @@ -385,7 +385,7 @@ public function arbitraryArray(): string /** * This method is only for `fakeForArray()` or methods only used inside `fakeForArray()`. If needed to use outside `fakeForArray()` context then some changes might be required. * Also see OpenAPI extension `x-no-relation` in README.md - * @param $data + * @param $data object|array * @return string|null * @throws ExceptionInterface * @throws InvalidConfigException @@ -397,7 +397,7 @@ public function arbitraryArray(): string */ public function aElementFaker($data): ?string { - $aElementData = Json::decode(Json::encode($data)); // object of stdClass -> array + $aElementData = Json::decode(Json::encode($data)); // element object of stdClass -> array $compoSchemaData = [ 'properties' => [ 'unnamedProp' => $aElementData['items'] diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 6fb95e77..e6d820de 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -126,9 +126,9 @@ components: type: array items: type: array + minItems: 11 items: type: integer - appearance: type: object properties: @@ -171,7 +171,7 @@ components: one_of_arr_complex: type: array - maxItems: 8 + minItems: 8 items: oneOf: - type: integer diff --git a/tests/unit/IssueFixTest.php b/tests/unit/IssueFixTest.php index 197a9eca..022ba78b 100644 --- a/tests/unit/IssueFixTest.php +++ b/tests/unit/IssueFixTest.php @@ -364,6 +364,12 @@ public function test158BugGiiapiGeneratedRulesEnumWithTrim() // https://github.com/php-openapi/yii2-openapi/issues/20 public function test20ConsiderOpenApiSpecExamplesInFakeCodeGeneration() { +// $faker = \Faker\Factory::create(); +// $uniqueFaker = $faker->unique(); +// +// var_dump(); +// return; + $testFile = Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php"); $this->runGenerator($testFile); // $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ From 4f6d72879b9563ea7ecd4fa925efa6c5c60f7a45 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Fri, 16 Aug 2024 20:31:51 +0530 Subject: [PATCH 22/28] Fix issues + add support for all refs only in oneOf --- README.md | 2 + src/lib/FakerStubResolver.php | 40 +++++++++++++------ .../index.yaml | 19 ++++++++- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 1cd2090b..fccf1037 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,8 @@ Provide custom database table column name in case of relationship column. This w - x-fk-column-name: redelivery_of # this will create `redelivery_of` column instead of `redelivery_of_id` ``` +### `x-no-relation` + ## Many-to-Many relation definition There are two ways for define many-to-many relations: diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 3a769438..9902cf2f 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -87,7 +87,7 @@ public function resolve(): ?string $mn = $config->modelNamespace; return '$faker->randomElement(\\' . $mn . ($mn ? '\\' : '') - . ucfirst((string) $this->attribute->reference) . '::find()->select("id")->column())'; + . ucfirst((string)$this->attribute->reference) . '::find()->select("id")->column())'; // TODO PK "id" can be also something else } $limits = $this->attribute->limits; @@ -285,10 +285,10 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st } if ($items instanceof Reference) { - $class = str_replace('#/components/schemas/', '', $items->getReference()); - $class .= 'Faker'; - return $this->wrapInArray('(new ' . $class . ')->generateModel()->attributes', false, $count); - } elseif (!empty($items->oneOf)) { + $aFakerForRef = $this->aElementFaker($items); + return $this->wrapInArray($aFakerForRef, $uniqueItems, $count); + } + if (!empty($items->oneOf)) { return $this->handleOneOf($items, $count); } @@ -296,8 +296,7 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st if ($type === null) { return $this->arbitraryArray(); } - $aFaker = $this->aElementFaker($this->property->getProperty()->getSerializableData()); - + $aFaker = $this->aElementFaker($this->property->getProperty()); if (in_array($type, ['string', 'number', 'integer', 'boolean', 'array'])) { return $this->wrapInArray($aFaker, $uniqueItems, $count); } @@ -350,9 +349,16 @@ public function fakeForObject(SpecObjectInterface $items): string } /** - * @param $items - * @param $count + * This method must be only used incase of array + * @param SpecObjectInterface $items + * @param int $count * @return string + * @throws ExceptionInterface + * @throws IOException + * @throws InvalidConfigException + * @throws InvalidDefinitionException + * @throws TypeErrorException + * @throws UnresolvableReferenceException * @internal */ public function handleOneOf(SpecObjectInterface $items, int $count): string @@ -361,8 +367,9 @@ public function handleOneOf(SpecObjectInterface $items, int $count): string foreach ($items->oneOf as $key => $aDataType) { /** @var Schema|Reference $aDataType */ - $a1 = $this->aElementFaker(['items' => $aDataType->getSerializableData()]); - $result .= '$dataType' . $key . ' = ' . $a1 . ';'; + $inp = $aDataType instanceof Reference ? $aDataType : ['items' => $aDataType->getSerializableData()]; + $aFaker = $this->aElementFaker($inp); + $result .= '$dataType' . $key . ' = ' . $aFaker . ';'; } $ct = count($items->oneOf) - 1; $result .= 'return ${"dataType".rand(0, ' . $ct . ')};'; @@ -385,7 +392,7 @@ public function arbitraryArray(): string /** * This method is only for `fakeForArray()` or methods only used inside `fakeForArray()`. If needed to use outside `fakeForArray()` context then some changes might be required. * Also see OpenAPI extension `x-no-relation` in README.md - * @param $data object|array + * @param $data array|\stdClass|SpecObjectInterface * @return string|null * @throws ExceptionInterface * @throws InvalidConfigException @@ -397,7 +404,14 @@ public function arbitraryArray(): string */ public function aElementFaker($data): ?string { - $aElementData = Json::decode(Json::encode($data)); // element object of stdClass -> array + if ($data instanceof Reference) { + $class = str_replace('#/components/schemas/', '', $data->getReference()); + $class .= 'Faker'; + return '(new ' . $class . ')->generateModel()->attributes'; + } + + $inp = $data instanceof SpecObjectInterface ? $data->getSerializableData() : $data; + $aElementData = Json::decode(Json::encode($inp)); $compoSchemaData = [ 'properties' => [ 'unnamedProp' => $aElementData['items'] diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index e6d820de..4b91def0 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -24,6 +24,12 @@ components: type: integer name: type: string + Fruit: + properties: + id: + type: integer + name: + type: string Pet: required: - id @@ -188,7 +194,16 @@ components: - type: array items: $ref: '#/components/schemas/User' + - $ref: '#/components/schemas/Fruit' + + one_of_from_multi_ref_arr: + type: array + x-no-relation: true + items: + oneOf: + - $ref: '#/components/schemas/User' + - $ref: '#/components/schemas/Fruit' + -# oneOf -# TODO count is not working in some cases \ No newline at end of file +# TODO count is not working in some cases From 65625f00e6a50b71f18621954995518b7ae4f8d6 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Sat, 17 Aug 2024 17:01:03 +0530 Subject: [PATCH 23/28] Refactor --- src/lib/FakerStubResolver.php | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 9902cf2f..c6b7ebc5 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -22,6 +22,7 @@ use cebe\yii2openapi\lib\items\JunctionSchemas; use cebe\yii2openapi\lib\openapi\ComponentSchema; use cebe\yii2openapi\lib\openapi\PropertySchema; +use stdClass; use Symfony\Component\VarExporter\Exception\ExceptionInterface; use Symfony\Component\VarExporter\VarExporter; use Yii; @@ -58,6 +59,7 @@ public function __construct(Attribute $attribute, PropertySchema $property, ?Con * @throws UnresolvableReferenceException * @throws InvalidDefinitionException * @throws ExceptionInterface + * @throws IOException */ public function resolve(): ?string { @@ -285,18 +287,18 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st } if ($items instanceof Reference) { - $aFakerForRef = $this->aElementFaker($items); + $aFakerForRef = $this->aElementFaker($items, $this->attribute->columnName); return $this->wrapInArray($aFakerForRef, $uniqueItems, $count); } if (!empty($items->oneOf)) { - return $this->handleOneOf($items, $count); + return $this->wrapInArray($this->handleOneOf($items, $count), $uniqueItems, $count, true); } $type = $items->type; if ($type === null) { return $this->arbitraryArray(); } - $aFaker = $this->aElementFaker($this->property->getProperty()); + $aFaker = $this->aElementFaker($this->property->getProperty(), $this->attribute->columnName); if (in_array($type, ['string', 'number', 'integer', 'boolean', 'array'])) { return $this->wrapInArray($aFaker, $uniqueItems, $count); } @@ -326,8 +328,6 @@ public function fakeForObject(SpecObjectInterface $items): string } $props = '[' . PHP_EOL; - $cs = new ComponentSchema($items, 'unnamed'); - $dbModels = (new AttributeResolver('unnamed', $cs, new JunctionSchemas([])))->resolve(); foreach ($items->properties as $name => $prop) { /** @var SpecObjectInterface $prop */ @@ -335,11 +335,8 @@ public function fakeForObject(SpecObjectInterface $items): string if ($prop->properties) { // nested object $result = $this->{__FUNCTION__}($prop); } else { - $ps = new PropertySchema($prop, $name, $cs); - $attr = $dbModels->attributes[$name]; - $result = (string)((new static($attr, $ps, $this->config))->resolve()); + $result = $this->aElementFaker(['items' => $prop->getSerializableData()], $name); } - $props .= '\'' . $name . '\' => ' . $result . ',' . PHP_EOL; } @@ -363,24 +360,24 @@ public function fakeForObject(SpecObjectInterface $items): string */ public function handleOneOf(SpecObjectInterface $items, int $count): string { - $result = 'array_map(function () use ($faker, $uniqueFaker) {'; + $result = ''; foreach ($items->oneOf as $key => $aDataType) { /** @var Schema|Reference $aDataType */ $inp = $aDataType instanceof Reference ? $aDataType : ['items' => $aDataType->getSerializableData()]; - $aFaker = $this->aElementFaker($inp); + $aFaker = $this->aElementFaker($inp, $this->attribute->columnName); $result .= '$dataType' . $key . ' = ' . $aFaker . ';'; } $ct = count($items->oneOf) - 1; - $result .= 'return ${"dataType".rand(0, ' . $ct . ')};'; - $result .= '}, range(1, ' . $count . '))'; + $result .= 'return ${"dataType".rand(0, ' . $ct . ')}'; return $result; } - public function wrapInArray(string $aFaker, bool $uniqueItems, int $count): string + public function wrapInArray(string $aFaker, bool $uniqueItems, int $count, bool $oneOf = false): string { + $ret = $oneOf ? '' : 'return '; return 'array_map(function () use ($faker, $uniqueFaker) { - return ' . ($uniqueItems ? str_replace('$faker->', '$uniqueFaker->', $aFaker) : $aFaker) . '; + ' . $ret . ($uniqueItems ? str_replace('$faker->', '$uniqueFaker->', $aFaker) : $aFaker) . '; }, range(1, ' . $count . '))'; } @@ -392,17 +389,18 @@ public function arbitraryArray(): string /** * This method is only for `fakeForArray()` or methods only used inside `fakeForArray()`. If needed to use outside `fakeForArray()` context then some changes might be required. * Also see OpenAPI extension `x-no-relation` in README.md - * @param $data array|\stdClass|SpecObjectInterface + * @param $data array|stdClass|SpecObjectInterface + * @param string|null $columnName * @return string|null * @throws ExceptionInterface + * @throws IOException * @throws InvalidConfigException * @throws InvalidDefinitionException * @throws TypeErrorException * @throws UnresolvableReferenceException - * @throws IOException * @internal */ - public function aElementFaker($data): ?string + public function aElementFaker($data, ?string $columnName = null): ?string { if ($data instanceof Reference) { $class = str_replace('#/components/schemas/', '', $data->getReference()); @@ -412,27 +410,29 @@ public function aElementFaker($data): ?string $inp = $data instanceof SpecObjectInterface ? $data->getSerializableData() : $data; $aElementData = Json::decode(Json::encode($inp)); + $columnName = $columnName ?? 'unnamedProp'; $compoSchemaData = [ 'properties' => [ - 'unnamedProp' => $aElementData['items'] + $columnName => $aElementData['items'] ] ]; // This condition is only for properties with type = array // If you intend to use this method from out of `fakeForArray()` context then below condition should be changed depending on your use case // Also see OpenAPI extension `x-no-relation` in README.md - if (!empty($compoSchemaData['properties']['unnamedProp']['items']['$ref'])) { - $compoSchemaData['properties']['unnamedProp']['x-no-relation'] = true; + if (!empty($compoSchemaData['properties'][$columnName]['items']['$ref'])) { + $compoSchemaData['properties'][$columnName]['x-no-relation'] = true; } $schema = new Schema($compoSchemaData); - $cs = new ComponentSchema($schema, 'UnnamedCompo'); + $compo = 'UnnamedCompo'; + $cs = new ComponentSchema($schema, $compo); if ($this->config) { $rc = new ReferenceContext($this->config->getOpenApi(), Yii::getAlias($this->config->openApiPath)); $schema->setReferenceContext($rc); } - $dbModels = (new AttributeResolver('UnnamedCompo', $cs, new JunctionSchemas([]), $this->config))->resolve(); + $dbModels = (new AttributeResolver($compo, $cs, new JunctionSchemas([]), $this->config))->resolve(); - return (new static($dbModels->attributes['unnamedProp'], $cs->getProperty('unnamedProp'), $this->config))->resolve(); + return (new static($dbModels->attributes[$columnName], $cs->getProperty($columnName), $this->config))->resolve(); } } From 77f6ff2c6cdc5a7a16f2757e22194749327032a4 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Sat, 17 Aug 2024 17:27:04 +0530 Subject: [PATCH 24/28] Fix bug --- src/lib/FakerStubResolver.php | 2 +- .../index.yaml | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index c6b7ebc5..8e253bfc 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -332,7 +332,7 @@ public function fakeForObject(SpecObjectInterface $items): string foreach ($items->properties as $name => $prop) { /** @var SpecObjectInterface $prop */ - if ($prop->properties) { // nested object + if (!empty($prop->properties)) { // nested object $result = $this->{__FUNCTION__}($prop); } else { $result = $this->aElementFaker(['items' => $prop->getSerializableData()], $name); diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 4b91def0..1078b726 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -123,6 +123,13 @@ components: type: integer minimum: 0 maximum: 200 + user: + $ref: '#/components/schemas/User' + user_2: + type: array + # x-no-relation: true # it is not required since we only implemented handling of such object for arrays only + items: + $ref: '#/components/schemas/User' tags: type: array items: @@ -155,14 +162,13 @@ components: user_ref_obj_arr_normal: # faker for this won't be generated type: array maxItems: 3 - # x-no-relation: true items: $ref: '#/components/schemas/User' user_ref_obj_arr: # special type: array maxItems: 3 - x-no-relation: true + x-no-relation: true # it is required because this property is not part of any array items: $ref: '#/components/schemas/User' @@ -198,12 +204,8 @@ components: one_of_from_multi_ref_arr: type: array - x-no-relation: true + # x-no-relation: true # it is not required since we only implemented handling of oneOf for arrays only items: oneOf: - $ref: '#/components/schemas/User' - $ref: '#/components/schemas/Fruit' - - - -# TODO count is not working in some cases From ee6f7ec0ecb38ca309f8b8ba407eb3664d68f539 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Sat, 17 Aug 2024 17:51:47 +0530 Subject: [PATCH 25/28] Add docs for `x-no-relation` and create its constant --- README.md | 65 ++++++++++++++++++++++++++++++++--- src/lib/AttributeResolver.php | 2 +- src/lib/CustomSpecAttr.php | 2 +- src/lib/FakerStubResolver.php | 2 +- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index fccf1037..c0b0fe07 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,9 @@ return $config; To use the web generator, open `index.php?r=gii` and select the `REST API Generator`. -On console you can run the generator with `./yii gii/api --openApiPath=@app/openapi.yaml`. Where `@app/openapi.yaml` should be the absolute path to your OpenAPI spec file. This can be JSON as well as YAML (see also [php-openapi/php-openapi](https://github.com/php-openapi/php-openapi/) for supported formats). +On console, you can run the generator with `./yii gii/api --openApiPath=@app/openapi.yaml`. Where `@app/openapi.yaml` +should be the absolute path to your OpenAPI spec file. This can be JSON as well as YAML (see +also [php-openapi/php-openapi](https://github.com/php-openapi/php-openapi/) for supported formats). Run `./yii gii/api --help` for all options. Example: Disable generation of migrations files `./yii gii/api --generateMigrations=0` @@ -311,6 +313,58 @@ Provide custom database table column name in case of relationship column. This w ### `x-no-relation` +To differentiate a component schema property from one-to-many or many-to-many relation in favour of array(json) of +related objects, `x-no-relation` is used. + +```yaml + comments: + type: array + items: + $ref: "#/components/schemas/Comment" +``` + +This will not generate 'comments' column in database migrations. But it will generate `getComments()` relation in model. + +In order to make it real database column, extension `x-no-relation` can be used. + +```yaml + comments: + type: array + x-no-relation: true + items: + $ref: "#/components/schemas/Comment" +``` + +Database column type can be `array`, `json` etc. to store such data. + +Now if the Comment schema from the above example is + +```yaml + Comment: + properties: + id: + type: integer + content: + type: string +``` + +then the value can be + +```json +[ + { + "id": 1, + "content": "Hi there" + }, + { + "id": 2, + "content": "Hi there 2" + } +] +``` + +At this moment, `x-no-relation` can be only used with OpenAPI schema data type `array`. + ## Many-to-Many relation definition There are two ways for define many-to-many relations: @@ -318,8 +372,8 @@ There are two ways for define many-to-many relations: ### Simple many-to-many without junction model - property name for many-to-many relation should be equal lower-cased, pluralized related schema name - - - referenced schema should contains mirrored reference to current schema + +- referenced schema should contain mirrored reference to current schema - migration for junction table can be generated automatically - table name should be [pluralized, lower-cased schema_name1]2[pluralized, lower-cased schema name2], in alphabetical order; @@ -510,12 +564,13 @@ created_at: ## Assumptions When generating code from an OpenAPI description there are many possible ways to achive a fitting result. -Thus there are some assumptions and limitations that are currently applied to make this work. +Thus, there are some assumptions and limitations that are currently applied to make this work. Here is a (possibly incomplete) list: - The current implementation works best with OpenAPI description that follows the [JSON:API](https://jsonapi.org/) guidelines. - The request and response format/schema is currently not extracted from OpenAPI schema and may need to be adjusted manually if it does not follow JSON:API -- 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()` +- 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()` - other fields can currently be used as primary keys using the `x-pk` OpenAPI extension (see below) but it may not be work correctly in all cases, please report bugs if you find them. Other things to keep in mind: diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index d1fe4678..f21ceb1c 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -218,7 +218,7 @@ protected function resolveProperty( } $attribute = Yii::createObject(Attribute::class, [$property->getName()]); - if (!empty($property->getAttr('x-no-relation'))) { // TODO custom attr + if (!empty($property->getAttr(CustomSpecAttr::NO_RELATION))) { // TODO custom attr $this->attributes[$property->getName()] = $attribute->setFakerStub($this->guessFakerStub($attribute, $property)); } diff --git a/src/lib/CustomSpecAttr.php b/src/lib/CustomSpecAttr.php index ba37d272..53640914 100644 --- a/src/lib/CustomSpecAttr.php +++ b/src/lib/CustomSpecAttr.php @@ -44,5 +44,5 @@ class CustomSpecAttr /** * Foreign key column name. See README for usage docs */ -// public const FK_COLUMN_NAME = 'x-fk-column-name'; + public const NO_RELATION = 'x-no-relation'; } diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index 8e253bfc..f0303904 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -421,7 +421,7 @@ public function aElementFaker($data, ?string $columnName = null): ?string // If you intend to use this method from out of `fakeForArray()` context then below condition should be changed depending on your use case // Also see OpenAPI extension `x-no-relation` in README.md if (!empty($compoSchemaData['properties'][$columnName]['items']['$ref'])) { - $compoSchemaData['properties'][$columnName]['x-no-relation'] = true; + $compoSchemaData['properties'][$columnName][CustomSpecAttr::NO_RELATION] = true; } $schema = new Schema($compoSchemaData); From 6cfadcfb7d14e04cdfd22d2809559eaf17211465 Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Sat, 17 Aug 2024 20:18:53 +0530 Subject: [PATCH 26/28] Handle example --- README.md | 2 +- src/lib/AttributeResolver.php | 2 +- src/lib/FakerStubResolver.php | 23 ++++++++----------- src/lib/items/Attribute.php | 22 ++++++++---------- .../index.yaml | 9 ++++++-- 5 files changed, 28 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index c0b0fe07..a71b5ec6 100644 --- a/README.md +++ b/README.md @@ -314,7 +314,7 @@ Provide custom database table column name in case of relationship column. This w ### `x-no-relation` To differentiate a component schema property from one-to-many or many-to-many relation in favour of array(json) of -related objects, `x-no-relation` is used. +related objects, `x-no-relation` (type: boolean, default: false) is used. ```yaml comments: diff --git a/src/lib/AttributeResolver.php b/src/lib/AttributeResolver.php index f21ceb1c..73d6a308 100644 --- a/src/lib/AttributeResolver.php +++ b/src/lib/AttributeResolver.php @@ -218,7 +218,7 @@ protected function resolveProperty( } $attribute = Yii::createObject(Attribute::class, [$property->getName()]); - if (!empty($property->getAttr(CustomSpecAttr::NO_RELATION))) { // TODO custom attr + if (!empty($property->getAttr(CustomSpecAttr::NO_RELATION))) { $this->attributes[$property->getName()] = $attribute->setFakerStub($this->guessFakerStub($attribute, $property)); } diff --git a/src/lib/FakerStubResolver.php b/src/lib/FakerStubResolver.php index f0303904..80eb7ec0 100644 --- a/src/lib/FakerStubResolver.php +++ b/src/lib/FakerStubResolver.php @@ -105,18 +105,21 @@ public function resolve(): ?string } elseif ($this->attribute->phpType === 'array' || substr($this->attribute->phpType, -2) === '[]') { $result = $this->fakeForArray($this->property->getProperty()); + if ($result !== '$faker->words()') { # example for array will only work with a list/`$faker->words()` + return $result; + } } elseif ($this->attribute->phpType === 'object') { $result = $this->fakeForObject($this->property->getProperty()); } else { return null; } - if (!$this->property->hasAttr('example')) { - return $result; - } - if (stripos($result, 'uniqueFaker') !== false) { + if (!$this->property->hasAttr('example') || + $this->property->getAttr('uniqueItems') + ) { return $result; } + $example = $this->property->getAttr('example'); $example = VarExporter::export($example); return str_replace('$faker->', '$faker->optional(0.92, ' . $example . ')->', $result); @@ -277,8 +280,6 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st $uniqueItems = $property->uniqueItems; } - // TODO consider example of OpenAPI spec - /** @var Schema|Reference|null $items */ $items = $property->items; @@ -308,13 +309,6 @@ private function fakeForArray(SpecObjectInterface $property, int $count = 4): st return $this->wrapInArray($result, $uniqueItems, $count); } - // TODO more complex type array/object; also consider $ref; may be recursively; may use `oneOf` - -// return '$faker->words()'; // TODO implement faker for array; also consider min, max, unique - -// if ($this->attribute->required) { -// return '["a" => "b"]'; // TODO this is incorrect, array schema should be checked first -// } return '[]'; } @@ -383,7 +377,8 @@ public function wrapInArray(string $aFaker, bool $uniqueItems, int $count, bool public function arbitraryArray(): string { - return '$faker->words()'; + $theFaker = $this->property->getAttr('uniqueItems') ? '$uniqueFaker' : '$faker'; + return $theFaker . '->words()'; } /** diff --git a/src/lib/items/Attribute.php b/src/lib/items/Attribute.php index 5563162d..95320836 100644 --- a/src/lib/items/Attribute.php +++ b/src/lib/items/Attribute.php @@ -7,19 +7,13 @@ namespace cebe\yii2openapi\lib\items; -use yii\helpers\VarDumper; -use \Yii; -use cebe\yii2openapi\lib\openapi\PropertySchema; -use cebe\yii2openapi\generator\ApiGenerator; +use cebe\yii2openapi\db\ColumnSchema; use cebe\yii2openapi\lib\exceptions\InvalidDefinitionException; +use cebe\yii2openapi\lib\openapi\PropertySchema; use yii\base\BaseObject; -use cebe\yii2openapi\db\ColumnSchema; -use yii\helpers\Inflector; -use yii\helpers\StringHelper; -use yii\db\mysql\Schema as MySqlSchema; -use SamIT\Yii2\MariaDb\Schema as MariaDbSchema; -use yii\db\pgsql\Schema as PgSqlSchema; +use yii\base\InvalidConfigException; use yii\base\NotSupportedException; +use yii\helpers\Inflector; use function is_array; use function strtolower; @@ -302,7 +296,7 @@ public function getFormattedDescription():string return $type.' $'.str_replace("\n", "\n * ", rtrim($comment)); } - public function toColumnSchema():ColumnSchema + public function toColumnSchema(): ColumnSchema { $column = new ColumnSchema([ 'name' => $this->columnName, @@ -333,7 +327,11 @@ public function toColumnSchema():ColumnSchema } /** - * @throws \yii\base\InvalidConfigException + * @param string $dbType + * @return string + * @throws InvalidDefinitionException + * @throws NotSupportedException + * @throws InvalidConfigException */ private function yiiAbstractTypeForDbSpecificType(string $dbType): string { diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml index 1078b726..97638e4b 100644 --- a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.yaml @@ -52,8 +52,8 @@ components: items: { } # array of arbitrary types e.g. [ "hello", -2, true, [5.7], {"id": 5} ] minItems: 6 maxItems: 10 - uniqueItems: true - # example: ['long-tail', 'short-tail', 'black', 'white'] + # uniqueItems: true + example: [ 'long-tail', 'short-tail', 'black', 'white' ] number_arr: type: array items: @@ -68,6 +68,8 @@ components: int_arr: type: array + # uniqueItems: true + example: [ 4, 5 ] items: type: integer @@ -158,6 +160,9 @@ components: properties: id: type: integer + title: + type: string + maxLength: 4 user_ref_obj_arr_normal: # faker for this won't be generated type: array From b3023c4af96ccc2893bec0c0aa53bed63d4f1d5c Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Sat, 17 Aug 2024 20:23:11 +0530 Subject: [PATCH 27/28] Add test spec file --- .../m200000_000000_create_table_fruits.php | 20 +++ .../m200000_000001_create_table_pets.php | 36 +++++ .../m200000_000002_create_table_users.php | 20 +++ .../mysql/models/BaseModelFaker.php | 144 ++++++++++++++++++ .../mysql/models/Fruit.php | 10 ++ .../mysql/models/FruitFaker.php | 41 +++++ .../mysql/models/Pet.php | 10 ++ .../mysql/models/PetFaker.php | 129 ++++++++++++++++ .../mysql/models/User.php | 10 ++ .../mysql/models/UserFaker.php | 41 +++++ .../mysql/models/base/Fruit.php | 26 ++++ .../mysql/models/base/Pet.php | 57 +++++++ .../mysql/models/base/User.php | 26 ++++ tests/unit/IssueFixTest.php | 20 +-- 14 files changed, 577 insertions(+), 13 deletions(-) create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000000_create_table_fruits.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000001_create_table_pets.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000002_create_table_users.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/BaseModelFaker.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/Fruit.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/FruitFaker.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/Pet.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/PetFaker.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/User.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/UserFaker.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Fruit.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Pet.php create mode 100644 tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/User.php diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000000_create_table_fruits.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000000_create_table_fruits.php new file mode 100644 index 00000000..18bf5d9f --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000000_create_table_fruits.php @@ -0,0 +1,20 @@ +createTable('{{%fruits}}', [ + 'id' => $this->primaryKey(), + 'name' => $this->text()->null(), + ]); + } + + public function down() + { + $this->dropTable('{{%fruits}}'); + } +} 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 new file mode 100644 index 00000000..01df98f3 --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000001_create_table_pets.php @@ -0,0 +1,36 @@ +createTable('{{%pets}}', [ + '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(), + ]); + } + + public function down() + { + $this->dropTable('{{%pets}}'); + } +} diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000002_create_table_users.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000002_create_table_users.php new file mode 100644 index 00000000..0aa915a3 --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/migrations_mysql_db/m200000_000002_create_table_users.php @@ -0,0 +1,20 @@ +createTable('{{%users}}', [ + 'id' => $this->primaryKey(), + 'name' => $this->text()->null(), + ]); + } + + public function down() + { + $this->dropTable('{{%users}}'); + } +} diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/BaseModelFaker.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/BaseModelFaker.php new file mode 100644 index 00000000..c367fbb4 --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/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/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/Fruit.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/Fruit.php new file mode 100644 index 00000000..c74c53d9 --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/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/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/Pet.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/Pet.php new file mode 100644 index 00000000..1b9df07c --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/Pet.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 Pet(); + //$model->id = $uniqueFaker->numberBetween(0, 1000000); + $model->name = $faker->optional(0.92, 'cat')->sentence; + $model->age = $faker->optional(0.92, 2)->numberBetween(0, 1000000); + $model->tags = array_map(function () use ($faker, $uniqueFaker) { + return $faker->sentence; + }, range(1, 4)); + $model->tags_arbit = $faker->optional(0.92, [ + 'long-tail', + 'short-tail', + 'black', + 'white', +])->words(); + $model->number_arr = array_map(function () use ($faker, $uniqueFaker) { + return $faker->randomFloat(); + }, range(1, 4)); + $model->number_arr_min_uniq = array_map(function () use ($faker, $uniqueFaker) { + return $uniqueFaker->randomFloat(); + }, range(1, 6)); + $model->int_arr = array_map(function () use ($faker, $uniqueFaker) { + return $faker->numberBetween(0, 1000000); + }, range(1, 4)); + $model->int_arr_min_uniq = array_map(function () use ($faker, $uniqueFaker) { + return $uniqueFaker->numberBetween(0, 1000000); + }, range(1, 7)); + $model->bool_arr = array_map(function () use ($faker, $uniqueFaker) { + return $faker->boolean; + }, range(1, 4)); + $model->arr_arr_int = array_map(function () use ($faker, $uniqueFaker) { + return array_map(function () use ($faker, $uniqueFaker) { + return $faker->numberBetween(0, 1000000); + }, range(1, 4)); + }, range(1, 4)); + $model->arr_arr_str = array_map(function () use ($faker, $uniqueFaker) { + return array_map(function () use ($faker, $uniqueFaker) { + return $faker->sentence; + }, range(1, 4)); + }, range(1, 4)); + $model->arr_arr_arr_str = array_map(function () use ($faker, $uniqueFaker) { + return array_map(function () use ($faker, $uniqueFaker) { + return array_map(function () use ($faker, $uniqueFaker) { + return $faker->sentence; + }, range(1, 5)); + }, range(1, 4)); + }, range(1, 3)); + $model->arr_of_obj = array_map(function () use ($faker, $uniqueFaker) { + return [ +'id' => $uniqueFaker->numberBetween(0, 1000000), +'name' => $faker->sentence, +'age' => $faker->numberBetween(0, 200), +'user' => $faker->randomElement(\app\models\User::find()->select("id")->column()), +'user_2' => array_map(function () use ($faker, $uniqueFaker) { + return (new UserFaker)->generateModel()->attributes; + }, range(1, 4)), +'tags' => array_map(function () use ($faker, $uniqueFaker) { + return $uniqueFaker->sentence; + }, range(1, 4)), +'arr_arr_int_2' => array_map(function () use ($faker, $uniqueFaker) { + return array_map(function () use ($faker, $uniqueFaker) { + return $faker->numberBetween(0, 1000000); + }, range(1, 11)); + }, range(1, 4)), +'appearance' => [ +'height' => $faker->numberBetween(0, 20), +'weight' => $faker->numberBetween(0, 1000000), +'email' => $faker->safeEmail, +'nested_obj' => [ +'id' => $uniqueFaker->numberBetween(0, 1000000), +'title' => $faker->title, +], +], +]; + }, range(1, 3)); + $model->user_ref_obj_arr = array_map(function () use ($faker, $uniqueFaker) { + return (new UserFaker)->generateModel()->attributes; + }, range(1, 3)); + $model->one_of_arr = array_map(function () use ($faker, $uniqueFaker) { + $dataType0 = $faker->numberBetween(0, 1000000);$dataType1 = $faker->sentence;$dataType2 = $faker->boolean;return ${"dataType".rand(0, 2)}; + }, range(1, 4)); + $model->one_of_arr_complex = array_map(function () use ($faker, $uniqueFaker) { + $dataType0 = $faker->numberBetween(0, 1000000);$dataType1 = $faker->sentence;$dataType2 = $faker->boolean;$dataType3 = $faker->words();$dataType4 = array_map(function () use ($faker, $uniqueFaker) { + return $faker->sentence; + }, range(1, 4));$dataType5 = [ +'id' => $uniqueFaker->numberBetween(0, 1000000), +];$dataType6 = array_map(function () use ($faker, $uniqueFaker) { + return (new UserFaker)->generateModel()->attributes; + }, range(1, 4));$dataType7 = (new FruitFaker)->generateModel()->attributes;return ${"dataType".rand(0, 7)}; + }, range(1, 8)); + $model->one_of_from_multi_ref_arr = array_map(function () use ($faker, $uniqueFaker) { + $dataType0 = (new UserFaker)->generateModel()->attributes;$dataType1 = (new FruitFaker)->generateModel()->attributes;return ${"dataType".rand(0, 1)}; + }, 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/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/User.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/User.php new file mode 100644 index 00000000..9b837d6e --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/User.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 User(); + //$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/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Fruit.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Fruit.php new file mode 100644 index 00000000..2106e69d --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Fruit.php @@ -0,0 +1,26 @@ + [['name'], 'trim'], + 'name_string' => [['name'], 'string'], + ]; + } +} 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 new file mode 100644 index 00000000..73f57ea7 --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/Pet.php @@ -0,0 +1,57 @@ + [['name'], 'trim'], + 'required' => [['name'], 'required'], + 'name_string' => [['name'], 'string'], + 'age_integer' => [['age'], 'integer'], + 'safe' => [['tags', 'tags_arbit', 'number_arr', 'number_arr_min_uniq', 'int_arr', 'int_arr_min_uniq', 'bool_arr', 'arr_arr_int', 'arr_arr_str', 'arr_arr_arr_str', 'arr_of_obj', 'user_ref_obj_arr', 'one_of_arr', 'one_of_arr_complex', 'one_of_from_multi_ref_arr'], 'safe'], + ]; + } + + public function getUserRefObjArrNormal() + { + return $this->hasMany(\app\models\User::class, ['pet_id' => 'id']); + } + + public function getUserRefObjArr() + { + return $this->hasMany(\app\models\User::class, ['pet_id' => 'id']); + } +} diff --git a/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/User.php b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/User.php new file mode 100644 index 00000000..08e59880 --- /dev/null +++ b/tests/specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql/models/base/User.php @@ -0,0 +1,26 @@ + [['name'], 'trim'], + 'name_string' => [['name'], 'string'], + ]; + } +} diff --git a/tests/unit/IssueFixTest.php b/tests/unit/IssueFixTest.php index 022ba78b..4e319b28 100644 --- a/tests/unit/IssueFixTest.php +++ b/tests/unit/IssueFixTest.php @@ -364,20 +364,14 @@ public function test158BugGiiapiGeneratedRulesEnumWithTrim() // https://github.com/php-openapi/yii2-openapi/issues/20 public function test20ConsiderOpenApiSpecExamplesInFakeCodeGeneration() { -// $faker = \Faker\Factory::create(); -// $uniqueFaker = $faker->unique(); -// -// var_dump(); -// return; - $testFile = Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/index.php"); $this->runGenerator($testFile); -// $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ -// 'recursive' => true, -// ]); -// $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql"), [ -// 'recursive' => true, -// ]); -// $this->checkFiles($actualFiles, $expectedFiles); // TODO + $actualFiles = FileHelper::findFiles(Yii::getAlias('@app'), [ + 'recursive' => true, + ]); + $expectedFiles = FileHelper::findFiles(Yii::getAlias("@specs/issue_fix/20_consider_openapi_spec_examples_in_faker_code_generation/mysql"), [ + 'recursive' => true, + ]); + $this->checkFiles($actualFiles, $expectedFiles); } } From 775ff26adf7915ac42c637900b659e003057efbc Mon Sep 17 00:00:00 2001 From: Sohel Ahmed Mesaniya Date: Sat, 17 Aug 2024 20:31:38 +0530 Subject: [PATCH 28/28] Enhance docs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a71b5ec6..8cabdcb5 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ related objects, `x-no-relation` (type: boolean, default: false) is used. $ref: "#/components/schemas/Comment" ``` -This will not generate 'comments' column in database migrations. But it will generate `getComments()` relation in model. +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. @@ -348,7 +348,7 @@ Now if the Comment schema from the above example is type: string ``` -then the value can be +then the value for `comments` can be ```json [ @@ -363,7 +363,7 @@ then the value can be ] ``` -At this moment, `x-no-relation` can be only used with OpenAPI schema data type `array`. +`x-no-relation` can be only used with OpenAPI schema data type `array`. ## Many-to-Many relation definition