From 725110ce712d50782d074258803f944a15e66bd9 Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Fri, 19 Mar 2021 14:14:26 -0400 Subject: [PATCH] Generate correct reference for nested models --- src/Generators/FactoryGenerator.php | 20 ++++++-- src/Generators/ModelGenerator.php | 34 ++++++++++--- src/Tree.php | 2 +- .../Generators/FactoryGeneratorTest.php | 36 +++++++++++++ .../Feature/Generators/ModelGeneratorTest.php | 49 ++++++++++++++++++ tests/fixtures/drafts/nested-models.yaml | 14 ++++++ .../factories/nested-models-laravel8.php | 34 +++++++++++++ .../models/nested-models-laravel8.php | 50 +++++++++++++++++++ 8 files changed, 228 insertions(+), 11 deletions(-) create mode 100644 tests/fixtures/drafts/nested-models.yaml create mode 100644 tests/fixtures/factories/nested-models-laravel8.php create mode 100644 tests/fixtures/models/nested-models-laravel8.php diff --git a/src/Generators/FactoryGenerator.php b/src/Generators/FactoryGenerator.php index eadbd75b..2f8219a5 100644 --- a/src/Generators/FactoryGenerator.php +++ b/src/Generators/FactoryGenerator.php @@ -17,6 +17,9 @@ class FactoryGenerator implements Generator /** @var \Illuminate\Contracts\Filesystem\Filesystem */ private $files; + /** @var Tree */ + private $tree; + private $imports = []; public function __construct($files) @@ -26,6 +29,8 @@ public function __construct($files) public function output(Tree $tree): array { + $this->tree = $tree; + $output = []; if (Blueprint::isLaravel8OrHigher()) { @@ -127,9 +132,10 @@ protected function buildDefinition(Model $model) } $class = Str::studly(Str::singular($table)); + $reference = $this->fullyQualifyModelReference($class) ?? $model; if (Blueprint::isLaravel8OrHigher()) { - $this->addImport($model, $model->fullyQualifiedNamespace() . '\\' . $class); + $this->addImport($model, $reference->fullyQualifiedNamespace() . '\\' . $class); } if ($key === 'id') { if (Blueprint::isLaravel8OrHigher()) { @@ -138,7 +144,7 @@ protected function buildDefinition(Model $model) $definition .= ',' . PHP_EOL; } else { $definition .= str_repeat(self::INDENT, 2) . "'{$column->name()}' => "; - $definition .= sprintf('factory(%s::class)', '\\' . $model->fullyQualifiedNamespace() . '\\' . $class); + $definition .= sprintf('factory(%s::class)', '\\' . $reference->fullyQualifiedNamespace() . '\\' . $class); $definition .= ',' . PHP_EOL; } } else { @@ -157,14 +163,15 @@ protected function buildDefinition(Model $model) } elseif ($column->dataType() === 'id' || ($column->dataType() === 'uuid' && Str::endsWith($column->name(), '_id'))) { $name = Str::beforeLast($column->name(), '_id'); $class = Str::studly($column->attributes()[0] ?? $name); + $reference = $this->fullyQualifyModelReference($class) ?? $model; if (Blueprint::isLaravel8OrHigher()) { - $this->addImport($model, $model->fullyQualifiedNamespace() . '\\' . $class); + $this->addImport($model, $reference->fullyQualifiedNamespace() . '\\' . $class); $definition .= str_repeat(self::INDENT, 3) . "'{$column->name()}' => "; $definition .= sprintf('%s::factory()', $class); } else { $definition .= str_repeat(self::INDENT, 2) . "'{$column->name()}' => "; - $definition .= sprintf('factory(%s::class)', '\\' . $model->fullyQualifiedNamespace() . '\\' . $class); + $definition .= sprintf('factory(%s::class)', '\\' . $reference->fullyQualifiedNamespace() . '\\' . $class); } $definition .= ',' . PHP_EOL; } elseif (in_array($column->dataType(), ['enum', 'set']) && !empty($column->attributes())) { @@ -286,4 +293,9 @@ private function fillableColumns(array $columns): array return !in_array('nullable', $column->modifiers()); }); } + + private function fullyQualifyModelReference(string $model_name) + { + return $this->tree->modelForContext($model_name); + } } diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php index 1119d7c3..bb64a830 100644 --- a/src/Generators/ModelGenerator.php +++ b/src/Generators/ModelGenerator.php @@ -14,6 +14,9 @@ class ModelGenerator implements Generator /** @var \Illuminate\Contracts\Filesystem\Filesystem */ private $files; + /** @var Tree */ + private $tree; + public function __construct($files) { $this->files = $files; @@ -21,6 +24,8 @@ public function __construct($files) public function output(Tree $tree): array { + $this->tree = $tree; + $output = []; if (Blueprint::isLaravel8OrHigher()) { @@ -210,19 +215,20 @@ protected function buildRelationships(Model $model) } $class_name = Str::studly($class ?? $method_name); + $fqcn = $this->fullyQualifyModelReference($class_name) ?? $model->fullyQualifiedNamespace() . '\\' . $class_name; if ($type === 'morphTo') { $relationship = sprintf('$this->%s()', $type); } elseif ($type === 'morphMany' || $type === 'morphOne') { $relation = Str::lower(Str::singular($column_name)) . 'able'; - $relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class_name, $relation); - } elseif (! is_null($key)) { - $relationship = sprintf('$this->%s(%s::class, \'%s\', \'%s\')', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class_name, $column_name, $key); - } elseif (! is_null($class) && $type === 'belongsToMany') { - $relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class_name, $column_name); + $relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, '\\' . $fqcn, $relation); + } elseif (!is_null($key)) { + $relationship = sprintf('$this->%s(%s::class, \'%s\', \'%s\')', $type, '\\' . $fqcn, $column_name, $key); + } elseif (!is_null($class) && $type === 'belongsToMany') { + $relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, '\\' . $fqcn, $column_name); $column_name = $class; } else { - $relationship = sprintf('$this->%s(%s::class)', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class_name); + $relationship = sprintf('$this->%s(%s::class)', $type, '\\' . $fqcn); } if ($type === 'morphTo') { @@ -408,4 +414,20 @@ private function phpDataType(string $dataType) return $php_data_types[strtolower($dataType)] ?? 'string'; } + + private function fullyQualifyModelReference(string $model_name) + { + // TODO: get model_name from tree. + // If not found, assume parallel namespace as controller. + // Use respond-statement.php as test case. + + /** @var \Blueprint\Models\Model $model */ + $model = $this->tree->modelForContext($model_name); + + if (isset($model)) { + return $model->fullyQualifiedClassName(); + } + + return null; + } } diff --git a/src/Tree.php b/src/Tree.php index 522c89bd..b4159de4 100644 --- a/src/Tree.php +++ b/src/Tree.php @@ -46,7 +46,7 @@ public function modelForContext(string $context) }); if (count($matches) === 1) { - return $this->models[$matches[0]]; + return $this->models[current($matches)]; } } diff --git a/tests/Feature/Generators/FactoryGeneratorTest.php b/tests/Feature/Generators/FactoryGeneratorTest.php index 90e71618..7889d93e 100644 --- a/tests/Feature/Generators/FactoryGeneratorTest.php +++ b/tests/Feature/Generators/FactoryGeneratorTest.php @@ -169,6 +169,42 @@ public function output_using_return_types() $this->assertEquals(['created' => ['database/factories/PostFactory.php']], $this->subject->output($tree)); } + /** + * @test + * @environment-setup useLaravel8 + */ + public function output_generates_references_for_nested_models() + { + $this->files->expects('stub') + ->with($this->factoryStub) + ->andReturn($this->stub($this->factoryStub)); + + $this->files->expects('exists') + ->times(4) + ->andReturnTrue(); + + $this->files->expects('put') + ->with('database/factories/QuestionTypeFactory.php', \Mockery::type('string')); + $this->files->expects('put') + ->with('database/factories/Appointment/AppointmentTypeFactory.php', \Mockery::type('string')); + $this->files->expects('put') + ->with('database/factories/Screening/ReportFactory.php', \Mockery::type('string')); + $this->files->expects('put') + ->with('database/factories/Screening/ScreeningQuestionFactory.php', $this->fixture('factories/nested-models-laravel8.php')); + + $tokens = $this->blueprint->parse($this->fixture('drafts/nested-models.yaml')); + $tree = $this->blueprint->analyze($tokens); + + $this->assertEquals([ + 'created' => [ + 'database/factories/QuestionTypeFactory.php', + 'database/factories/Appointment/AppointmentTypeFactory.php', + 'database/factories/Screening/ReportFactory.php', + 'database/factories/Screening/ScreeningQuestionFactory.php', + ], + ], $this->subject->output($tree)); + } + /** * @test * @environment-setup useLaravel8 diff --git a/tests/Feature/Generators/ModelGeneratorTest.php b/tests/Feature/Generators/ModelGeneratorTest.php index a0c39b99..33b2805e 100644 --- a/tests/Feature/Generators/ModelGeneratorTest.php +++ b/tests/Feature/Generators/ModelGeneratorTest.php @@ -581,6 +581,55 @@ public function output_generates_models_with_guarded_property_when_config_option $this->assertEquals(['created' => ['app/Comment.php']], $this->subject->output($tree)); } + /** + * @test + * @environment-setup useLaravel8 + */ + public function output_generates_models_with_namespaces_correctly() + { + $this->app['config']->set('blueprint.models_namespace', 'Models'); + + $this->files->expects('stub') + ->with($this->modelStub) + ->andReturn($this->stub($this->modelStub)); + $this->files->expects('stub') + ->times(4) + ->with('model.fillable.stub') + ->andReturn($this->stub('model.fillable.stub')); + $this->files->expects('stub') + ->times(4) + ->with('model.casts.stub') + ->andReturn($this->stub('model.casts.stub')); + $this->files->expects('stub') + ->times(4) + ->with('model.method.stub') + ->andReturn($this->stub('model.method.stub')); + + $this->files->expects('exists') + ->times(4) + ->andReturnTrue(); + $this->files->expects('put') + ->with('app/Models/QuestionType.php', \Mockery::type('string')); + $this->files->expects('put') + ->with('app/Models/Appointment/AppointmentType.php', \Mockery::type('string')); + $this->files->expects('put') + ->with('app/Models/Screening/Report.php', \Mockery::type('string')); + $this->files->expects('put') + ->with('app/Models/Screening/ScreeningQuestion.php', $this->fixture('models/nested-models-laravel8.php')); + + $tokens = $this->blueprint->parse($this->fixture('drafts/nested-models.yaml')); + $tree = $this->blueprint->analyze($tokens); + + $this->assertEquals([ + 'created' => [ + 'app/Models/QuestionType.php', + 'app/Models/Appointment/AppointmentType.php', + 'app/Models/Screening/Report.php', + 'app/Models/Screening/ScreeningQuestion.php', + ], + ], $this->subject->output($tree)); + } + /** * @test * @environment-setup useLaravel8 diff --git a/tests/fixtures/drafts/nested-models.yaml b/tests/fixtures/drafts/nested-models.yaml new file mode 100644 index 00000000..d13df0da --- /dev/null +++ b/tests/fixtures/drafts/nested-models.yaml @@ -0,0 +1,14 @@ +models: + QuestionType: + description: string + + Appointment/AppointmentType: + name: string + + Screening/Report: + name: string + + Screening/ScreeningQuestion: + report_id: id + appointment_type_id: id + question_type_id: id diff --git a/tests/fixtures/factories/nested-models-laravel8.php b/tests/fixtures/factories/nested-models-laravel8.php new file mode 100644 index 00000000..453545cb --- /dev/null +++ b/tests/fixtures/factories/nested-models-laravel8.php @@ -0,0 +1,34 @@ + Report::factory(), + 'appointment_type_id' => AppointmentType::factory(), + 'question_type_id' => QuestionType::factory(), + ]; + } +} diff --git a/tests/fixtures/models/nested-models-laravel8.php b/tests/fixtures/models/nested-models-laravel8.php new file mode 100644 index 00000000..147165e5 --- /dev/null +++ b/tests/fixtures/models/nested-models-laravel8.php @@ -0,0 +1,50 @@ + 'integer', + 'report_id' => 'integer', + 'appointment_type_id' => 'integer', + 'question_type_id' => 'integer', + ]; + + + public function report() + { + return $this->belongsTo(\App\Models\Screening\Report::class); + } + + public function appointmentType() + { + return $this->belongsTo(\App\Models\Appointment\AppointmentType::class); + } + + public function questionType() + { + return $this->belongsTo(\App\Models\QuestionType::class); + } +}