From a00e6127e2ad97b778a9ce4aadd5dc76c28f9cb9 Mon Sep 17 00:00:00 2001 From: Edwin Dayot Date: Tue, 8 Oct 2019 14:45:16 +0200 Subject: [PATCH 1/3] Fixed the custom query not being handled by interface's relations --- src/Support/InterfaceType.php | 26 +- src/Support/SelectFields.php | 260 ++++++++++++------ .../InterfaceTests/CommentType.php | 8 +- .../InterfaceTests/InterfaceTest.php | 244 +++++++++++++++- .../InterfaceTests/LikableInterfaceType.php | 16 +- .../SelectFields/InterfaceTests/PostType.php | 10 +- 6 files changed, 464 insertions(+), 100 deletions(-) diff --git a/src/Support/InterfaceType.php b/src/Support/InterfaceType.php index bf2a05b8..da41988f 100644 --- a/src/Support/InterfaceType.php +++ b/src/Support/InterfaceType.php @@ -25,6 +25,21 @@ protected function getTypeResolver(): ?Closure }; } + protected function getTypesResolver(): ?Closure + { + if (! method_exists($this, 'types')) { + return null; + } + + $resolver = [$this, 'types']; + + return function () use ($resolver): array { + $args = func_get_args(); + + return call_user_func_array($resolver, $args); + }; + } + /** * Get the attributes from the container. * @@ -34,9 +49,14 @@ public function getAttributes(): array { $attributes = parent::getAttributes(); - $resolver = $this->getTypeResolver(); - if ($resolver) { - $attributes['resolveType'] = $resolver; + $resolverType = $this->getTypeResolver(); + if ($resolverType) { + $attributes['resolveType'] = $resolverType; + } + + $resolverTypes = $this->getTypesResolver(); + if ($resolverTypes) { + $attributes['types'] = $resolverTypes; } return $attributes; diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index 56b821df..9c50b47b 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -17,7 +17,9 @@ use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Query\Expression; +use Illuminate\Support\Arr; use RuntimeException; class SelectFields @@ -114,6 +116,16 @@ public static function getSelectableFieldsAndRelations( }; } + private static function getTableNameFromParentType(GraphqlType $parentType): ?string + { + return isset($parentType->config['model']) ? app($parentType->config['model'])->getTable() : null; + } + + private static function getPrimaryKeyFromParentType(GraphqlType $parentType): ?string + { + return isset($parentType->config['model']) ? app($parentType->config['model'])->getKeyName() : null; + } + /** * Get the selects and withs from the given fields * and recurse if necessary. @@ -159,6 +171,12 @@ protected static function handleFields( continue; } + $parentTypeUnwrapped = $parentType; + + if ($parentTypeUnwrapped instanceof WrappingType) { + $parentTypeUnwrapped = $parentTypeUnwrapped->getWrappedType(true); + } + // First check if the field is even accessible $canSelect = self::validateField($fieldObject, $queryArgs); if ($canSelect === true) { @@ -170,7 +188,10 @@ protected static function handleFields( // Pagination if (is_a($parentType, config('graphql.pagination_type', PaginationType::class))) { - self::handleFields($queryArgs, $field, $fieldObject->config['type']->getWrappedType(), $select, $with, $ctx); + /* @var GraphqlType $fieldType */ + $fieldType = $fieldObject->config['type']; + self::handleFields($queryArgs, $field, $fieldType->getWrappedType(), $select, + $with, $ctx); } // With elseif (is_array($field['fields']) && $queryable) { @@ -180,53 +201,18 @@ protected static function handleFields( $relationsKey = $fieldObject->config['alias'] ?? $key; $relation = call_user_func([app($parentType->config['model']), $relationsKey]); - // Add the foreign key here, if it's a 'belongsTo'/'belongsToMany' relation - if (method_exists($relation, 'getForeignKey')) { - $foreignKey = $relation->getForeignKey(); - } elseif (method_exists($relation, 'getQualifiedForeignPivotKeyName')) { - $foreignKey = $relation->getQualifiedForeignPivotKeyName(); - } else { - $foreignKey = $relation->getQualifiedForeignKeyName(); - } - - $foreignKey = $parentTable ? ($parentTable.'.'.preg_replace('/^'.preg_quote($parentTable, '/').'\./', '', $foreignKey)) : $foreignKey; - - if (is_a($relation, MorphTo::class)) { - $foreignKeyType = $relation->getMorphType(); - $foreignKeyType = $parentTable ? ($parentTable.'.'.$foreignKeyType) : $foreignKeyType; - - if (! in_array($foreignKey, $select)) { - $select[] = $foreignKey; - } - - if (! in_array($foreignKeyType, $select)) { - $select[] = $foreignKeyType; - } - } elseif (is_a($relation, BelongsTo::class)) { - if (! in_array($foreignKey, $select)) { - $select[] = $foreignKey; - } - } - // If 'HasMany', then add it in the 'with' - elseif ((is_a($relation, HasMany::class) || is_a($relation, MorphMany::class) || is_a($relation, HasOne::class) || is_a($relation, MorphOne::class)) - && ! array_key_exists($foreignKey, $field)) { - $segments = explode('.', $foreignKey); - $foreignKey = end($segments); - if (! array_key_exists($foreignKey, $field)) { - $field['fields'][$foreignKey] = self::ALWAYS_RELATION_KEY; - } - - if (is_a($relation, MorphMany::class) || is_a($relation, MorphOne::class)) { - $field['fields'][$relation->getMorphType()] = self::ALWAYS_RELATION_KEY; - } - } + self::handleRelation($select, $relation, $parentTable, $field); // New parent type, which is the relation $newParentType = $parentType->getField($key)->config['type']; self::addAlwaysFields($fieldObject, $field, $parentTable, true); - $with[$relationsKey] = self::getSelectableFieldsAndRelations($queryArgs, $field, $newParentType, $customQuery, false, $ctx); + $with[$relationsKey] = self::getSelectableFieldsAndRelations($queryArgs, $field, $newParentType, + $customQuery, false, $ctx); + } elseif (is_a($parentTypeUnwrapped, \GraphQL\Type\Definition\InterfaceType::class)) { + self::handleInterfaceFields($queryArgs, $field, $parentTypeUnwrapped, $select, $with, $ctx, + $fieldObject, $key, $customQuery); } else { self::handleFields($queryArgs, $field, $fieldObject->config['type'], $select, $with, $ctx); } @@ -261,6 +247,40 @@ protected static function handleFields( } } + private static function isMongodbInstance(GraphqlType $parentType): bool + { + $mongoType = 'Jenssegers\Mongodb\Eloquent\Model'; + + return isset($parentType->config['model']) ? app($parentType->config['model']) instanceof $mongoType : false; + } + + /** + * @param string|Expression $field + * @param array $select Passed by reference, adds further fields to select + * @param string|null $parentTable + * @param bool $forRelation + */ + protected static function addFieldToSelect($field, array &$select, ?string $parentTable, bool $forRelation): void + { + if ($field instanceof Expression) { + $select[] = $field; + + return; + } + + if ($forRelation && ! array_key_exists($field, $select['fields'])) { + $select['fields'][$field] = [ + 'args' => [], + 'fields' => true, + ]; + } elseif (! $forRelation && ! in_array($field, $select)) { + $field = $parentTable ? ($parentTable.'.'.$field) : $field; + if (! in_array($field, $select)) { + $select[] = $field; + } + } + } + /** * Check the privacy status, if it's given. * @@ -325,6 +345,52 @@ private static function isQueryable(array $fieldObject): bool return ($fieldObject['is_relation'] ?? true) === true; } + /** + * @param array $select + * @param mixed $relation + * @param string|null $parentTable + * @param array $field + */ + private static function handleRelation(array &$select, $relation, ?string $parentTable, &$field): void + { + // Add the foreign key here, if it's a 'belongsTo'/'belongsToMany' relation + if (method_exists($relation, 'getForeignKey')) { + $foreignKey = $relation->getForeignKey(); + } elseif (method_exists($relation, 'getQualifiedForeignPivotKeyName')) { + $foreignKey = $relation->getQualifiedForeignPivotKeyName(); + } else { + $foreignKey = $relation->getQualifiedForeignKeyName(); + } + $foreignKey = $parentTable ? ($parentTable.'.'.preg_replace('/^'.preg_quote($parentTable, '/').'\./', + '', $foreignKey)) : $foreignKey; + if (is_a($relation, MorphTo::class)) { + $foreignKeyType = $relation->getMorphType(); + $foreignKeyType = $parentTable ? ($parentTable.'.'.$foreignKeyType) : $foreignKeyType; + if (! in_array($foreignKey, $select)) { + $select[] = $foreignKey; + } + if (! in_array($foreignKeyType, $select)) { + $select[] = $foreignKeyType; + } + } elseif (is_a($relation, BelongsTo::class)) { + if (! in_array($foreignKey, $select)) { + $select[] = $foreignKey; + } + } // If 'HasMany', then add it in the 'with' + elseif ((is_a($relation, HasMany::class) || is_a($relation, MorphMany::class) || is_a($relation, + HasOne::class) || is_a($relation, MorphOne::class)) + && ! array_key_exists($foreignKey, $field)) { + $segments = explode('.', $foreignKey); + $foreignKey = end($segments); + if (! array_key_exists($foreignKey, $field)) { + $field['fields'][$foreignKey] = self::ALWAYS_RELATION_KEY; + } + if (is_a($relation, MorphMany::class) || is_a($relation, MorphOne::class)) { + $field['fields'][$relation->getMorphType()] = self::ALWAYS_RELATION_KEY; + } + } + } + /** * Add selects that are given by the 'always' attribute. * @@ -354,47 +420,87 @@ protected static function addAlwaysFields( } /** - * @param string|Expression $field - * @param array $select Passed by reference, adds further fields to select - * @param string|null $parentTable - * @param bool $forRelation + * @param array $queryArgs + * @param array $field + * @param GraphqlType $parentType + * @param array $select + * @param array $with + * @param mixed $ctx + * @param FieldDefinition $fieldObject + * @param string $key + * @param Closure|null $customQuery */ - protected static function addFieldToSelect($field, array &$select, ?string $parentTable, bool $forRelation): void - { - if ($field instanceof Expression) { - $select[] = $field; - - return; - } - - if ($forRelation && ! array_key_exists($field, $select['fields'])) { - $select['fields'][$field] = [ - 'args' => [], - 'fields' => true, - ]; - } elseif (! $forRelation && ! in_array($field, $select)) { - $field = $parentTable ? ($parentTable.'.'.$field) : $field; - if (! in_array($field, $select)) { - $select[] = $field; + protected static function handleInterfaceFields( + array $queryArgs, + array $field, + GraphqlType $parentType, + array &$select, + array &$with, + $ctx, + FieldDefinition $fieldObject, + string $key, + ?Closure $customQuery + ) { + $relationsKey = Arr::get($fieldObject->config, 'alias', $key); + + $with[$relationsKey] = function ($query) use ( + $queryArgs, + $field, + $parentType, + &$select, + $ctx, + $customQuery, + $key, + $fieldObject + ) { + $parentTable = self::isMongodbInstance($parentType) ? null : self::getTableNameFromParentType($parentType); + + self::handleRelation($select, $query, $parentTable, $field); + + // New parent type, which is the relation + try { + if (method_exists($parentType, 'getField')) { + $newParentType = $parentType->getField($key)->config['type']; + $customQuery = $parentType->getField($key)->config['query'] ?? $customQuery; + } else { + return $query; + } + } catch (InvariantViolation $e) { + return $query; } - } - } - private static function getPrimaryKeyFromParentType(GraphqlType $parentType): ?string - { - return isset($parentType->config['model']) ? app($parentType->config['model'])->getKeyName() : null; - } + self::addAlwaysFields($fieldObject, $field, $parentTable, true); + + // Find the type of the current relation by comparing table names + if (isset($parentType->config['types'])) { + $typesFiltered = array_filter( + $parentType->config['types'](), + function (GraphqlType $type) use ($query) { + /* @var Relation $query */ + return app($type->config['model'])->getTable() === $query->getParent()->getTable(); + }); + $typesFiltered = array_values($typesFiltered ?? []); + + if (count($typesFiltered) === 1) { + /* @var GraphqlType $type */ + $type = $typesFiltered[0]; + $relationField = $type->getField($key); + $newParentType = $relationField->config['type']; + // If a custom query is available on the selected type, it should replace the interface's one + $customQuery = $relationField->config['query'] ?? $customQuery; + } + } - private static function getTableNameFromParentType(GraphqlType $parentType): ?string - { - return isset($parentType->config['model']) ? app($parentType->config['model'])->getTable() : null; - } + if ($newParentType instanceof WrappingType) { + $newParentType = $newParentType->getWrappedType(true); + } - private static function isMongodbInstance(GraphqlType $parentType): bool - { - $mongoType = 'Jenssegers\Mongodb\Eloquent\Model'; + /** @var callable $callable */ + $callable = self::getSelectableFieldsAndRelations($queryArgs, $field, $newParentType, $customQuery, + false, $ctx); - return isset($parentType->config['model']) ? app($parentType->config['model']) instanceof $mongoType : false; + return $callable($query); + }; } public function getSelect(): array diff --git a/tests/Database/SelectFields/InterfaceTests/CommentType.php b/tests/Database/SelectFields/InterfaceTests/CommentType.php index 42801cd3..9a1568c7 100644 --- a/tests/Database/SelectFields/InterfaceTests/CommentType.php +++ b/tests/Database/SelectFields/InterfaceTests/CommentType.php @@ -4,7 +4,6 @@ namespace Rebing\GraphQL\Tests\Database\SelectFields\InterfaceTests; -use GraphQL\Type\Definition\Type; use Rebing\GraphQL\Support\Facades\GraphQL; use Rebing\GraphQL\Support\Type as GraphQLType; use Rebing\GraphQL\Tests\Support\Models\Comment; @@ -20,12 +19,7 @@ public function fields(): array { $interface = GraphQL::type('LikableInterface'); - return [ - 'title' => [ - 'type' => Type::nonNull(Type::string()), - 'alias' => 'title', - ], - ] + $interface->getFields(); + return $interface->getFields(); } public function interfaces(): array diff --git a/tests/Database/SelectFields/InterfaceTests/InterfaceTest.php b/tests/Database/SelectFields/InterfaceTests/InterfaceTest.php index d477b0d6..975886f7 100644 --- a/tests/Database/SelectFields/InterfaceTests/InterfaceTest.php +++ b/tests/Database/SelectFields/InterfaceTests/InterfaceTest.php @@ -4,7 +4,7 @@ namespace Rebing\GraphQL\Tests\Database\SelectFields\InterfaceTests; -use PHPUnit\Framework\ExpectationFailedException; +use Illuminate\Foundation\Application; use Rebing\GraphQL\Tests\Support\Models\Comment; use Rebing\GraphQL\Tests\Support\Models\Like; use Rebing\GraphQL\Tests\Support\Models\Post; @@ -81,7 +81,7 @@ public function testGeneratedRelationSqlQuery(): void $this->assertSqlQueries(<<<'SQL' select "id", "title" from "posts"; -select * from "comments" where "comments"."post_id" = ? and "comments"."post_id" is not null order by "comments"."id" asc; +select "comments"."title", "comments"."post_id", "comments"."id" from "comments" where "comments"."post_id" in (?) and "id" >= ? order by "comments"."id" asc; SQL ); @@ -103,9 +103,8 @@ public function testGeneratedRelationSqlQuery(): void $this->assertSame($expectedResult, $result); } - public function testGeneratedInterfaceFieldSqlQuery() + public function testGeneratedInterfaceFieldSqlQuery(): void { - $this->expectException(ExpectationFailedException::class); $post = factory(Post::class) ->create([ 'title' => 'Title of the post', @@ -141,12 +140,21 @@ public function testGeneratedInterfaceFieldSqlQuery() $result = $this->graphql($graphql); - $this->assertSqlQueries(<<<'SQL' + if (Application::VERSION < '5.6') { + $this->assertSqlQueries(<<<'SQL' select "users"."id" from "users"; select "likes"."likable_id", "likes"."likable_type", "likes"."user_id", "likes"."id" from "likes" where "likes"."user_id" in (?); -select "id", "name" from "posts" where "posts"."id" in (?); +select * from "posts" where "posts"."id" in (?); SQL - ); + ); + } else { + $this->assertSqlQueries(<<<'SQL' +select "users"."id" from "users"; +select "likes"."likable_id", "likes"."likable_type", "likes"."user_id", "likes"."id" from "likes" where "likes"."user_id" in (?); +select "id", "title" from "posts" where "posts"."id" in (?); +SQL + ); + } $expectedResult = [ 'data' => [ @@ -168,6 +176,228 @@ public function testGeneratedInterfaceFieldSqlQuery() $this->assertSame($expectedResult, $result); } + public function testGeneratedInterfaceFieldWithRelationSqlQuery(): void + { + $post = factory(Post::class) + ->create([ + 'title' => 'Title of the post', + ]); + factory(Comment::class) + ->create([ + 'title' => 'Title of the comment', + 'post_id' => $post->id, + ]); + + $user = factory(User::class)->create(); + $user2 = factory(User::class)->create(); + $like1 = Like::create([ + 'likable_id' => $post->id, + 'likable_type' => Post::class, + 'user_id' => $user->id, + ]); + $like2 = Like::create([ + 'likable_id' => $post->id, + 'likable_type' => Post::class, + 'user_id' => $user2->id, + ]); + + $graphql = <<<'GRAPHQL' +{ + userQuery { + id + likes{ + likable{ + id + title + likes{ + id + } + } + } + } +} +GRAPHQL; + + $this->sqlCounterReset(); + + $result = $this->graphql($graphql); + + if (Application::VERSION < '5.6') { + $this->assertSqlQueries(<<<'SQL' +select "users"."id" from "users"; +select "likes"."likable_id", "likes"."likable_type", "likes"."user_id", "likes"."id" from "likes" where "likes"."user_id" in (?, ?); +select * from "posts" where "posts"."id" in (?); +select "likes"."id", "likes"."likable_id", "likes"."likable_type" from "likes" where "likes"."likable_id" in (?) and "likes"."likable_type" = ? and 0=0; +SQL + ); + } else { + $this->assertSqlQueries(<<<'SQL' +select "users"."id" from "users"; +select "likes"."likable_id", "likes"."likable_type", "likes"."user_id", "likes"."id" from "likes" where "likes"."user_id" in (?, ?); +select "id", "title" from "posts" where "posts"."id" in (?); +select "likes"."id", "likes"."likable_id", "likes"."likable_type" from "likes" where "likes"."likable_id" in (?) and "likes"."likable_type" = ? and 0=0; +SQL + ); + } + + $expectedResult = [ + 'data' => [ + 'userQuery' => [ + [ + 'id' => (string) $user->id, + 'likes' => [ + [ + 'likable' => [ + 'id' => (string) $post->id, + 'title' => $post->title, + 'likes' => [ + [ + 'id' => (string) $like1->id, + ], + [ + 'id' => (string) $like2->id, + ], + ], + ], + ], + ], + ], + [ + 'id' => (string) $user2->id, + 'likes' => [ + [ + 'likable' => [ + 'id' => (string) $post->id, + 'title' => $post->title, + 'likes' => [ + [ + 'id' => (string) $like1->id, + ], + [ + 'id' => (string) $like2->id, + ], + ], + ], + ], + ], + ], + ], + ], + ]; + $this->assertSame($expectedResult, $result); + } + + public function testGeneratedInterfaceFieldWithRelationAndCustomQueryOnInterfaceSqlQuery(): void + { + $post = factory(Post::class) + ->create([ + 'title' => 'Title of the post', + ]); + $comment = factory(Comment::class) + ->create([ + 'title' => 'Title of the comment', + 'post_id' => $post->id, + ]); + + $user = factory(User::class)->create(); + $user2 = factory(User::class)->create(); + $like1 = Like::create([ + 'likable_id' => $comment->id, + 'likable_type' => Comment::class, + 'user_id' => $user->id, + ]); + $like2 = Like::create([ + 'likable_id' => $comment->id, + 'likable_type' => Comment::class, + 'user_id' => $user2->id, + ]); + + $graphql = <<<'GRAPHQL' +{ + userQuery { + id + likes{ + likable{ + id + title + likes{ + id + } + } + } + } +} +GRAPHQL; + + $this->sqlCounterReset(); + + $result = $this->graphql($graphql); + + if (Application::VERSION < '5.6') { + $this->assertSqlQueries(<<<'SQL' +select "users"."id" from "users"; +select "likes"."likable_id", "likes"."likable_type", "likes"."user_id", "likes"."id" from "likes" where "likes"."user_id" in (?, ?); +select * from "comments" where "comments"."id" in (?); +select "likes"."id", "likes"."likable_id", "likes"."likable_type" from "likes" where "likes"."likable_id" in (?) and "likes"."likable_type" = ? and 1=1; +SQL + ); + } else { + $this->assertSqlQueries(<<<'SQL' +select "users"."id" from "users"; +select "likes"."likable_id", "likes"."likable_type", "likes"."user_id", "likes"."id" from "likes" where "likes"."user_id" in (?, ?); +select "id", "title" from "comments" where "comments"."id" in (?); +select "likes"."id", "likes"."likable_id", "likes"."likable_type" from "likes" where "likes"."likable_id" in (?) and "likes"."likable_type" = ? and 1=1; +SQL + ); + } + + $expectedResult = [ + 'data' => [ + 'userQuery' => [ + [ + 'id' => (string) $user->id, + 'likes' => [ + [ + 'likable' => [ + 'id' => (string) $comment->id, + 'title' => $comment->title, + 'likes' => [ + [ + 'id' => (string) $like1->id, + ], + [ + 'id' => (string) $like2->id, + ], + ], + ], + ], + ], + ], + [ + 'id' => (string) $user2->id, + 'likes' => [ + [ + 'likable' => [ + 'id' => (string) $comment->id, + 'title' => $comment->title, + 'likes' => [ + [ + 'id' => (string) $like1->id, + ], + [ + 'id' => (string) $like2->id, + ], + ], + ], + ], + ], + ], + ], + ], + ]; + $this->assertSame($expectedResult, $result); + } + protected function getEnvironmentSetUp($app) { parent::getEnvironmentSetUp($app); diff --git a/tests/Database/SelectFields/InterfaceTests/LikableInterfaceType.php b/tests/Database/SelectFields/InterfaceTests/LikableInterfaceType.php index 8a21fe58..705a3bf1 100644 --- a/tests/Database/SelectFields/InterfaceTests/LikableInterfaceType.php +++ b/tests/Database/SelectFields/InterfaceTests/LikableInterfaceType.php @@ -5,6 +5,7 @@ namespace Rebing\GraphQL\Tests\Database\SelectFields\InterfaceTests; use GraphQL\Type\Definition\Type; +use Illuminate\Database\Eloquent\Relations\MorphMany; use Rebing\GraphQL\Support\Facades\GraphQL; use Rebing\GraphQL\Support\InterfaceType; use Rebing\GraphQL\Tests\Support\Models\Comment; @@ -16,6 +17,14 @@ class LikableInterfaceType extends InterfaceType 'name' => 'LikableInterface', ]; + public function types(): array + { + return [ + GraphQL::type('Post'), + GraphQL::type('Comment'), + ]; + } + public function fields(): array { return [ @@ -24,7 +33,12 @@ public function fields(): array ], 'title' => [ 'type' => Type::nonNull(Type::string()), - 'alias' => 'title', + ], + 'likes' => [ + 'type' => Type::listOf(GraphQL::type('Like')), + 'query' => function (array $args, MorphMany $query) { + return $query->whereRaw('1=1'); + }, ], ]; } diff --git a/tests/Database/SelectFields/InterfaceTests/PostType.php b/tests/Database/SelectFields/InterfaceTests/PostType.php index 4d687dd0..4e779569 100644 --- a/tests/Database/SelectFields/InterfaceTests/PostType.php +++ b/tests/Database/SelectFields/InterfaceTests/PostType.php @@ -5,6 +5,7 @@ namespace Rebing\GraphQL\Tests\Database\SelectFields\InterfaceTests; use GraphQL\Type\Definition\Type; +use Illuminate\Database\Eloquent\Relations\MorphMany; use Rebing\GraphQL\Support\Facades\GraphQL; use Rebing\GraphQL\Support\Type as GraphQLType; use Rebing\GraphQL\Tests\Support\Models\Post; @@ -21,11 +22,10 @@ public function fields(): array $interface = GraphQL::type('LikableInterface'); return [ - 'title' => [ - 'type' => Type::nonNull(Type::string()), - 'alias' => 'name', - 'resolve' => function ($root) { - return $root->title; + 'likes' => [ + 'type' => Type::listOf(GraphQL::type('Like')), + 'query' => function (array $args, MorphMany $query) { + return $query->whereRaw('0=0'); }, ], ] + $interface->getFields(); From e47f2b80778032f27ba95c3ca90c1f82274514e3 Mon Sep 17 00:00:00 2001 From: Markus Podar Date: Sat, 23 Nov 2019 14:13:33 +0100 Subject: [PATCH 2/3] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ba88c3..bea8847a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG ### Fixed - Fix validation rules for non-null list of non-null objects [\#511 / crissi](https://github.com/rebing/graphql-laravel/pull/511/files) - Add morph type to returned models [\#503 / crissi](https://github.com/rebing/graphql-laravel/pull/503) +- Fixed the custom query not being handled by interface's relations [\#486 / EdwinDayot](https://github.com/rebing/graphql-laravel/pull/486) ### Changed - Switch Code Style handling from StyleCI to PHP-CS Fixer [\#502 / crissi](https://github.com/rebing/graphql-laravel/pull/502) From 703b30ed9cb7374788748f4cb3a97543204fb98e Mon Sep 17 00:00:00 2001 From: Markus Podar Date: Sat, 23 Nov 2019 14:23:25 +0100 Subject: [PATCH 3/3] Update readme --- Readme.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Readme.md b/Readme.md index 2d34bfaf..c77da5b5 100644 --- a/Readme.md +++ b/Readme.md @@ -1448,6 +1448,21 @@ class HumanType extends GraphQLType } ``` +#### Supporting custom queries on interface relations + +If an interface contains a relation with a custom query, it's required to implement `public function types()` returning an array of `GraphQL::type()`, i.e. all the possible types it may resolve to (quite similar as it works for unions) so that it works correctly with `SelectFields`. + +Based on the previous code example, the method would look like: +```php + public function types(): array + { + return[ + GraphQL::type('Human'), + GraphQL::type('Droid'), + ]; + } +``` + #### Sharing Interface fields Since you often have to repeat many of the field definitons of the Interface in the concrete types, it makes sense to share the definitions of the Interface.