From 49c42771d87064afe4e1403e7bd48e085a4cc77b Mon Sep 17 00:00:00 2001 From: Gianluca Bine Date: Thu, 14 May 2020 22:15:19 -0300 Subject: [PATCH 1/6] Add support for polymorphic relations --- src/Generators/ModelGenerator.php | 16 ++++++- src/Lexers/ModelLexer.php | 5 ++- .../Feature/Generator/ModelGeneratorTest.php | 45 +++++++++++++++++++ .../definitions/polymorphic-relationships.bp | 15 +++++++ .../models/image-polymorphic-relationship.php | 32 +++++++++++++ .../models/post-polymorphic-relationship.php | 32 +++++++++++++ .../models/user-polymorphic-relationship.php | 32 +++++++++++++ 7 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/definitions/polymorphic-relationships.bp create mode 100644 tests/fixtures/models/image-polymorphic-relationship.php create mode 100644 tests/fixtures/models/post-polymorphic-relationship.php create mode 100644 tests/fixtures/models/user-polymorphic-relationship.php diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php index ccf59a2a..99f32cde 100644 --- a/src/Generators/ModelGenerator.php +++ b/src/Generators/ModelGenerator.php @@ -146,9 +146,21 @@ private function buildRelationships(Model $model) $name = Str::beforeLast($name, '_id'); $class = Str::studly($class ?? $name); - $relationship = sprintf("\$this->%s(%s::class)", $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class); - $method_name = $type === 'hasMany' || $type === 'belongsToMany' ? Str::plural($name) : $name; + if ($type === 'morphTo') { + $relationship = sprintf("\$this->%s()", $type); + } elseif ($type === 'morphMany' || $type === 'morphOne') { + $relation = Str::of($name)->lower()->singular() . 'able'; + $relationship = sprintf("\$this->%s(%s::class, '%s')", $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class, $relation); + } else { + $relationship = sprintf("\$this->%s(%s::class)", $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class); + } + + if ($type === 'morphTo') { + $method_name = Str::lower($class); + } else { + $method_name = $type === 'hasMany' || $type === 'belongsToMany' || $type === 'morphMany' ? Str::plural($name) : $name; + } $method = str_replace('DummyName', Str::camel($method_name), $template); $method = str_replace('null', $relationship, $method); diff --git a/src/Lexers/ModelLexer.php b/src/Lexers/ModelLexer.php index 333cda7f..7381f6a3 100644 --- a/src/Lexers/ModelLexer.php +++ b/src/Lexers/ModelLexer.php @@ -12,7 +12,10 @@ class ModelLexer implements Lexer 'belongsto' => 'belongsTo', 'hasone' => 'hasOne', 'hasmany' => 'hasMany', - 'belongstomany' => 'belongsToMany' + 'belongstomany' => 'belongsToMany', + 'morphone' => 'morphOne', + 'morphmany' => 'morphMany', + 'morphto' => 'morphTo', ]; private static $dataTypes = [ diff --git a/tests/Feature/Generator/ModelGeneratorTest.php b/tests/Feature/Generator/ModelGeneratorTest.php index 98193cb2..5861fd26 100644 --- a/tests/Feature/Generator/ModelGeneratorTest.php +++ b/tests/Feature/Generator/ModelGeneratorTest.php @@ -164,6 +164,51 @@ public function output_generates_relationships() $this->assertEquals(['created' => ['app/Subscription.php']], $this->subject->output($tree)); } + /** + * @test + */ + public function output_generates_polymorphic_relationships() + { + $this->files->expects('stub') + ->with('model/class.stub') + ->andReturn(file_get_contents('stubs/model/class.stub')); + $this->files->expects('stub') + ->times(3) + ->with('model/fillable.stub') + ->andReturn(file_get_contents('stubs/model/fillable.stub')); + $this->files->expects('stub') + ->times(3) + ->with('model/casts.stub') + ->andReturn(file_get_contents('stubs/model/casts.stub')); + $this->files->expects('stub') + ->times(3) + ->with('model/method.stub') + ->andReturn(file_get_contents('stubs/model/method.stub')); + + $this->files->expects('exists') + ->with('app') + ->andReturnTrue(); + $this->files->expects('put') + ->with('app/Post.php', $this->fixture('models/post-polymorphic-relationship.php')); + + $this->files->expects('exists') + ->with('app') + ->andReturnTrue(); + $this->files->expects('put') + ->with('app/User.php', $this->fixture('models/user-polymorphic-relationship.php')); + + $this->files->expects('exists') + ->with('app') + ->andReturnTrue(); + $this->files->expects('put') + ->with('app/Image.php', $this->fixture('models/image-polymorphic-relationship.php')); + + $tokens = $this->blueprint->parse($this->fixture('definitions/polymorphic-relationships.bp')); + $tree = $this->blueprint->analyze($tokens); + + $this->assertEquals(['created' => ['app/Post.php', 'app/User.php', 'app/Image.php']], $this->subject->output($tree)); + } + /** * @test */ diff --git a/tests/fixtures/definitions/polymorphic-relationships.bp b/tests/fixtures/definitions/polymorphic-relationships.bp new file mode 100644 index 00000000..ac4b9143 --- /dev/null +++ b/tests/fixtures/definitions/polymorphic-relationships.bp @@ -0,0 +1,15 @@ +models: + Post: + name: string:400 + relationships: + morphMany: Image + + User: + name: string:400 + relationships: + morphMany: Image + + Image: + url: string:400 + relationships: + morphTo: Imageable diff --git a/tests/fixtures/models/image-polymorphic-relationship.php b/tests/fixtures/models/image-polymorphic-relationship.php new file mode 100644 index 00000000..8aa3b034 --- /dev/null +++ b/tests/fixtures/models/image-polymorphic-relationship.php @@ -0,0 +1,32 @@ + 'integer', + ]; + + + public function imageable() + { + return $this->morphTo(); + } +} diff --git a/tests/fixtures/models/post-polymorphic-relationship.php b/tests/fixtures/models/post-polymorphic-relationship.php new file mode 100644 index 00000000..a985d00d --- /dev/null +++ b/tests/fixtures/models/post-polymorphic-relationship.php @@ -0,0 +1,32 @@ + 'integer', + ]; + + + public function images() + { + return $this->morphMany(\App\Image::class, 'imageable'); + } +} diff --git a/tests/fixtures/models/user-polymorphic-relationship.php b/tests/fixtures/models/user-polymorphic-relationship.php new file mode 100644 index 00000000..dd5f75e4 --- /dev/null +++ b/tests/fixtures/models/user-polymorphic-relationship.php @@ -0,0 +1,32 @@ + 'integer', + ]; + + + public function images() + { + return $this->morphMany(\App\Image::class, 'imageable'); + } +} From ab541579e78aeb25b5cca67c8b42963de9b2141b Mon Sep 17 00:00:00 2001 From: Gianluca Bine Date: Fri, 15 May 2020 23:03:28 -0300 Subject: [PATCH 2/6] Add support for polymorphic relations in migrations --- src/Generators/MigrationGenerator.php | 5 ++ src/Lexers/ModelLexer.php | 4 ++ src/Models/Model.php | 11 ++++ .../Generator/MigrationGeneratorTest.php | 64 +++++++++++++++++++ tests/Feature/Lexers/ModelLexerTest.php | 36 +++++++++++ ...polymorphic_relationships_images_table.php | 34 ++++++++++ ...ic_relationships_images_table_laravel6.php | 34 ++++++++++ .../polymorphic_relationships_posts_table.php | 32 ++++++++++ ...hic_relationships_posts_table_laravel6.php | 32 ++++++++++ .../polymorphic_relationships_users_table.php | 32 ++++++++++ ...hic_relationships_users_table_laravel6.php | 32 ++++++++++ 11 files changed, 316 insertions(+) create mode 100644 tests/fixtures/migrations/polymorphic_relationships_images_table.php create mode 100644 tests/fixtures/migrations/polymorphic_relationships_images_table_laravel6.php create mode 100644 tests/fixtures/migrations/polymorphic_relationships_posts_table.php create mode 100644 tests/fixtures/migrations/polymorphic_relationships_posts_table_laravel6.php create mode 100644 tests/fixtures/migrations/polymorphic_relationships_users_table.php create mode 100644 tests/fixtures/migrations/polymorphic_relationships_users_table_laravel6.php diff --git a/src/Generators/MigrationGenerator.php b/src/Generators/MigrationGenerator.php index ece71716..d4350c25 100644 --- a/src/Generators/MigrationGenerator.php +++ b/src/Generators/MigrationGenerator.php @@ -167,6 +167,11 @@ protected function buildDefinition(Model $model) $definition .= self::INDENT . '$table->' . $model->softDeletesDataType() . '();' . PHP_EOL; } + if ($model->morphTo()) { + $definition .= self::INDENT . sprintf('$table->unsignedBigInteger(\'%s\');', Str::lower($model->morphTo() . "_id")) . PHP_EOL; + $definition .= self::INDENT . sprintf('$table->string(\'%s\');', Str::lower($model->morphTo() . "_type")) . PHP_EOL; + } + if ($model->usesTimestamps()) { $definition .= self::INDENT . '$table->' . $model->timestampsDataType() . '();' . PHP_EOL; } diff --git a/src/Lexers/ModelLexer.php b/src/Lexers/ModelLexer.php index 7381f6a3..0a7d217d 100644 --- a/src/Lexers/ModelLexer.php +++ b/src/Lexers/ModelLexer.php @@ -152,6 +152,10 @@ private function buildModel(string $name, array $columns) foreach ($columns['relationships'] as $type => $relationships) { foreach (explode(',', $relationships) as $reference) { $model->addRelationship(self::$relationships[strtolower($type)], trim($reference)); + + if ($type === 'morphTo') { + $model->setMorphTo(trim($reference)); + } } } } diff --git a/src/Models/Model.php b/src/Models/Model.php index 4ff3005d..d40f3210 100644 --- a/src/Models/Model.php +++ b/src/Models/Model.php @@ -11,6 +11,7 @@ class Model private $primaryKey = 'id'; private $timestamps = 'timestamps'; private $softDeletes = false; + private $morphTo; private $columns = []; private $relationships = []; private $pivotTables = []; @@ -162,4 +163,14 @@ public function pivotTables(): array { return $this->pivotTables; } + + public function setMorphTo(string $reference) + { + $this->morphTo = $reference; + } + + public function morphTo(): ?string + { + return $this->morphTo; + } } diff --git a/tests/Feature/Generator/MigrationGeneratorTest.php b/tests/Feature/Generator/MigrationGeneratorTest.php index ef5ecd4b..830c56bf 100644 --- a/tests/Feature/Generator/MigrationGeneratorTest.php +++ b/tests/Feature/Generator/MigrationGeneratorTest.php @@ -368,6 +368,70 @@ public function output_does_not_duplicate_pivot_table_migration_laravel6() $this->assertEquals(['created' => [$company_migration, $people_migration, $pivot_migration]], $this->subject->output($tree)); } + /** + * @test + */ + public function output_works_with_polymorphic_relationships() + { + $this->files->expects('stub') + ->with('migration.stub') + ->andReturn(file_get_contents('stubs/migration.stub')); + + $now = Carbon::now(); + Carbon::setTestNow($now); + + $post_migration = str_replace('timestamp', $now->copy()->subSeconds(2)->format('Y_m_d_His'), 'database/migrations/timestamp_create_posts_table.php'); + $user_migration = str_replace('timestamp', $now->copy()->subSecond()->format('Y_m_d_His'), 'database/migrations/timestamp_create_users_table.php'); + $image_migration = str_replace('timestamp', $now->format('Y_m_d_His'), 'database/migrations/timestamp_create_images_table.php'); + + $this->files->expects('put') + ->with($post_migration, $this->fixture('migrations/polymorphic_relationships_posts_table.php')); + $this->files->expects('put') + ->with($user_migration, $this->fixture('migrations/polymorphic_relationships_users_table.php')); + $this->files->expects('put') + ->with($image_migration, $this->fixture('migrations/polymorphic_relationships_images_table.php')); + + $tokens = $this->blueprint->parse($this->fixture('definitions/polymorphic-relationships.bp')); + $tree = $this->blueprint->analyze($tokens); + + $this->assertEquals(['created' => [$post_migration, $user_migration, $image_migration]], $this->subject->output($tree)); + } + + /** + * @test + */ + public function output_works_with_polymorphic_relationships_laravel6() + { + $app = \Mockery::mock(); + $app->shouldReceive('version') + ->withNoArgs() + ->andReturn('6.0.0'); + App::swap($app); + + $this->files->expects('stub') + ->with('migration.stub') + ->andReturn(file_get_contents('stubs/migration.stub')); + + $now = Carbon::now(); + Carbon::setTestNow($now); + + $post_migration = str_replace('timestamp', $now->copy()->subSeconds(2)->format('Y_m_d_His'), 'database/migrations/timestamp_create_posts_table.php'); + $user_migration = str_replace('timestamp', $now->copy()->subSecond()->format('Y_m_d_His'), 'database/migrations/timestamp_create_users_table.php'); + $image_migration = str_replace('timestamp', $now->format('Y_m_d_His'), 'database/migrations/timestamp_create_images_table.php'); + + $this->files->expects('put') + ->with($post_migration, $this->fixture('migrations/polymorphic_relationships_posts_table_laravel6.php')); + $this->files->expects('put') + ->with($user_migration, $this->fixture('migrations/polymorphic_relationships_users_table_laravel6.php')); + $this->files->expects('put') + ->with($image_migration, $this->fixture('migrations/polymorphic_relationships_images_table_laravel6.php')); + + $tokens = $this->blueprint->parse($this->fixture('definitions/polymorphic-relationships.bp')); + $tree = $this->blueprint->analyze($tokens); + + $this->assertEquals(['created' => [$post_migration, $user_migration, $image_migration]], $this->subject->output($tree)); + } + public function modelTreeDataProvider() { return [ diff --git a/tests/Feature/Lexers/ModelLexerTest.php b/tests/Feature/Lexers/ModelLexerTest.php index cfd386f1..663f0192 100644 --- a/tests/Feature/Lexers/ModelLexerTest.php +++ b/tests/Feature/Lexers/ModelLexerTest.php @@ -499,6 +499,42 @@ public function it_stores_relationships() $this->assertEquals(['Duration', 'Transaction:tid'], $relationships['hasOne']); } + /** + * @test + */ + public function it_enables_morphable_and_set_its_reference() + { + $tokens = [ + 'models' => [ + 'Model' => [ + 'relationships' => [ + 'morphTo' => 'Morphable', + ] + ], + ], + ]; + + $actual = $this->subject->analyze($tokens); + + $this->assertIsArray($actual['models']); + $this->assertCount(1, $actual['models']); + + $model = $actual['models']['Model']; + $this->assertEquals('Model', $model->name()); + $this->assertEquals('Morphable', $model->morphTo()); + $this->assertTrue($model->usesTimestamps()); + + $columns = $model->columns(); + $this->assertCount(1, $columns); + $this->assertEquals('id', $columns['id']->name()); + $this->assertEquals('id', $columns['id']->dataType()); + $this->assertEquals([], $columns['id']->modifiers()); + + $relationships = $model->relationships(); + $this->assertCount(1, $relationships); + $this->assertEquals(['Morphable'], $relationships['morphTo']); + } + public function dataTypeAttributesDataProvider() { return [ diff --git a/tests/fixtures/migrations/polymorphic_relationships_images_table.php b/tests/fixtures/migrations/polymorphic_relationships_images_table.php new file mode 100644 index 00000000..22138a77 --- /dev/null +++ b/tests/fixtures/migrations/polymorphic_relationships_images_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('url', 400); + $table->unsignedBigInteger('imageable_id'); + $table->string('imageable_type'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('images'); + } +} diff --git a/tests/fixtures/migrations/polymorphic_relationships_images_table_laravel6.php b/tests/fixtures/migrations/polymorphic_relationships_images_table_laravel6.php new file mode 100644 index 00000000..ec0cf69e --- /dev/null +++ b/tests/fixtures/migrations/polymorphic_relationships_images_table_laravel6.php @@ -0,0 +1,34 @@ +bigIncrements('id'); + $table->string('url', 400); + $table->unsignedBigInteger('imageable_id'); + $table->string('imageable_type'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('images'); + } +} diff --git a/tests/fixtures/migrations/polymorphic_relationships_posts_table.php b/tests/fixtures/migrations/polymorphic_relationships_posts_table.php new file mode 100644 index 00000000..daa77b34 --- /dev/null +++ b/tests/fixtures/migrations/polymorphic_relationships_posts_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name', 400); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('posts'); + } +} diff --git a/tests/fixtures/migrations/polymorphic_relationships_posts_table_laravel6.php b/tests/fixtures/migrations/polymorphic_relationships_posts_table_laravel6.php new file mode 100644 index 00000000..9f45d2bb --- /dev/null +++ b/tests/fixtures/migrations/polymorphic_relationships_posts_table_laravel6.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + $table->string('name', 400); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('posts'); + } +} diff --git a/tests/fixtures/migrations/polymorphic_relationships_users_table.php b/tests/fixtures/migrations/polymorphic_relationships_users_table.php new file mode 100644 index 00000000..b9c84d54 --- /dev/null +++ b/tests/fixtures/migrations/polymorphic_relationships_users_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name', 400); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('users'); + } +} diff --git a/tests/fixtures/migrations/polymorphic_relationships_users_table_laravel6.php b/tests/fixtures/migrations/polymorphic_relationships_users_table_laravel6.php new file mode 100644 index 00000000..bcd71a5c --- /dev/null +++ b/tests/fixtures/migrations/polymorphic_relationships_users_table_laravel6.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + $table->string('name', 400); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('users'); + } +} From d668b9992275df1ff2aae87ac23b5ee504f7d26d Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Tue, 19 May 2020 21:53:36 -0400 Subject: [PATCH 3/6] Single quote strings --- src/Generators/ModelGenerator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php index 99f32cde..01b88570 100644 --- a/src/Generators/ModelGenerator.php +++ b/src/Generators/ModelGenerator.php @@ -148,12 +148,12 @@ private function buildRelationships(Model $model) $class = Str::studly($class ?? $name); if ($type === 'morphTo') { - $relationship = sprintf("\$this->%s()", $type); + $relationship = sprintf('$this->%s()', $type); } elseif ($type === 'morphMany' || $type === 'morphOne') { $relation = Str::of($name)->lower()->singular() . 'able'; - $relationship = sprintf("\$this->%s(%s::class, '%s')", $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class, $relation); + $relationship = sprintf('$this->%s(%s::class, \'%s\')', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class, $relation); } else { - $relationship = sprintf("\$this->%s(%s::class)", $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class); + $relationship = sprintf('$this->%s(%s::class)', $type, '\\' . $model->fullyQualifiedNamespace() . '\\' . $class); } if ($type === 'morphTo') { From 59551d9e7b0229d300133d6015caede1ba64d179 Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Tue, 19 May 2020 21:55:32 -0400 Subject: [PATCH 4/6] Improve readability of conditional --- src/Generators/ModelGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Generators/ModelGenerator.php b/src/Generators/ModelGenerator.php index 01b88570..68e79b0b 100644 --- a/src/Generators/ModelGenerator.php +++ b/src/Generators/ModelGenerator.php @@ -159,7 +159,7 @@ private function buildRelationships(Model $model) if ($type === 'morphTo') { $method_name = Str::lower($class); } else { - $method_name = $type === 'hasMany' || $type === 'belongsToMany' || $type === 'morphMany' ? Str::plural($name) : $name; + $method_name = in_array($type, ['hasMany', 'belongsToMany', 'morphMany']) ? Str::plural($name) : $name; } $method = str_replace('DummyName', Str::camel($method_name), $template); $method = str_replace('null', $relationship, $method); From 3cdd4d66ef96cba78183afae19c17dc50a757497 Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Tue, 19 May 2020 21:57:01 -0400 Subject: [PATCH 5/6] PHPDoc alignment --- tests/Feature/Generator/ModelGeneratorTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Feature/Generator/ModelGeneratorTest.php b/tests/Feature/Generator/ModelGeneratorTest.php index 5861fd26..8eabecb6 100644 --- a/tests/Feature/Generator/ModelGeneratorTest.php +++ b/tests/Feature/Generator/ModelGeneratorTest.php @@ -93,8 +93,8 @@ public function output_generates_models($definition, $path, $model) } /** - * @test - */ + * @test + */ public function output_works_for_pascal_case_definition() { $this->files->expects('stub') @@ -165,8 +165,8 @@ public function output_generates_relationships() } /** - * @test - */ + * @test + */ public function output_generates_polymorphic_relationships() { $this->files->expects('stub') @@ -371,8 +371,8 @@ public function output_generates_models_with_guarded_property_when_config_option } /** - * @test - */ + * @test + */ public function output_generates_models_with_custom_namespace_correctly() { $definition = 'definitions/custom-models-namespace.bp'; From 296cb2418498c2658af75f4c8dea2f5b97525723 Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Tue, 19 May 2020 21:58:13 -0400 Subject: [PATCH 6/6] PHPDoc alignment --- tests/Feature/Lexers/ModelLexerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Feature/Lexers/ModelLexerTest.php b/tests/Feature/Lexers/ModelLexerTest.php index 663f0192..428fbc58 100644 --- a/tests/Feature/Lexers/ModelLexerTest.php +++ b/tests/Feature/Lexers/ModelLexerTest.php @@ -500,8 +500,8 @@ public function it_stores_relationships() } /** - * @test - */ + * @test + */ public function it_enables_morphable_and_set_its_reference() { $tokens = [