From 6559ad9853c8389d3bd26b5807da5cf717c4b1c6 Mon Sep 17 00:00:00 2001 From: Ali Sasani Date: Sun, 19 Oct 2025 00:09:16 +0330 Subject: [PATCH 1/5] [feat] add relation handler for union type --- src/Support/SelectFields.php | 93 ++++++++++++++++++++++++++++++++---- src/Support/UnionType.php | 4 ++ 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index 661cb647..3614b089 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -207,14 +207,29 @@ protected static function handleFields( static::addAlwaysFields($fieldObject, $field, $parentTable, true); - $with[$relationsKey] = static::getSelectableFieldsAndRelations( - $queryArgs, - $field, - $newParentType, - $customQuery, - false, - $ctx - ); + // Check if this is a MorphTo relation + if (is_a($relation, MorphTo::class)) { + // For MorphTo relations, we need to handle them specially + // because they can have different models, and we need to eager load based on the query + static::handleMorphToRelation( + $queryArgs, + $field, + $with, + $ctx, + $fieldObject, + $key, + $customQuery + ); + } else { + $with[$relationsKey] = static::getSelectableFieldsAndRelations( + $queryArgs, + $field, + $newParentType, + $customQuery, + false, + $ctx + ); + } } elseif (is_a($parentTypeUnwrapped, \GraphQL\Type\Definition\InterfaceType::class)) { static::handleInterfaceFields( $queryArgs, @@ -518,6 +533,68 @@ function (GraphqlType $type) use ($query) { }; } + /** + * Handle MorphTo relations + * @param mixed $ctx + */ + protected static function handleMorphToRelation( + array $queryArgs, + array $field, + array &$with, + $ctx, + FieldDefinition $fieldObject, + string $key, + ?Closure $customQuery + ): void { + $relationsKey = Arr::get($fieldObject->config, 'alias', $key); + + /* @var GraphqlType $fieldType */ + $fieldType = $fieldObject->config['type']; + + if ($fieldType instanceof WrappingType) { + $fieldType = $fieldType->getInnermostType(); + } + + /** @var UnionType $union */ + $union = $fieldType; + + $relationNames = (isset($union->config['relationName']) && \is_callable($union->config['relationName'])) + ? $union->config['relationName']() + : null; + $with[$relationsKey] = function (MorphTo $relation) use ($queryArgs, $field, $union, $relationNames, $customQuery, $ctx): void { + $morphRelation = []; + + foreach ($union->getTypes() as $unionType) { + // Get the model class name for the morph type + if (isset($unionType->config['model'])) { + $modelClass = $unionType->config['model']; + } else { + // Fallback to type name if no model is configured + $modelClass = $relationNames[$unionType->name()] ?? $unionType->name(); + } + + /** @var callable $callable */ + $callable = static::getSelectableFieldsAndRelations( + $queryArgs, + $field, + $unionType, + $customQuery, + false, + $ctx + ); + + $morphRelation[$modelClass] = $callable; + + } + + if (!empty($morphRelation)) { + $relation->morphWith($morphRelation); + } + }; + + + } + public function getSelect(): array { return $this->select; diff --git a/src/Support/UnionType.php b/src/Support/UnionType.php index 7f13fe8b..955c1ab8 100644 --- a/src/Support/UnionType.php +++ b/src/Support/UnionType.php @@ -30,6 +30,10 @@ public function getAttributes(): array $attributes['resolveType'] = [$this, 'resolveType']; } + if (method_exists($this, 'relationName')) { + $attributes['relationName'] = [$this, 'relationName']; + } + return $attributes; } From b73ec76a62db4f24a93128b748f7ab0e31609070 Mon Sep 17 00:00:00 2001 From: Ali Sasani Date: Sat, 25 Oct 2025 01:14:56 +0330 Subject: [PATCH 2/5] [feat] change method WithMorph to constrain on union types --- src/Support/SelectFields.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index 3614b089..fac9e078 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -588,7 +588,7 @@ protected static function handleMorphToRelation( } if (!empty($morphRelation)) { - $relation->morphWith($morphRelation); + $relation->constrain($morphRelation); } }; From 38f71d77a3ada38945d11f8408d2867f98e05c00 Mon Sep 17 00:00:00 2001 From: Ali Sasani Date: Sat, 25 Oct 2025 01:16:48 +0330 Subject: [PATCH 3/5] [test] add test to eager load union Types --- .../UnionMorphTests/CommentType.php | 38 ++++ .../UnionMorphTests/CommentableUnionType.php | 49 +++++ .../UnionMorphTests/CommentsQuery.php | 40 +++++ .../SelectFields/UnionMorphTests/FileType.php | 35 ++++ .../UnionMorphTests/FolderType.php | 28 +++ .../SelectFields/UnionMorphTests/PostType.php | 33 ++++ .../UnionMorphTests/ProductType.php | 35 ++++ .../UnionMorphTests/UnionMorphTest.php | 169 ++++++++++++++++++ tests/Support/Models/Comment.php | 18 +- tests/Support/Models/File.php | 38 ++++ tests/Support/Models/Folder.php | 32 ++++ tests/Support/Models/Post.php | 12 ++ tests/Support/Models/Product.php | 57 ++++++ .../database/factories/FileFactory.php | 21 +++ .../database/factories/FolderFactory.php | 19 ++ .../database/factories/ProductFactory.php | 23 +++ .../migrations/____comments_table.php | 3 +- .../database/migrations/____files_table.php | 24 +++ .../database/migrations/____folders_table.php | 22 +++ .../database/migrations/____posts_table.php | 1 + .../migrations/____products_table.php | 22 +++ 21 files changed, 712 insertions(+), 7 deletions(-) create mode 100644 tests/Database/SelectFields/UnionMorphTests/CommentType.php create mode 100644 tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php create mode 100644 tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php create mode 100644 tests/Database/SelectFields/UnionMorphTests/FileType.php create mode 100644 tests/Database/SelectFields/UnionMorphTests/FolderType.php create mode 100644 tests/Database/SelectFields/UnionMorphTests/PostType.php create mode 100644 tests/Database/SelectFields/UnionMorphTests/ProductType.php create mode 100644 tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php create mode 100644 tests/Support/Models/File.php create mode 100644 tests/Support/Models/Folder.php create mode 100644 tests/Support/Models/Product.php create mode 100644 tests/Support/database/factories/FileFactory.php create mode 100644 tests/Support/database/factories/FolderFactory.php create mode 100644 tests/Support/database/factories/ProductFactory.php create mode 100644 tests/Support/database/migrations/____files_table.php create mode 100644 tests/Support/database/migrations/____folders_table.php create mode 100644 tests/Support/database/migrations/____products_table.php diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentType.php b/tests/Database/SelectFields/UnionMorphTests/CommentType.php new file mode 100644 index 00000000..be8327b3 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/CommentType.php @@ -0,0 +1,38 @@ + 'Comment', + 'model' => Comment::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'title' => [ + 'type' => Type::nonNull(Type::string()), + ], + 'body' => [ + 'type' => Type::string(), + ], + 'file' => [ + 'type' => GraphQL::type('File'), + ], + 'commentable' => [ + 'type' => GraphQL::type('CommentableUnion'), + ], + ]; + } +} \ No newline at end of file diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php b/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php new file mode 100644 index 00000000..7ce0fdad --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php @@ -0,0 +1,49 @@ + 'CommentableUnion', + ]; + + public function types(): array + { + return [ + GraphQL::type('Post'), + GraphQL::type('Product'), + ]; + } + + public function relationName(): array + { + return [ + Post::class => 'post', + Product::class => 'product', + ]; + } + + /** + * @param object $value + */ + public function resolveType($value): ?GraphqlType + { + if ($value instanceof Post) { + return GraphQL::type('Post'); + } + if ($value instanceof Product) { + return GraphQL::type('Product'); + } + return null; + } +} + + diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php b/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php new file mode 100644 index 00000000..1a9252a9 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php @@ -0,0 +1,40 @@ + 'comments', + ]; + + public function type(): Type + { + return Type::listOf(GraphQL::type('Comment')); + } + + public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) + { + /** @var SelectFields $selectFields */ + $selectFields = $getSelectFields(); + + /** @var Comment[] $comments */ + $comments = Comment::query() + ->select($selectFields->getSelect()) + ->with($selectFields->getRelations()) + ->get(); + + return $comments; + } +} + + diff --git a/tests/Database/SelectFields/UnionMorphTests/FileType.php b/tests/Database/SelectFields/UnionMorphTests/FileType.php new file mode 100644 index 00000000..2d60d95b --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/FileType.php @@ -0,0 +1,35 @@ + 'File', + 'model' => File::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'name' => [ + 'type' => Type::string(), + ], + 'path' => [ + 'type' => Type::string(), + ], + 'folder' => [ + 'type' => GraphQL::type('Folder'), + ], + ]; + } +} \ No newline at end of file diff --git a/tests/Database/SelectFields/UnionMorphTests/FolderType.php b/tests/Database/SelectFields/UnionMorphTests/FolderType.php new file mode 100644 index 00000000..e1cb7839 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/FolderType.php @@ -0,0 +1,28 @@ + 'Folder', + 'model' => Folder::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'name' => [ + 'type' => Type::string(), + ], + ]; + } +} \ No newline at end of file diff --git a/tests/Database/SelectFields/UnionMorphTests/PostType.php b/tests/Database/SelectFields/UnionMorphTests/PostType.php new file mode 100644 index 00000000..4f639616 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/PostType.php @@ -0,0 +1,33 @@ + 'Post', + 'model' => Post::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'title' => [ + 'type' => Type::string(), + ], + 'file' => [ + 'type' => GraphQL::type('File'), + ], + ]; + } +} + diff --git a/tests/Database/SelectFields/UnionMorphTests/ProductType.php b/tests/Database/SelectFields/UnionMorphTests/ProductType.php new file mode 100644 index 00000000..d04bd336 --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/ProductType.php @@ -0,0 +1,35 @@ + 'Product', + 'model' => Product::class, + ]; + + public function fields(): array + { + return [ + 'id' => [ + 'type' => Type::nonNull(Type::id()), + ], + 'name' => [ + 'type' => Type::string(), + ], + 'price' => [ + 'type' => Type::float(), + ], + 'file' => [ + 'type' => GraphQL::type('File'), + ], + ]; + } +} diff --git a/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php b/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php new file mode 100644 index 00000000..939509bf --- /dev/null +++ b/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php @@ -0,0 +1,169 @@ +set('graphql.schemas.default', [ + 'query' => [ + CommentsQuery::class, + ], + ]); + + $app['config']->set('graphql.types', [ + FileType::class, + FolderType::class, + PostType::class, + ProductType::class, + CommentableUnionType::class, + CommentType::class, + ]); + + Model::preventLazyLoading(); + } + + public function testUnionMorphEagerLoading(): void + { + + // Create 2 products with different files and product folder + /** @var Folder $productFolder */ + $productFolder = Folder::factory()->create(['name' => 'product']); + + /** @var File $productFile1 */ + $productFile1 = File::factory()->create(['folder_id' => $productFolder->id, 'name' => 'product_file_1.pdf']); + + /** @var File $productFile2 */ + $productFile2 = File::factory()->create(['folder_id' => $productFolder->id, 'name' => 'product_file_2.jpg']); + + /** @var Product $product1 */ + $product1 = Product::factory()->create(['file_id' => $productFile1->id, 'name' => 'Product 1']); + + /** @var Product $product2 */ + $product2 = Product::factory()->create(['file_id' => $productFile2->id, 'name' => 'Product 2']); + + // Create 2 posts with different files and post folder + /** @var User $user */ + $user = User::factory()->create(); + + /** @var Folder $postFolder */ + $postFolder = Folder::factory()->create(['name' => 'post']); + + /** @var File $postFile1 */ + $postFile1 = File::factory()->create(['folder_id' => $postFolder->id, 'name' => 'post_file_1.docx']); + + /** @var File $postFile2 */ + $postFile2 = File::factory()->create(['folder_id' => $postFolder->id, 'name' => 'post_file_2.png']); + + /** @var Post $post1 */ + $post1 = Post::factory()->create(['user_id' => $user->id, 'file_id' => $postFile1->id, 'title' => 'Post 1']); + + /** @var Post $post2 */ + $post2 = Post::factory()->create(['user_id' => $user->id, 'file_id' => $postFile2->id, 'title' => 'Post 2']); + + // Create comments for all products and posts using morphable relationships + /** @var Comment $product1Comment */ + $product1Comment = Comment::factory()->create(); + $product1->commentableComments()->save($product1Comment); + + /** @var Comment $product2Comment */ + $product2Comment = Comment::factory()->create(); + $product2->commentableComments()->save($product2Comment); + + /** @var Comment $post1Comment */ + $post1Comment = Comment::factory()->create(); + $post1->commentableComments()->save($post1Comment); + + /** @var Comment $post2Comment */ + $post2Comment = Comment::factory()->create(); + $post2->commentableComments()->save($post2Comment); + + $query = + /** @lang GraphQL */ <<<'GRAQPHQL' +{ + comments { + id + title + body + commentable { + ... on Post { + id + title + file { + id + name + path + folder { + id + name + } + } + } + ... on Product { + id + name + price + file { + id + name + path + folder { + id + name + } + } + } + } + } +} +GRAQPHQL; + + $result = $this->httpGraphql($query); + + self::assertArrayHasKey('data', $result); + self::assertCount(4, $result['data']['comments']); + + // Verify comments data structure + $comments = $result['data']['comments']; + + // Check that we have 4 comments (2 for products, 2 for posts) + self::assertCount(4, $comments); + + // Verify commentable field contains the correct data + foreach ($comments as $comment) { + self::assertArrayHasKey('id', $comment); + self::assertArrayHasKey('title', $comment); + self::assertArrayHasKey('commentable', $comment); + + $commentable = $comment['commentable']; + if (isset($commentable['title'])) { + // This is a Post comment + self::assertArrayHasKey('file', $commentable); + if (isset($commentable['file'])) { + self::assertArrayHasKey('folder', $commentable['file']); + } + } elseif (isset($commentable['name'])) { + // This is a Product comment + self::assertArrayHasKey('file', $commentable); + if (isset($commentable['file'])) { + self::assertArrayHasKey('folder', $commentable['file']); + } + } + } + } +} diff --git a/tests/Support/Models/Comment.php b/tests/Support/Models/Comment.php index ef66191e..a798cd9c 100644 --- a/tests/Support/Models/Comment.php +++ b/tests/Support/Models/Comment.php @@ -9,15 +9,16 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Relations\MorphTo; use Rebing\GraphQL\Tests\Support\database\factories\CommentFactory; /** - * @property int $id - * @property int $post_id - * @property string $title - * @property string|null $body - * @property bool $flag - * @property-read Post $post + * @property int $id + * @property int $post_id + * @property string $title + * @property string|null $body + * @property bool $flag + * @property-read Post $post * @property-read Collection|Like[] $likes */ class Comment extends Model @@ -34,6 +35,11 @@ public function likes(): MorphMany return $this->morphMany(Like::class, 'likable'); } + public function commentable(): MorphTo + { + return $this->morphTo(); + } + protected static function newFactory(): Factory { return CommentFactory::new(); diff --git a/tests/Support/Models/File.php b/tests/Support/Models/File.php new file mode 100644 index 00000000..d876360a --- /dev/null +++ b/tests/Support/Models/File.php @@ -0,0 +1,38 @@ +belongsTo(Folder::class); + } + +} diff --git a/tests/Support/Models/Folder.php b/tests/Support/Models/Folder.php new file mode 100644 index 00000000..37a00e75 --- /dev/null +++ b/tests/Support/Models/Folder.php @@ -0,0 +1,32 @@ +hasMany(File::class); + } + + protected static function newFactory(): Factory + { + return FolderFactory::new(); + } +} diff --git a/tests/Support/Models/Post.php b/tests/Support/Models/Post.php index 538823ba..19c05ee5 100644 --- a/tests/Support/Models/Post.php +++ b/tests/Support/Models/Post.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Support\Carbon; use Rebing\GraphQL\Tests\Support\database\factories\PostFactory; +use Rebing\GraphQL\Tests\Support\Models\File; /** * @property int $id @@ -24,6 +25,7 @@ * @property bool $is_published * @property-read Collection|Comment[] $comments * @property-read Collection|Like[] $likes + * @property-read Collection|File[] $files */ class Post extends Model { @@ -49,6 +51,16 @@ public function comments(): HasMany return $this->hasMany(Comment::class)->orderBy('comments.id'); } + public function commentableComments(): MorphMany + { + return $this->morphMany(Comment::class, 'commentable'); + } + + public function file(): BelongsTo + { + return $this->belongsTo(File::class, 'file_id'); + } + public function likes(): MorphMany { return $this->morphMany(Like::class, 'likable'); diff --git a/tests/Support/Models/Product.php b/tests/Support/Models/Product.php new file mode 100644 index 00000000..a86c98fa --- /dev/null +++ b/tests/Support/Models/Product.php @@ -0,0 +1,57 @@ +belongsTo(File::class, 'file_id'); + } + + public function commentableComments(): MorphMany + { + return $this->morphMany(Comment::class, 'commentable'); + } + + public function getIsPublishedAttribute(): bool + { + $publishedAt = $this->published_at; + + return null !== $publishedAt; + } + + protected static function newFactory(): Factory + { + return ProductFactory::new(); + } +} diff --git a/tests/Support/database/factories/FileFactory.php b/tests/Support/database/factories/FileFactory.php new file mode 100644 index 00000000..86a15851 --- /dev/null +++ b/tests/Support/database/factories/FileFactory.php @@ -0,0 +1,21 @@ + $this->faker->word() . '.txt', + 'path' => '/files/' . $this->faker->word(), + 'folder_id' => null, + ]; + } +} diff --git a/tests/Support/database/factories/FolderFactory.php b/tests/Support/database/factories/FolderFactory.php new file mode 100644 index 00000000..240c5488 --- /dev/null +++ b/tests/Support/database/factories/FolderFactory.php @@ -0,0 +1,19 @@ + $this->faker->word(), + ]; + } +} diff --git a/tests/Support/database/factories/ProductFactory.php b/tests/Support/database/factories/ProductFactory.php new file mode 100644 index 00000000..d70c88d6 --- /dev/null +++ b/tests/Support/database/factories/ProductFactory.php @@ -0,0 +1,23 @@ + + */ +class ProductFactory extends Factory +{ + protected $model = Product::class; + + public function definition(): array + { + return [ + 'name' => fake()->words(3, true), + 'price' => fake()->randomFloat(2, 10, 1000), + ]; + } +} diff --git a/tests/Support/database/migrations/____comments_table.php b/tests/Support/database/migrations/____comments_table.php index 959f35dc..b1b65c9c 100644 --- a/tests/Support/database/migrations/____comments_table.php +++ b/tests/Support/database/migrations/____comments_table.php @@ -12,7 +12,8 @@ public function up(): void { Schema::create('comments', function (Blueprint $table): void { $table->increments('id'); - $table->integer('post_id'); + $table->integer('post_id')->nullable(); + $table->nullableMorphs('commentable'); $table->string('title'); $table->string('body')->nullable(); $table->boolean('flag')->default('false'); diff --git a/tests/Support/database/migrations/____files_table.php b/tests/Support/database/migrations/____files_table.php new file mode 100644 index 00000000..5760b407 --- /dev/null +++ b/tests/Support/database/migrations/____files_table.php @@ -0,0 +1,24 @@ +increments('id'); + $table->unsignedInteger('folder_id')->nullable(); + $table->string('name'); + $table->string('path'); + $table->unsignedInteger('size')->default(0); + $table->timestamps(); + }); + } +} + + diff --git a/tests/Support/database/migrations/____folders_table.php b/tests/Support/database/migrations/____folders_table.php new file mode 100644 index 00000000..0fd3e15c --- /dev/null +++ b/tests/Support/database/migrations/____folders_table.php @@ -0,0 +1,22 @@ +increments('id'); + $table->string(column: 'name'); + $table->unsignedInteger('parent_id')->nullable(); + $table->timestamps(); + }); + } +} + + diff --git a/tests/Support/database/migrations/____posts_table.php b/tests/Support/database/migrations/____posts_table.php index 8c5ad33d..9a57210c 100644 --- a/tests/Support/database/migrations/____posts_table.php +++ b/tests/Support/database/migrations/____posts_table.php @@ -17,6 +17,7 @@ public function up(): void $table->integer('user_id')->nullable(); $table->text('properties')->nullable(); $table->boolean('flag')->default('false'); + $table->unsignedInteger('file_id')->nullable(); $table->dateTime('published_at')->nullable(); $table->timestamps(); }); diff --git a/tests/Support/database/migrations/____products_table.php b/tests/Support/database/migrations/____products_table.php new file mode 100644 index 00000000..3a846328 --- /dev/null +++ b/tests/Support/database/migrations/____products_table.php @@ -0,0 +1,22 @@ +increments('id'); + $table->string('name'); + $table->decimal('price', 10, 2)->nullable(); + $table->unsignedInteger('file_id')->nullable(); + $table->dateTime(column: 'published_at')->nullable(); + $table->timestamps(); + }); + } +} From a05e616f1b5d9250bf009374a68175ccd7618e7e Mon Sep 17 00:00:00 2001 From: Ali Sasani Date: Sat, 25 Oct 2025 01:40:34 +0330 Subject: [PATCH 4/5] [test] update test, check queries --- .../UnionMorphTests/UnionMorphTest.php | 127 ++++++++++++++---- 1 file changed, 98 insertions(+), 29 deletions(-) diff --git a/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php b/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php index 939509bf..65225bf7 100644 --- a/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php +++ b/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php @@ -12,10 +12,13 @@ use Rebing\GraphQL\Tests\Support\Models\Post; use Rebing\GraphQL\Tests\Support\Models\Product; use Rebing\GraphQL\Tests\Support\Models\User; +use Rebing\GraphQL\Tests\Support\Traits\SqlAssertionTrait; use Rebing\GraphQL\Tests\TestCaseDatabase; class UnionMorphTest extends TestCaseDatabase { + use SqlAssertionTrait; + protected function getEnvironmentSetUp($app): void { parent::getEnvironmentSetUp($app); @@ -133,37 +136,103 @@ public function testUnionMorphEagerLoading(): void } GRAQPHQL; + $this->sqlCounterReset(); + $result = $this->httpGraphql($query); - self::assertArrayHasKey('data', $result); - self::assertCount(4, $result['data']['comments']); + $this->assertSqlQueries( + /** @lang SQL */ <<<'SQL' +select "comments"."id", "comments"."title", "comments"."body", "comments"."commentable_id", "comments"."commentable_type" from "comments"; +select "products"."id", "products"."name", "products"."price", "products"."file_id" from "products" where "products"."id" in (?, ?); +select "files"."id", "files"."name", "files"."path", "files"."folder_id" from "files" where "files"."id" in (?, ?); +select "folders"."id", "folders"."name" from "folders" where "folders"."id" in (?); +select "posts"."id", "posts"."file_id", "posts"."title" from "posts" where "posts"."id" in (?, ?); +select "files"."id", "files"."name", "files"."path", "files"."folder_id" from "files" where "files"."id" in (?, ?); +select "folders"."id", "folders"."name" from "folders" where "folders"."id" in (?); +SQL + ); - // Verify comments data structure - $comments = $result['data']['comments']; - - // Check that we have 4 comments (2 for products, 2 for posts) - self::assertCount(4, $comments); - - // Verify commentable field contains the correct data - foreach ($comments as $comment) { - self::assertArrayHasKey('id', $comment); - self::assertArrayHasKey('title', $comment); - self::assertArrayHasKey('commentable', $comment); - - $commentable = $comment['commentable']; - if (isset($commentable['title'])) { - // This is a Post comment - self::assertArrayHasKey('file', $commentable); - if (isset($commentable['file'])) { - self::assertArrayHasKey('folder', $commentable['file']); - } - } elseif (isset($commentable['name'])) { - // This is a Product comment - self::assertArrayHasKey('file', $commentable); - if (isset($commentable['file'])) { - self::assertArrayHasKey('folder', $commentable['file']); - } - } - } + $expectedResult = [ + 'data' => [ + 'comments' => [ + [ + 'id' => (string) $product1Comment->id, + 'title' => $product1Comment->title, + 'body' => $product1Comment->body, + 'commentable' => [ + 'id' => (string) $product1->id, + 'name' => $product1->name, + 'price' => $product1->price, + 'file' => [ + 'id' => (string) $productFile1->id, + 'name' => $productFile1->name, + 'path' => $productFile1->path, + 'folder' => [ + 'id' => (string) $productFolder->id, + 'name' => $productFolder->name, + ], + ], + ], + ], + [ + 'id' => (string) $product2Comment->id, + 'title' => $product2Comment->title, + 'body' => $product2Comment->body, + 'commentable' => [ + 'id' => (string) $product2->id, + 'name' => $product2->name, + 'price' => $product2->price, + 'file' => [ + 'id' => (string) $productFile2->id, + 'name' => $productFile2->name, + 'path' => $productFile2->path, + 'folder' => [ + 'id' => (string) $productFolder->id, + 'name' => $productFolder->name, + ], + ], + ], + ], + [ + 'id' => (string) $post1Comment->id, + 'title' => $post1Comment->title, + 'body' => $post1Comment->body, + 'commentable' => [ + 'id' => (string) $post1->id, + 'title' => $post1->title, + 'file' => [ + 'id' => (string) $postFile1->id, + 'name' => $postFile1->name, + 'path' => $postFile1->path, + 'folder' => [ + 'id' => (string) $postFolder->id, + 'name' => $postFolder->name, + ], + ], + ], + ], + [ + 'id' => (string) $post2Comment->id, + 'title' => $post2Comment->title, + 'body' => $post2Comment->body, + 'commentable' => [ + 'id' => (string) $post2->id, + 'title' => $post2->title, + 'file' => [ + 'id' => (string) $postFile2->id, + 'name' => $postFile2->name, + 'path' => $postFile2->path, + 'folder' => [ + 'id' => (string) $postFolder->id, + 'name' => $postFolder->name, + ], + ], + ], + ], + ], + ], + ]; + + self::assertSame($expectedResult, $result); } } From 3f3719f1c6154631c34bb37ff3f643855a6cd0d4 Mon Sep 17 00:00:00 2001 From: Ali Sasani Date: Sat, 25 Oct 2025 01:50:53 +0330 Subject: [PATCH 5/5] [style] run lint --- src/Support/SelectFields.php | 3 -- .../UnionMorphTests/CommentType.php | 2 +- .../UnionMorphTests/CommentableUnionType.php | 8 ++--- .../UnionMorphTests/CommentsQuery.php | 2 -- .../SelectFields/UnionMorphTests/FileType.php | 2 +- .../UnionMorphTests/FolderType.php | 2 +- .../SelectFields/UnionMorphTests/PostType.php | 1 - .../UnionMorphTests/UnionMorphTest.php | 34 +++++++++---------- tests/Support/Models/Comment.php | 10 +++--- tests/Support/Models/File.php | 2 -- tests/Support/Models/Post.php | 1 - tests/Support/Models/Product.php | 5 --- .../database/migrations/____files_table.php | 2 -- .../database/migrations/____folders_table.php | 2 -- 14 files changed, 28 insertions(+), 48 deletions(-) diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index fac9e078..98a6dde4 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -584,15 +584,12 @@ protected static function handleMorphToRelation( ); $morphRelation[$modelClass] = $callable; - } if (!empty($morphRelation)) { $relation->constrain($morphRelation); } }; - - } public function getSelect(): array diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentType.php b/tests/Database/SelectFields/UnionMorphTests/CommentType.php index be8327b3..80f96698 100644 --- a/tests/Database/SelectFields/UnionMorphTests/CommentType.php +++ b/tests/Database/SelectFields/UnionMorphTests/CommentType.php @@ -35,4 +35,4 @@ public function fields(): array ], ]; } -} \ No newline at end of file +} diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php b/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php index 7ce0fdad..ae3eb3c2 100644 --- a/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php +++ b/tests/Database/SelectFields/UnionMorphTests/CommentableUnionType.php @@ -26,7 +26,7 @@ public function types(): array public function relationName(): array { return [ - Post::class => 'post', + Post::class => 'post', Product::class => 'product', ]; } @@ -35,15 +35,15 @@ public function relationName(): array * @param object $value */ public function resolveType($value): ?GraphqlType - { + { if ($value instanceof Post) { return GraphQL::type('Post'); } + if ($value instanceof Product) { return GraphQL::type('Product'); } + return null; } } - - diff --git a/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php b/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php index 1a9252a9..70c97520 100644 --- a/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php +++ b/tests/Database/SelectFields/UnionMorphTests/CommentsQuery.php @@ -36,5 +36,3 @@ public function resolve($root, $args, $context, ResolveInfo $info, Closure $getS return $comments; } } - - diff --git a/tests/Database/SelectFields/UnionMorphTests/FileType.php b/tests/Database/SelectFields/UnionMorphTests/FileType.php index 2d60d95b..0cab9b86 100644 --- a/tests/Database/SelectFields/UnionMorphTests/FileType.php +++ b/tests/Database/SelectFields/UnionMorphTests/FileType.php @@ -32,4 +32,4 @@ public function fields(): array ], ]; } -} \ No newline at end of file +} diff --git a/tests/Database/SelectFields/UnionMorphTests/FolderType.php b/tests/Database/SelectFields/UnionMorphTests/FolderType.php index e1cb7839..aefdd7f3 100644 --- a/tests/Database/SelectFields/UnionMorphTests/FolderType.php +++ b/tests/Database/SelectFields/UnionMorphTests/FolderType.php @@ -25,4 +25,4 @@ public function fields(): array ], ]; } -} \ No newline at end of file +} diff --git a/tests/Database/SelectFields/UnionMorphTests/PostType.php b/tests/Database/SelectFields/UnionMorphTests/PostType.php index 4f639616..57df053d 100644 --- a/tests/Database/SelectFields/UnionMorphTests/PostType.php +++ b/tests/Database/SelectFields/UnionMorphTests/PostType.php @@ -30,4 +30,3 @@ public function fields(): array ]; } } - diff --git a/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php b/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php index 65225bf7..1f154ed4 100644 --- a/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php +++ b/tests/Database/SelectFields/UnionMorphTests/UnionMorphTest.php @@ -4,11 +4,9 @@ namespace Rebing\GraphQL\Tests\Database\SelectFields\UnionMorphTests; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\MorphTo; use Rebing\GraphQL\Tests\Support\Models\Comment; use Rebing\GraphQL\Tests\Support\Models\File; use Rebing\GraphQL\Tests\Support\Models\Folder; -use Rebing\GraphQL\Tests\Support\Models\Like; use Rebing\GraphQL\Tests\Support\Models\Post; use Rebing\GraphQL\Tests\Support\Models\Product; use Rebing\GraphQL\Tests\Support\Models\User; @@ -33,7 +31,7 @@ protected function getEnvironmentSetUp($app): void FileType::class, FolderType::class, PostType::class, - ProductType::class, + ProductType::class, CommentableUnionType::class, CommentType::class, ]); @@ -43,39 +41,38 @@ protected function getEnvironmentSetUp($app): void public function testUnionMorphEagerLoading(): void { - // Create 2 products with different files and product folder /** @var Folder $productFolder */ $productFolder = Folder::factory()->create(['name' => 'product']); - + /** @var File $productFile1 */ $productFile1 = File::factory()->create(['folder_id' => $productFolder->id, 'name' => 'product_file_1.pdf']); - + /** @var File $productFile2 */ $productFile2 = File::factory()->create(['folder_id' => $productFolder->id, 'name' => 'product_file_2.jpg']); - + /** @var Product $product1 */ $product1 = Product::factory()->create(['file_id' => $productFile1->id, 'name' => 'Product 1']); - + /** @var Product $product2 */ $product2 = Product::factory()->create(['file_id' => $productFile2->id, 'name' => 'Product 2']); // Create 2 posts with different files and post folder /** @var User $user */ $user = User::factory()->create(); - + /** @var Folder $postFolder */ $postFolder = Folder::factory()->create(['name' => 'post']); - + /** @var File $postFile1 */ $postFile1 = File::factory()->create(['folder_id' => $postFolder->id, 'name' => 'post_file_1.docx']); - + /** @var File $postFile2 */ $postFile2 = File::factory()->create(['folder_id' => $postFolder->id, 'name' => 'post_file_2.png']); - + /** @var Post $post1 */ $post1 = Post::factory()->create(['user_id' => $user->id, 'file_id' => $postFile1->id, 'title' => 'Post 1']); - + /** @var Post $post2 */ $post2 = Post::factory()->create(['user_id' => $user->id, 'file_id' => $postFile2->id, 'title' => 'Post 2']); @@ -83,21 +80,21 @@ public function testUnionMorphEagerLoading(): void /** @var Comment $product1Comment */ $product1Comment = Comment::factory()->create(); $product1->commentableComments()->save($product1Comment); - + /** @var Comment $product2Comment */ $product2Comment = Comment::factory()->create(); $product2->commentableComments()->save($product2Comment); - + /** @var Comment $post1Comment */ $post1Comment = Comment::factory()->create(); $post1->commentableComments()->save($post1Comment); - + /** @var Comment $post2Comment */ $post2Comment = Comment::factory()->create(); $post2->commentableComments()->save($post2Comment); $query = - /** @lang GraphQL */ <<<'GRAQPHQL' +/** @lang GraphQL */ <<<'GRAQPHQL' { comments { id @@ -141,7 +138,8 @@ public function testUnionMorphEagerLoading(): void $result = $this->httpGraphql($query); $this->assertSqlQueries( - /** @lang SQL */ <<<'SQL' + /** @lang SQL */ + <<<'SQL' select "comments"."id", "comments"."title", "comments"."body", "comments"."commentable_id", "comments"."commentable_type" from "comments"; select "products"."id", "products"."name", "products"."price", "products"."file_id" from "products" where "products"."id" in (?, ?); select "files"."id", "files"."name", "files"."path", "files"."folder_id" from "files" where "files"."id" in (?, ?); diff --git a/tests/Support/Models/Comment.php b/tests/Support/Models/Comment.php index a798cd9c..ace15198 100644 --- a/tests/Support/Models/Comment.php +++ b/tests/Support/Models/Comment.php @@ -13,11 +13,11 @@ use Rebing\GraphQL\Tests\Support\database\factories\CommentFactory; /** - * @property int $id - * @property int $post_id - * @property string $title - * @property string|null $body - * @property bool $flag + * @property int $id + * @property int $post_id + * @property string $title + * @property string|null $body + * @property bool $flag * @property-read Post $post * @property-read Collection|Like[] $likes */ diff --git a/tests/Support/Models/File.php b/tests/Support/Models/File.php index d876360a..3642ed5f 100644 --- a/tests/Support/Models/File.php +++ b/tests/Support/Models/File.php @@ -7,7 +7,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\MorphTo; use Rebing\GraphQL\Tests\Support\database\factories\FileFactory; /** @@ -34,5 +33,4 @@ public function folder(): BelongsTo { return $this->belongsTo(Folder::class); } - } diff --git a/tests/Support/Models/Post.php b/tests/Support/Models/Post.php index 19c05ee5..f5e77b62 100644 --- a/tests/Support/Models/Post.php +++ b/tests/Support/Models/Post.php @@ -12,7 +12,6 @@ use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Support\Carbon; use Rebing\GraphQL\Tests\Support\database\factories\PostFactory; -use Rebing\GraphQL\Tests\Support\Models\File; /** * @property int $id diff --git a/tests/Support/Models/Product.php b/tests/Support/Models/Product.php index a86c98fa..9bdb52ec 100644 --- a/tests/Support/Models/Product.php +++ b/tests/Support/Models/Product.php @@ -3,17 +3,13 @@ declare(strict_types = 1); namespace Rebing\GraphQL\Tests\Support\Models; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphMany; -use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Support\Carbon; use Rebing\GraphQL\Tests\Support\database\factories\ProductFactory; -use Rebing\GraphQL\Tests\Support\Models\Comment; -use Rebing\GraphQL\Tests\Support\Models\File; /** * @property int $id @@ -32,7 +28,6 @@ class Product extends Model 'published_at', ]; - public function file(): BelongsTo { return $this->belongsTo(File::class, 'file_id'); diff --git a/tests/Support/database/migrations/____files_table.php b/tests/Support/database/migrations/____files_table.php index 5760b407..84fa78e5 100644 --- a/tests/Support/database/migrations/____files_table.php +++ b/tests/Support/database/migrations/____files_table.php @@ -20,5 +20,3 @@ public function up(): void }); } } - - diff --git a/tests/Support/database/migrations/____folders_table.php b/tests/Support/database/migrations/____folders_table.php index 0fd3e15c..880c8d5e 100644 --- a/tests/Support/database/migrations/____folders_table.php +++ b/tests/Support/database/migrations/____folders_table.php @@ -18,5 +18,3 @@ public function up(): void }); } } - -