From 4867c97bd420ee5240f980b45e94999d895f06f4 Mon Sep 17 00:00:00 2001 From: "xingshenqiang@uniondrug.cn" Date: Sat, 15 Jun 2019 17:04:26 +0800 Subject: [PATCH 01/17] Fix args in sub queries --- src/Rebing/GraphQL/Support/SelectFields.php | 65 +++++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/src/Rebing/GraphQL/Support/SelectFields.php b/src/Rebing/GraphQL/Support/SelectFields.php index 7b2135d0..fd79b2d6 100644 --- a/src/Rebing/GraphQL/Support/SelectFields.php +++ b/src/Rebing/GraphQL/Support/SelectFields.php @@ -5,6 +5,11 @@ namespace Rebing\GraphQL\Support; use Closure; +use GraphQL\Language\AST\FieldNode; +use GraphQL\Language\AST\FragmentDefinitionNode; +use GraphQL\Language\AST\FragmentSpreadNode; +use GraphQL\Language\AST\InlineFragmentNode; +use GraphQL\Language\AST\SelectionSetNode; use Illuminate\Support\Arr; use GraphQL\Error\InvariantViolation; use GraphQL\Type\Definition\UnionType; @@ -27,6 +32,8 @@ class SelectFields private $relations = []; /** @var array */ private static $privacyValidations = []; + /** @var ResolveInfo */ + private $info; const FOREIGN_KEY = 'foreignKey'; @@ -44,13 +51,55 @@ public function __construct(ResolveInfo $info, $parentType, array $args) if (! is_null($info->fieldNodes[0]->selectionSet)) { self::$args = $args; - $fields = self::getSelectableFieldsAndRelations($info->getFieldSelection(5), $parentType); + $fields = self::getSelectableFieldsAndRelations($this->getFieldSelection($info,5), $parentType); $this->select = $fields[0]; $this->relations = $fields[1]; } } + public function getFieldSelection(ResolveInfo $info, $depth = 0) + { + $data = []; + + /** @var FieldNode $fieldNode */ + foreach ($info->fieldNodes as $fieldNode) { + $data = array_merge_recursive($data, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); + } + + $fields['fields'] = $data; + $fields['args'] = self::$args; + return $fields; + } + + private function foldSelectionSet(SelectionSetNode $selectionSet, $descend) + { + $fields = []; + + foreach ($selectionSet->selections as $selectionNode) { + if ($selectionNode instanceof FieldNode) { + $fields[$selectionNode->name->value]['fields'] = $descend > 0 && !empty($selectionNode->selectionSet) + ? $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1) + : true; + $fields[$selectionNode->name->value]['args'] = []; + foreach ($selectionNode->arguments as $argument) { + $fields[$selectionNode->name->value]['args'][$argument->name->value] = $argument->value->value; + } + } else if ($selectionNode instanceof FragmentSpreadNode) { + $spreadName = $selectionNode->name->value; + if (isset($this->info->fragments[$spreadName])) { + /** @var FragmentDefinitionNode $fragment */ + $fragment = $this->info->fragments[$spreadName]; + $fields = array_merge_recursive($this->foldSelectionSet($fragment->selectionSet, $descend), $fields); + } + } else if ($selectionNode instanceof InlineFragmentNode) { + $fields = array_merge_recursive($this->foldSelectionSet($selectionNode->selectionSet, $descend), $fields); + } + } + + return $fields; + } + /** * Retrieve the fields (top level) and relations that * will be selected with the query. @@ -89,9 +138,9 @@ public static function getSelectableFieldsAndRelations(array $requestedFields, $ if ($topLevel) { return [$select, $with]; } else { - return function ($query) use ($with, $select, $customQuery) { + return function ($query) use ($with, $select, $customQuery, $requestedFields) { if ($customQuery) { - $query = $customQuery(self::$args, $query); + $query = $customQuery($requestedFields['args'] ?? self::$args, $query); } $query->select($select); @@ -108,7 +157,7 @@ protected static function handleFields(array $requestedFields, $parentType, arra { $parentTable = self::isMongodbInstance($parentType) ? null : self::getTableNameFromParentType($parentType); - foreach ($requestedFields as $key => $field) { + foreach ($requestedFields['fields'] as $key => $field) { // Ignore __typename, as it's a special case if ($key === '__typename') { continue; @@ -145,7 +194,7 @@ protected static function handleFields(array $requestedFields, $parentType, arra self::handleFields($field, $fieldObject->config['type']->getWrappedType(), $select, $with); } // With - elseif (is_array($field) && $queryable) { + elseif (is_array($field['fields']) && $queryable) { if (isset($parentType->config['model'])) { // Get the next parent type, so that 'with' queries could be made // Both keys for the relation are required (e.g 'id' <-> 'user_id') @@ -183,7 +232,7 @@ protected static function handleFields(array $requestedFields, $parentType, arra $segments = explode('.', $foreignKey); $foreignKey = end($segments); if (! array_key_exists($foreignKey, $field)) { - $field[$foreignKey] = self::FOREIGN_KEY; + $field['fields'][$foreignKey] = self::FOREIGN_KEY; } } @@ -335,4 +384,8 @@ public function getRelations() { return $this->relations; } + + public function getResolveInfo() { + return $this->info; + } } From 2d89c71383800462cb41da8c5f7b09aba9d9860f Mon Sep 17 00:00:00 2001 From: sowork <807522127@qq.com> Date: Sat, 15 Jun 2019 09:16:27 +0000 Subject: [PATCH 02/17] Apply fixes from StyleCI --- src/Rebing/GraphQL/Support/SelectFields.php | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Rebing/GraphQL/Support/SelectFields.php b/src/Rebing/GraphQL/Support/SelectFields.php index fd79b2d6..d0e6c2f6 100644 --- a/src/Rebing/GraphQL/Support/SelectFields.php +++ b/src/Rebing/GraphQL/Support/SelectFields.php @@ -5,16 +5,16 @@ namespace Rebing\GraphQL\Support; use Closure; -use GraphQL\Language\AST\FieldNode; -use GraphQL\Language\AST\FragmentDefinitionNode; -use GraphQL\Language\AST\FragmentSpreadNode; -use GraphQL\Language\AST\InlineFragmentNode; -use GraphQL\Language\AST\SelectionSetNode; use Illuminate\Support\Arr; +use GraphQL\Language\AST\FieldNode; use GraphQL\Error\InvariantViolation; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\WrappingType; +use GraphQL\Language\AST\SelectionSetNode; +use GraphQL\Language\AST\FragmentSpreadNode; +use GraphQL\Language\AST\InlineFragmentNode; +use GraphQL\Language\AST\FragmentDefinitionNode; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphTo; @@ -51,7 +51,7 @@ public function __construct(ResolveInfo $info, $parentType, array $args) if (! is_null($info->fieldNodes[0]->selectionSet)) { self::$args = $args; - $fields = self::getSelectableFieldsAndRelations($this->getFieldSelection($info,5), $parentType); + $fields = self::getSelectableFieldsAndRelations($this->getFieldSelection($info, 5), $parentType); $this->select = $fields[0]; $this->relations = $fields[1]; @@ -69,6 +69,7 @@ public function getFieldSelection(ResolveInfo $info, $depth = 0) $fields['fields'] = $data; $fields['args'] = self::$args; + return $fields; } @@ -78,21 +79,21 @@ private function foldSelectionSet(SelectionSetNode $selectionSet, $descend) foreach ($selectionSet->selections as $selectionNode) { if ($selectionNode instanceof FieldNode) { - $fields[$selectionNode->name->value]['fields'] = $descend > 0 && !empty($selectionNode->selectionSet) + $fields[$selectionNode->name->value]['fields'] = $descend > 0 && ! empty($selectionNode->selectionSet) ? $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1) : true; $fields[$selectionNode->name->value]['args'] = []; foreach ($selectionNode->arguments as $argument) { $fields[$selectionNode->name->value]['args'][$argument->name->value] = $argument->value->value; } - } else if ($selectionNode instanceof FragmentSpreadNode) { + } elseif ($selectionNode instanceof FragmentSpreadNode) { $spreadName = $selectionNode->name->value; if (isset($this->info->fragments[$spreadName])) { /** @var FragmentDefinitionNode $fragment */ $fragment = $this->info->fragments[$spreadName]; $fields = array_merge_recursive($this->foldSelectionSet($fragment->selectionSet, $descend), $fields); } - } else if ($selectionNode instanceof InlineFragmentNode) { + } elseif ($selectionNode instanceof InlineFragmentNode) { $fields = array_merge_recursive($this->foldSelectionSet($selectionNode->selectionSet, $descend), $fields); } } @@ -385,7 +386,8 @@ public function getRelations() return $this->relations; } - public function getResolveInfo() { + public function getResolveInfo() + { return $this->info; } } From 771ff001ba7c2dfc5b1e0637984e064a408ea852 Mon Sep 17 00:00:00 2001 From: "xingshenqiang@uniondrug.cn" Date: Sat, 15 Jun 2019 18:33:02 +0800 Subject: [PATCH 03/17] update NestedRelationLoadingTest.php --- .../NestedRelationLoadingTest.php | 48 +------------------ 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php b/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php index a0d275e0..1494bfe1 100644 --- a/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php +++ b/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php @@ -793,8 +793,8 @@ public function testQuerySelectAndWithAndNestedSubArgs(): void $this->assertSqlQueries(<<<'SQL' select "users"."id", "users"."name" from "users" order by "users"."id" asc; -select "posts"."body", "posts"."id", "posts"."title", "posts"."user_id" from "posts" where "posts"."user_id" in (?, ?) order by "posts"."id" asc; -select "comments"."body", "comments"."id", "comments"."title", "comments"."post_id" from "comments" where "comments"."post_id" in (?, ?, ?, ?) order by "comments"."id" asc; +select "posts"."body", "posts"."id", "posts"."title", "posts"."user_id" from "posts" where "posts"."user_id" in (?, ?) and "posts"."flag" = ? order by "posts"."id" asc; +select "comments"."body", "comments"."id", "comments"."title", "comments"."post_id" from "comments" where "comments"."post_id" in (?, ?) and "comments"."flag" = ? order by "comments"."id" asc; SQL ); @@ -815,28 +815,6 @@ public function testQuerySelectAndWithAndNestedSubArgs(): void 'id' => (string) $users[0]->posts[0]->comments[0]->id, 'title' => $users[0]->posts[0]->comments[0]->title, ], - [ - 'body' => $users[0]->posts[0]->comments[1]->body, - 'id' => (string) $users[0]->posts[0]->comments[1]->id, - 'title' => $users[0]->posts[0]->comments[1]->title, - ], - ], - ], - [ - 'body' => $users[0]->posts[1]->body, - 'id' => (string) $users[0]->posts[1]->id, - 'title' => $users[0]->posts[1]->title, - 'comments' => [ - [ - 'body' => $users[0]->posts[1]->comments[0]->body, - 'id' => (string) $users[0]->posts[1]->comments[0]->id, - 'title' => $users[0]->posts[1]->comments[0]->title, - ], - [ - 'body' => $users[0]->posts[1]->comments[1]->body, - 'id' => (string) $users[0]->posts[1]->comments[1]->id, - 'title' => $users[0]->posts[1]->comments[1]->title, - ], ], ], ], @@ -855,28 +833,6 @@ public function testQuerySelectAndWithAndNestedSubArgs(): void 'id' => (string) $users[1]->posts[0]->comments[0]->id, 'title' => $users[1]->posts[0]->comments[0]->title, ], - [ - 'body' => $users[1]->posts[0]->comments[1]->body, - 'id' => (string) $users[1]->posts[0]->comments[1]->id, - 'title' => $users[1]->posts[0]->comments[1]->title, - ], - ], - ], - [ - 'body' => $users[1]->posts[1]->body, - 'id' => (string) $users[1]->posts[1]->id, - 'title' => $users[1]->posts[1]->title, - 'comments' => [ - [ - 'body' => $users[1]->posts[1]->comments[0]->body, - 'id' => (string) $users[1]->posts[1]->comments[0]->id, - 'title' => $users[1]->posts[1]->comments[0]->title, - ], - [ - 'body' => $users[1]->posts[1]->comments[1]->body, - 'id' => (string) $users[1]->posts[1]->comments[1]->id, - 'title' => $users[1]->posts[1]->comments[1]->title, - ], ], ], ], From c7e7cd86c255bdebf6741a3b7e6fb73711c41c26 Mon Sep 17 00:00:00 2001 From: "xingshenqiang@uniondrug.cn" Date: Tue, 18 Jun 2019 18:34:12 +0800 Subject: [PATCH 04/17] solve the problems cacused by variables --- src/Rebing/GraphQL/Support/SelectFields.php | 11 +++++- .../NestedRelationLoadingTest.php | 38 +------------------ 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/Rebing/GraphQL/Support/SelectFields.php b/src/Rebing/GraphQL/Support/SelectFields.php index d0e6c2f6..6ca53609 100644 --- a/src/Rebing/GraphQL/Support/SelectFields.php +++ b/src/Rebing/GraphQL/Support/SelectFields.php @@ -5,6 +5,8 @@ namespace Rebing\GraphQL\Support; use Closure; +use GraphQL\Language\AST\ArgumentNode; +use GraphQL\Language\AST\VariableNode; use Illuminate\Support\Arr; use GraphQL\Language\AST\FieldNode; use GraphQL\Error\InvariantViolation; @@ -84,7 +86,14 @@ private function foldSelectionSet(SelectionSetNode $selectionSet, $descend) : true; $fields[$selectionNode->name->value]['args'] = []; foreach ($selectionNode->arguments as $argument) { - $fields[$selectionNode->name->value]['args'][$argument->name->value] = $argument->value->value; + if ($argument->value instanceof VariableNode) { + $value = $argument->value->name->value; + } else { + $value = $argument->value->value; + } + if ($argument instanceof ArgumentNode) { + $fields[$selectionNode->name->value]['args'][$argument->name->value] = $value; + } } } elseif ($selectionNode instanceof FragmentSpreadNode) { $spreadName = $selectionNode->name->value; diff --git a/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php b/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php index 1494bfe1..ff4cc55d 100644 --- a/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php +++ b/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php @@ -630,8 +630,8 @@ public function testQuerySelectAndWithAndSubArgs(): void $this->assertSqlQueries(<<<'SQL' select "users"."id", "users"."name" from "users" order by "users"."id" asc; -select "posts"."body", "posts"."id", "posts"."title", "posts"."user_id" from "posts" where "posts"."user_id" in (?, ?) order by "posts"."id" asc; -select "comments"."body", "comments"."id", "comments"."title", "comments"."post_id" from "comments" where "comments"."post_id" in (?, ?, ?, ?) order by "comments"."id" asc; +select "posts"."body", "posts"."id", "posts"."title", "posts"."user_id" from "posts" where "posts"."user_id" in (?, ?) and "posts"."flag" = ? order by "posts"."id" asc; +select "comments"."body", "comments"."id", "comments"."title", "comments"."post_id" from "comments" where "comments"."post_id" in (?, ?) order by "comments"."id" asc; SQL ); @@ -659,23 +659,6 @@ public function testQuerySelectAndWithAndSubArgs(): void ], ], ], - [ - 'body' => $users[0]->posts[1]->body, - 'id' => (string) $users[0]->posts[1]->id, - 'title' => $users[0]->posts[1]->title, - 'comments' => [ - [ - 'body' => $users[0]->posts[1]->comments[0]->body, - 'id' => (string) $users[0]->posts[1]->comments[0]->id, - 'title' => $users[0]->posts[1]->comments[0]->title, - ], - [ - 'body' => $users[0]->posts[1]->comments[1]->body, - 'id' => (string) $users[0]->posts[1]->comments[1]->id, - 'title' => $users[0]->posts[1]->comments[1]->title, - ], - ], - ], ], ], [ @@ -699,23 +682,6 @@ public function testQuerySelectAndWithAndSubArgs(): void ], ], ], - [ - 'body' => $users[1]->posts[1]->body, - 'id' => (string) $users[1]->posts[1]->id, - 'title' => $users[1]->posts[1]->title, - 'comments' => [ - [ - 'body' => $users[1]->posts[1]->comments[0]->body, - 'id' => (string) $users[1]->posts[1]->comments[0]->id, - 'title' => $users[1]->posts[1]->comments[0]->title, - ], - [ - 'body' => $users[1]->posts[1]->comments[1]->body, - 'id' => (string) $users[1]->posts[1]->comments[1]->id, - 'title' => $users[1]->posts[1]->comments[1]->title, - ], - ], - ], ], ], ], From 646920c4f14147abadec2447a03152df2ec37cc4 Mon Sep 17 00:00:00 2001 From: sowork <807522127@qq.com> Date: Tue, 18 Jun 2019 10:38:35 +0000 Subject: [PATCH 05/17] Apply fixes from StyleCI --- src/Rebing/GraphQL/Support/SelectFields.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rebing/GraphQL/Support/SelectFields.php b/src/Rebing/GraphQL/Support/SelectFields.php index 6ca53609..98c10ce0 100644 --- a/src/Rebing/GraphQL/Support/SelectFields.php +++ b/src/Rebing/GraphQL/Support/SelectFields.php @@ -5,11 +5,11 @@ namespace Rebing\GraphQL\Support; use Closure; -use GraphQL\Language\AST\ArgumentNode; -use GraphQL\Language\AST\VariableNode; use Illuminate\Support\Arr; use GraphQL\Language\AST\FieldNode; use GraphQL\Error\InvariantViolation; +use GraphQL\Language\AST\ArgumentNode; +use GraphQL\Language\AST\VariableNode; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\WrappingType; From 5750416f244e1891594e2640c2c806c854d5f9af Mon Sep 17 00:00:00 2001 From: "xingshenqiang@uniondrug.cn" Date: Tue, 18 Jun 2019 22:53:50 +0800 Subject: [PATCH 06/17] fix variables --- src/Rebing/GraphQL/Support/SelectFields.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Rebing/GraphQL/Support/SelectFields.php b/src/Rebing/GraphQL/Support/SelectFields.php index 6ca53609..155c60d1 100644 --- a/src/Rebing/GraphQL/Support/SelectFields.php +++ b/src/Rebing/GraphQL/Support/SelectFields.php @@ -50,22 +50,23 @@ public function __construct(ResolveInfo $info, $parentType, array $args) $parentType = $parentType->getWrappedType(true); } + $this->info = $info; if (! is_null($info->fieldNodes[0]->selectionSet)) { self::$args = $args; - $fields = self::getSelectableFieldsAndRelations($this->getFieldSelection($info, 5), $parentType); + $fields = self::getSelectableFieldsAndRelations($this->getFieldSelection(5), $parentType); $this->select = $fields[0]; $this->relations = $fields[1]; } } - public function getFieldSelection(ResolveInfo $info, $depth = 0) + public function getFieldSelection($depth = 0) { $data = []; /** @var FieldNode $fieldNode */ - foreach ($info->fieldNodes as $fieldNode) { + foreach ($this->info->fieldNodes as $fieldNode) { $data = array_merge_recursive($data, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); } @@ -87,7 +88,7 @@ private function foldSelectionSet(SelectionSetNode $selectionSet, $descend) $fields[$selectionNode->name->value]['args'] = []; foreach ($selectionNode->arguments as $argument) { if ($argument->value instanceof VariableNode) { - $value = $argument->value->name->value; + $value = $this->info->variableValues[$argument->value->name->value] ?? null; } else { $value = $argument->value->value; } From 917b1e51046764de00b33609e02f85bc1554a155 Mon Sep 17 00:00:00 2001 From: "xingshenqiang@uniondrug.cn" Date: Tue, 18 Jun 2019 23:31:38 +0800 Subject: [PATCH 07/17] remote merge --- CHANGELOG.md | 7 + Readme.md | 24 +-- composer.json | 4 +- docs/advanced.md | 48 +++--- .../GraphQL/Console/stubs/mutation.stub | 4 +- src/Rebing/GraphQL/Console/stubs/query.stub | 4 +- src/Rebing/GraphQL/Console/stubs/type.stub | 2 +- src/Rebing/GraphQL/GraphQL.php | 128 +++++++++----- src/Rebing/GraphQL/GraphQLController.php | 6 +- .../GraphQL/GraphQLLumenServiceProvider.php | 4 +- src/Rebing/GraphQL/GraphQLServiceProvider.php | 19 ++- .../GraphQL/GraphQLUploadMiddleware.php | 7 +- src/Rebing/GraphQL/Support/Field.php | 38 +++-- src/Rebing/GraphQL/Support/InterfaceType.php | 8 +- src/Rebing/GraphQL/Support/PaginationType.php | 21 +-- src/Rebing/GraphQL/Support/SelectFields.php | 60 ++++--- src/Rebing/GraphQL/Support/Type.php | 26 ++- src/Rebing/GraphQL/Support/UnionType.php | 3 +- src/Rebing/GraphQL/Support/UploadType.php | 21 +-- src/Rebing/GraphQL/routes.php | 5 +- src/resources/views/graphiql.php | 8 +- ...ionValidationUniqueWithCustomRulesTest.php | 161 ++++++++++++++++++ .../MutationWithCustomRuleWithRuleObject.php | 51 ++++++ .../RuleObjectFail.php | 20 +++ .../RuleObjectPass.php | 20 +++ .../InterfaceTests/ExampleInterfaceQuery.php | 2 +- .../InterfaceTests/ExampleInterfaceType.php | 2 +- .../InterfaceTests/InterfaceImpl1Type.php | 4 +- .../CommentType.php | 2 +- .../NestedRelationLoadingTests/PostType.php | 2 +- .../NestedRelationLoadingTests/UserType.php | 2 +- .../NestedRelationLoadingTests/UsersQuery.php | 4 +- tests/Support/Objects/CustomExampleType.php | 2 +- tests/Support/Objects/ExampleEnumType.php | 2 +- tests/Support/Objects/ExampleField.php | 4 +- .../Objects/ExampleFilterInputType.php | 2 +- tests/Support/Objects/ExampleInputType.php | 2 +- .../Support/Objects/ExampleInterfaceType.php | 2 +- .../ExampleNestedValidationInputObject.php | 2 +- tests/Support/Objects/ExampleType.php | 2 +- tests/Support/Objects/ExampleUnionType.php | 2 +- .../Objects/ExampleValidationField.php | 4 +- .../Objects/ExampleValidationInputObject.php | 2 +- .../Objects/ExamplesAuthorizeQuery.php | 6 +- .../Support/Objects/ExamplesFilteredQuery.php | 4 +- .../Objects/ExamplesPaginationQuery.php | 4 +- tests/Support/Objects/ExamplesQuery.php | 4 +- .../Support/Objects/UpdateExampleMutation.php | 6 +- .../UpdateExampleMutationWithInputType.php | 6 +- ...stNonNullWithSelectFieldsAndModelQuery.php | 4 +- tests/Support/Queries/PostQuery.php | 4 +- ...AndModelAndAliasAndCustomResolverQuery.php | 4 +- ...tWithSelectFieldsAndModelAndAliasQuery.php | 4 +- .../PostWithSelectFieldsAndModelQuery.php | 4 +- .../PostWithSelectFieldsNoModelQuery.php | 4 +- ...stsListOfWithSelectFieldsAndModelQuery.php | 2 +- ...NonNullOfWithSelectFieldsAndModelQuery.php | 2 +- ...AndListOfWithSelectFieldsAndModelQuery.php | 2 +- tests/Support/Types/PostType.php | 2 +- ...WithModelAndAliasAndCustomResolverType.php | 2 +- .../Types/PostWithModelAndAliasType.php | 2 +- tests/Support/Types/PostWithModelType.php | 2 +- tests/Unit/EndpointTest.php | 11 ++ .../LaravelValidatorTest.php | 94 ++++++++++ .../LaravelValidatorTests/RuleObjectFail.php | 20 +++ .../LaravelValidatorTests/RuleObjectPass.php | 20 +++ .../MutationCustomRulesTest.php | 62 +++++++ .../MutationWithCustomRuleWithClosure.php | 46 +++++ .../MutationWithCustomRuleWithRuleObject.php | 44 +++++ .../MutationCustomRulesTests/RuleObject.php | 20 +++ ...utationValidationInWithCustomRulesTest.php | 110 ++++++++++++ .../MutationWithCustomRuleWithRuleObject.php | 51 ++++++ .../RuleObjectFail.php | 20 +++ .../RuleObjectPass.php | 20 +++ 74 files changed, 1092 insertions(+), 237 deletions(-) create mode 100644 tests/Database/MutationValidationUniqueWithCustomRulesTests/MutationValidationUniqueWithCustomRulesTest.php create mode 100644 tests/Database/MutationValidationUniqueWithCustomRulesTests/MutationWithCustomRuleWithRuleObject.php create mode 100644 tests/Database/MutationValidationUniqueWithCustomRulesTests/RuleObjectFail.php create mode 100644 tests/Database/MutationValidationUniqueWithCustomRulesTests/RuleObjectPass.php create mode 100644 tests/Unit/LaravelValidatorTests/LaravelValidatorTest.php create mode 100644 tests/Unit/LaravelValidatorTests/RuleObjectFail.php create mode 100644 tests/Unit/LaravelValidatorTests/RuleObjectPass.php create mode 100644 tests/Unit/MutationCustomRulesTests/MutationCustomRulesTest.php create mode 100644 tests/Unit/MutationCustomRulesTests/MutationWithCustomRuleWithClosure.php create mode 100644 tests/Unit/MutationCustomRulesTests/MutationWithCustomRuleWithRuleObject.php create mode 100644 tests/Unit/MutationCustomRulesTests/RuleObject.php create mode 100644 tests/Unit/MutationValidationInWithCustomRulesTests/MutationValidationInWithCustomRulesTest.php create mode 100644 tests/Unit/MutationValidationInWithCustomRulesTests/MutationWithCustomRuleWithRuleObject.php create mode 100644 tests/Unit/MutationValidationInWithCustomRulesTests/RuleObjectFail.php create mode 100644 tests/Unit/MutationValidationInWithCustomRulesTests/RuleObjectPass.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 37828c40..7a4c7999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,14 @@ CHANGELOG Next release ------------ ## Breaking changes +- Added PHP types / phpdoc to all methods / properties [\#331](https://github.com/rebing/graphql-laravel/pull/331) + - Changes in method signatures will require small adaptions. - Validation errors are moved from error.validation to error.extensions.validation as per GraphQL spec recommendation [\#294](https://github.com/rebing/graphql-laravel/pull/294) - SelectFields on interface types now only selects specific fields instead of all [\#294](https://github.com/rebing/graphql-laravel/pull/294) - Although this could be consider a bug fix, it changes what columns are selected and if your code as a side-effect dependent on all columns being selected, it will break ### Added +- GraphiQL: use regenerated CSRF from server if present [\#332](https://github.com/rebing/graphql-laravel/pull/332) - New config options `headers` to send custom HTTP headers and `json_encoding_options` for encoding the JSON response [\#293](https://github.com/rebing/graphql-laravel/pull/293) - Auto-resolve aliased fields [\#283](https://github.com/rebing/graphql-laravel/pull/283) - Added declare(strict_types=1) directive to all files @@ -23,6 +26,10 @@ Next release ### Fixed - SelectFields now works with wrapped types (nonNull, listOf) +### Removed +- Unused static field `\Rebing\GraphQL\Support\Type::$instances` +- Unused field `\Rebing\GraphQL\Support\Type::$unionType` + 2019-03-07, v1.21.2 ------------------- diff --git a/Readme.md b/Readme.md index 00ed0bf2..6c3af116 100644 --- a/Readme.md +++ b/Readme.md @@ -166,7 +166,7 @@ class UserType extends GraphQLType 'model' => User::class, ]; - public function fields() + public function fields(): array { return [ 'id' => [ @@ -227,12 +227,12 @@ class UsersQuery extends Query 'name' => 'Users query' ]; - public function type() + public function type(): Type { return Type::listOf(GraphQL::type('user')); } - public function args() + public function args(): array { return [ 'id' => ['name' => 'id', 'type' => Type::string()], @@ -306,12 +306,12 @@ class UpdateUserPasswordMutation extends Mutation 'name' => 'UpdateUserPassword' ]; - public function type() + public function type(): Type { return GraphQL::type('user'); } - public function args() + public function args(): array { return [ 'id' => ['name' => 'id', 'type' => Type::nonNull(Type::string())], @@ -387,12 +387,12 @@ class UpdateUserEmailMutation extends Mutation 'name' => 'UpdateUserEmail' ]; - public function type() + public function type(): Type { return GraphQL::type('user'); } - public function args() + public function args(): array { return [ 'id' => ['name' => 'id', 'type' => Type::string()], @@ -400,7 +400,7 @@ class UpdateUserEmailMutation extends Mutation ]; } - public function rules(array $args = []) + protected function rules(array $args = []): array { return [ 'id' => ['required'], @@ -431,7 +431,7 @@ class UpdateUserEmailMutation extends Mutation //... - public function args() + public function args(): array { return [ 'id' => [ @@ -487,7 +487,7 @@ argument doesn't conflict with any existing data, you could perform the followin > return specific errors per validation type. ````php -public function validationErrorMessages ($args = []) +public function validationErrorMessages(array $args = []): array { return [ 'name.required' => 'Please enter your full name', @@ -523,12 +523,12 @@ class UserProfilePhotoMutation extends Mutation 'name' => 'UpdateUserProfilePhoto' ]; - public function type() + public function type(): Type { return GraphQL::type('user'); } - public function args() + public function args(): array { return [ 'profilePicture' => [ diff --git a/composer.json b/composer.json index c0ec9521..64e5b6ec 100644 --- a/composer.json +++ b/composer.json @@ -40,8 +40,8 @@ "phpstan/phpstan": "^0.11.8" }, "autoload": { - "psr-0": { - "Rebing\\GraphQL\\": "src/" + "psr-4": { + "Rebing\\GraphQL\\": "src/Rebing/GraphQL/" } }, "autoload-dev": { diff --git a/docs/advanced.md b/docs/advanced.md index d2974dae..51895607 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -26,7 +26,7 @@ use Auth; class UsersQuery extends Query { - public function authorize(array $args) + public function authorize(array $args): bool { // true, if logged in return ! Auth::guest(); @@ -43,7 +43,7 @@ use Auth; class UsersQuery extends Query { - public function authorize(array $args) + public function authorize(array $args): bool { if (isset($args['id'])) { return Auth::id() == $args['id']; @@ -67,7 +67,7 @@ class UserType extends GraphQLType { // ... - public function fields() + public function fields(): array { return [ 'id' => [ @@ -113,7 +113,7 @@ class UserType extends GraphQLType // ... - public function fields() + public function fields(): array { return [ 'id' => [ @@ -175,12 +175,12 @@ class PictureField extends Field 'description' => 'A picture', ]; - public function type() + public function type(): Type { return Type::string(); } - public function args() + public function args(): array { return [ 'width' => [ @@ -224,7 +224,7 @@ class UserType extends GraphQLType 'model' => User::class, ]; - public function fields() + public function fields(): array { return [ 'id' => [ @@ -272,12 +272,12 @@ class UsersQuery extends Query 'name' => 'Users query' ]; - public function type() + public function type(): Type { return Type::listOf(GraphQL::type('user')); } - public function args() + public function args(): array { return [ 'id' => ['name' => 'id', 'type' => Type::string()], @@ -328,7 +328,7 @@ class UserType extends GraphQLType /** * @return array */ - public function fields() + public function fields(): array { return [ 'uuid' => [ @@ -365,7 +365,7 @@ class ProfileType extends GraphQLType 'model' => UserProfileModel::class, ]; - public function fields() + public function fields(): array { return [ 'name' => [ @@ -386,7 +386,7 @@ class PostType extends GraphQLType 'model' => PostModel::class, ]; - public function fields() + public function fields(): array { return [ 'title' => [ @@ -412,7 +412,7 @@ class UserType extends GraphQLType // ... - public function fields() + public function fields(): array { return [ @@ -440,7 +440,7 @@ Note that you have to manually handle the limit and page values: ```php class PostsQuery extends Query { - public function type() + public function type(): \GraphQL\Type\Definition\Type { return GraphQL::paginate('posts'); } @@ -643,7 +643,7 @@ use Rebing\GraphQL\Support\Type as GraphQLType; class TestType extends GraphQLType { - public function fields() + public function fields(): array { return [ 'episode_type' => [ @@ -720,7 +720,7 @@ class CharacterInterface extends InterfaceType 'description' => 'Character interface.', ]; - public function fields() + public function fields(): array { return [ 'id' => [ @@ -766,7 +766,7 @@ class HumanType extends GraphQLType 'description' => 'A human.' ]; - public function fields() + public function fields(): array { return [ 'id' => [ @@ -785,7 +785,7 @@ class HumanType extends GraphQLType ]; } - public function interfaces() + public function interfaces(): array { return [ GraphQL::type('Character') @@ -803,7 +803,7 @@ With this you could write the `fields` method of your `HumanType` class like thi ```php -public function fields() +public function fields(): array { $interface = GraphQL::type('Character'); @@ -823,7 +823,7 @@ public function fields() Or by using the `getFields` method: ```php -public function fields() +public function fields(): array { $interface = GraphQL::type('Character'); @@ -859,7 +859,7 @@ class ReviewInput extends GraphQLType 'description' => 'A review with a comment and a score (0 to 5)' ]; - public function fields() + public function fields(): array { return [ 'comment' => [ @@ -893,7 +893,7 @@ Then use it in a mutation, like: // app/GraphQL/Type/TestMutation.php class TestMutation extends GraphQLType { - public function args() + public function args(): array { return [ 'review' => [ @@ -917,7 +917,7 @@ class UserType extends GraphQLType // ... - public function fields() + public function fields(): array { return [ // ... @@ -964,7 +964,7 @@ class UserType extends GraphQLType 'model' => User::class, ]; - public function fields() + public function fields(): array { return [ 'id' => [ diff --git a/src/Rebing/GraphQL/Console/stubs/mutation.stub b/src/Rebing/GraphQL/Console/stubs/mutation.stub index b107935b..ccf2dc36 100644 --- a/src/Rebing/GraphQL/Console/stubs/mutation.stub +++ b/src/Rebing/GraphQL/Console/stubs/mutation.stub @@ -16,12 +16,12 @@ class DummyClass extends Mutation 'description' => 'A mutation' ]; - public function type() + public function type(): Type { return Type::listOf(Type::string()); } - public function args() + public function args(): array { return [ diff --git a/src/Rebing/GraphQL/Console/stubs/query.stub b/src/Rebing/GraphQL/Console/stubs/query.stub index eb4fc7ae..21511784 100644 --- a/src/Rebing/GraphQL/Console/stubs/query.stub +++ b/src/Rebing/GraphQL/Console/stubs/query.stub @@ -16,12 +16,12 @@ class DummyClass extends Query 'description' => 'A query' ]; - public function type() + public function type(): Type { return Type::listOf(Type::string()); } - public function args() + public function args(): array { return [ diff --git a/src/Rebing/GraphQL/Console/stubs/type.stub b/src/Rebing/GraphQL/Console/stubs/type.stub index 0ad3fd0a..8ba357e0 100644 --- a/src/Rebing/GraphQL/Console/stubs/type.stub +++ b/src/Rebing/GraphQL/Console/stubs/type.stub @@ -13,7 +13,7 @@ class DummyClass extends GraphQLType 'description' => 'A type' ]; - public function fields() + public function fields(): array { return [ diff --git a/src/Rebing/GraphQL/GraphQL.php b/src/Rebing/GraphQL/GraphQL.php index 50c83ff7..1df43ecd 100644 --- a/src/Rebing/GraphQL/GraphQL.php +++ b/src/Rebing/GraphQL/GraphQL.php @@ -11,28 +11,42 @@ use GraphQL\Type\Schema; use Illuminate\Support\Arr; use GraphQL\Error\FormattedError; +use GraphQL\Type\Definition\Type; use GraphQL\GraphQL as GraphQLBase; +use GraphQL\Executor\ExecutionResult; use GraphQL\Type\Definition\ObjectType; use Rebing\GraphQL\Error\ValidationError; use Rebing\GraphQL\Support\PaginationType; use Rebing\GraphQL\Error\AuthorizationError; use Rebing\GraphQL\Exception\SchemaNotFound; use Illuminate\Contracts\Debug\ExceptionHandler; +use Illuminate\Contracts\Foundation\Application; class GraphQL { + /** @var Application */ protected $app; protected $schemas = []; + /** + * Maps GraphQL type names to their class name. + * + * @var array + */ protected $types = []; + /** @var Type[] */ protected $typesInstances = []; - public function __construct($app) + public function __construct(Application $app) { $this->app = $app; } - public function schema($schema = null) + /** + * @param Schema|array|string|null $schema + * @return Schema + */ + public function schema($schema = null): Schema { if ($schema instanceof Schema) { return $schema; @@ -89,17 +103,23 @@ public function schema($schema = null) /** * @param string $query - * @param array $params + * @param array|null $params * @param array $opts Additional options, like 'schema', 'context' or 'operationName' * - * @return mixed + * @return array */ - public function query($query, $params = [], $opts = []) + public function query(string $query, ?array $params = [], array $opts = []): array { return $this->queryAndReturnResult($query, $params, $opts)->toArray(); } - public function queryAndReturnResult($query, $params = [], $opts = []) + /** + * @param string $query + * @param array|null $params + * @param array $opts Additional options, like 'schema', 'context' or 'operationName' + * @return ExecutionResult + */ + public function queryAndReturnResult(string $query, ?array $params = [], array $opts = []): ExecutionResult { $context = Arr::get($opts, 'context'); $schemaName = Arr::get($opts, 'schema'); @@ -118,14 +138,14 @@ public function queryAndReturnResult($query, $params = [], $opts = []) return $result; } - public function addTypes($types) + public function addTypes(array $types): void { foreach ($types as $name => $type) { $this->addType($type, is_numeric($name) ? null : $name); } } - public function addType($class, $name = null) + public function addType(string $class, string $name = null): void { if (! $name) { $type = is_object($class) ? $class : app($class); @@ -135,7 +155,7 @@ public function addType($class, $name = null) $this->types[$name] = $class; } - public function type($name, $fresh = false) + public function type(string $name, bool $fresh = false): Type { if (! isset($this->types[$name])) { throw new RuntimeException('Type '.$name.' not found.'); @@ -156,7 +176,12 @@ public function type($name, $fresh = false) return $instance; } - public function objectType($type, $opts = []) + /** + * @param ObjectType|array|string $type + * @param array $opts + * @return ObjectType + */ + public function objectType($type, array $opts = []): ObjectType { // If it's already an ObjectType, just update properties and return it. // If it's an array, assume it's an array of fields and build ObjectType @@ -181,7 +206,12 @@ public function objectType($type, $opts = []) return $objectType; } - protected function buildObjectTypeFromClass($type, $opts = []) + /** + * @param ObjectType|string $type + * @param array $opts + * @return ObjectType + */ + protected function buildObjectTypeFromClass($type, array $opts = []): ObjectType { if (! is_object($type)) { $type = $this->app->make($type); @@ -194,7 +224,7 @@ protected function buildObjectTypeFromClass($type, $opts = []) return $type->toType(); } - protected function buildObjectTypeFromFields($fields, $opts = []) + protected function buildObjectTypeFromFields(array $fields, array $opts = []): ObjectType { $typeFields = []; foreach ($fields as $name => $field) { @@ -215,12 +245,20 @@ protected function buildObjectTypeFromFields($fields, $opts = []) ], $opts)); } - public function addSchema($name, $schema) + /** + * @param string $name + * @param Schema|array $schema + */ + public function addSchema(string $name, $schema): void { $this->mergeSchemas($name, $schema); } - public function mergeSchemas($name, $schema) + /** + * @param string $name + * @param Schema|array $schema + */ + public function mergeSchemas(string $name, $schema): void { if (isset($this->schemas[$name]) && $this->schemas[$name]) { $this->schemas[$name] = array_merge_recursive($this->schemas[$name], $schema); @@ -229,57 +267,49 @@ public function mergeSchemas($name, $schema) } } - public function clearType($name) + public function clearType(string $name): void { if (isset($this->types[$name])) { unset($this->types[$name]); } } - public function clearSchema($name) + public function clearSchema(string $name): void { if (isset($this->schemas[$name])) { unset($this->schemas[$name]); } } - public function clearTypes() + public function clearTypes(): void { $this->types = []; } - public function clearSchemas() + public function clearSchemas(): void { $this->schemas = []; } - public function getTypes() + /** + * @return array + */ + public function getTypes(): array { return $this->types; } - public function getSchemas() + public function getSchemas(): array { return $this->schemas; } - protected function clearTypeInstances() + protected function clearTypeInstances(): void { $this->typesInstances = []; } - protected function getTypeName($class, $name = null) - { - if ($name) { - return $name; - } - - $type = is_object($class) ? $class : $this->app->make($class); - - return $type->name; - } - - public function paginate($typeName, $customName = null) + public function paginate(string $typeName, string $customName = null): Type { $name = $customName ?: $typeName.'_pagination'; @@ -291,7 +321,12 @@ public function paginate($typeName, $customName = null) return $this->typesInstances[$name]; } - public static function formatError(Error $e) + /** + * @see \GraphQL\Executor\ExecutionResult::setErrorFormatter + * @param Error $e + * @return array + */ + public static function formatError(Error $e): array { $debug = config('app.debug') ? (Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE) : 0; $formatter = FormattedError::prepareFormatter(null, $debug); @@ -305,7 +340,12 @@ public static function formatError(Error $e) return $error; } - public static function handleErrors(array $errors, callable $formatter) + /** + * @param Error[] $errors + * @param callable $formatter + * @return Error[] + */ + public static function handleErrors(array $errors, callable $formatter): array { $handler = app()->make(ExceptionHandler::class); foreach ($errors as $error) { @@ -328,20 +368,20 @@ public static function handleErrors(array $errors, callable $formatter) * Eg. 'user/me' * will open the query path /graphql/user/me. * - * @param $name - * @param $schemaParameterPattern - * @param $queryRoute + * @param string $name + * @param string $schemaParameterPattern + * @param string $queryRoute * - * @return mixed + * @return string mixed */ - public static function routeNameTransformer($name, $schemaParameterPattern, $queryRoute) + public static function routeNameTransformer(string $name, string $schemaParameterPattern, string $queryRoute): string { $multiLevelPath = explode('/', $name); $routeName = null; if (count($multiLevelPath) > 1) { if (Helpers::isLumen()) { - array_walk($multiLevelPath, function (&$multiName) { + array_walk($multiLevelPath, function (string &$multiName): void { $multiName = "$multiName:$multiName"; }); } @@ -357,7 +397,11 @@ public static function routeNameTransformer($name, $schemaParameterPattern, $que return $routeName ?: preg_replace($schemaParameterPattern, '{'.(Helpers::isLumen() ? "$name:$name" : $name).'}', $queryRoute); } - protected function getSchemaConfiguration($schema) + /** + * @param array|string $schema + * @return array + */ + protected function getSchemaConfiguration($schema): array { $schemaName = is_string($schema) ? $schema : config('graphql.default_schema', 'default'); diff --git a/src/Rebing/GraphQL/GraphQLController.php b/src/Rebing/GraphQL/GraphQLController.php index ffdeb3f1..f469dc9e 100644 --- a/src/Rebing/GraphQL/GraphQLController.php +++ b/src/Rebing/GraphQL/GraphQLController.php @@ -7,11 +7,13 @@ use Exception; use Illuminate\Support\Arr; use Illuminate\Http\Request; +use Illuminate\Http\JsonResponse; use Illuminate\Routing\Controller; +use Illuminate\Contracts\View\View; class GraphQLController extends Controller { - public function query(Request $request, $schema = null) + public function query(Request $request, string $schema = null): JsonResponse { $middleware = new GraphQLUploadMiddleware(); $request = $middleware->processRequest($request); @@ -72,7 +74,7 @@ protected function queryContext() } } - public function graphiql(Request $request, $schema = null) + public function graphiql(Request $request, string $schema = null): View { $graphqlPath = '/'.config('graphql.prefix'); if ($schema) { diff --git a/src/Rebing/GraphQL/GraphQLLumenServiceProvider.php b/src/Rebing/GraphQL/GraphQLLumenServiceProvider.php index 9d47b94c..d9b30799 100644 --- a/src/Rebing/GraphQL/GraphQLLumenServiceProvider.php +++ b/src/Rebing/GraphQL/GraphQLLumenServiceProvider.php @@ -8,7 +8,7 @@ class GraphQLLumenServiceProvider extends GraphQLServiceProvider { - protected function bootPublishes() + protected function bootPublishes(): void { $configPath = __DIR__.'/../../config'; @@ -25,7 +25,7 @@ class_alias('Laravel\Lumen\Routing\Controller', 'Illuminate\Routing\Controller') parent::register(); } - public function registerConsole() + public function registerConsole(): void { parent::registerConsole(); diff --git a/src/Rebing/GraphQL/GraphQLServiceProvider.php b/src/Rebing/GraphQL/GraphQLServiceProvider.php index ce5fbe31..427bb6a9 100644 --- a/src/Rebing/GraphQL/GraphQLServiceProvider.php +++ b/src/Rebing/GraphQL/GraphQLServiceProvider.php @@ -11,6 +11,7 @@ use GraphQL\Validator\Rules\QueryComplexity; use Rebing\GraphQL\Console\QueryMakeCommand; use Rebing\GraphQL\Console\MutationMakeCommand; +use Illuminate\Contracts\Foundation\Application; use GraphQL\Validator\Rules\DisableIntrospection; class GraphQLServiceProvider extends ServiceProvider @@ -20,7 +21,7 @@ class GraphQLServiceProvider extends ServiceProvider * * @return void */ - public function boot() + public function boot(): void { $this->bootPublishes(); @@ -36,7 +37,7 @@ public function boot() * * @return void */ - protected function bootRouter() + protected function bootRouter(): void { if (config('graphql.routes')) { include __DIR__.'/routes.php'; @@ -48,7 +49,7 @@ protected function bootRouter() * * @return void */ - protected function bootPublishes() + protected function bootPublishes(): void { $configPath = __DIR__.'/../../config'; @@ -67,7 +68,7 @@ protected function bootPublishes() * * @return void */ - protected function bootTypes() + protected function bootTypes(): void { $configTypes = config('graphql.types'); $this->app['graphql']->addTypes($configTypes); @@ -78,7 +79,7 @@ protected function bootTypes() * * @return void */ - protected function bootSchemas() + protected function bootSchemas(): void { $configSchemas = config('graphql.schemas'); foreach ($configSchemas as $name => $schema) { @@ -91,7 +92,7 @@ protected function bootSchemas() * * @return void */ - protected function applySecurityRules() + protected function applySecurityRules(): void { $maxQueryComplexity = config('graphql.security.query_max_complexity'); if ($maxQueryComplexity !== null) { @@ -129,9 +130,9 @@ public function register() } } - public function registerGraphQL() + public function registerGraphQL(): void { - $this->app->singleton('graphql', function ($app) { + $this->app->singleton('graphql', function (Application $app): GraphQL { $graphql = new GraphQL($app); $this->applySecurityRules(); @@ -145,7 +146,7 @@ public function registerGraphQL() * * @return void */ - public function registerConsole() + public function registerConsole(): void { $this->commands(TypeMakeCommand::class); $this->commands(QueryMakeCommand::class); diff --git a/src/Rebing/GraphQL/GraphQLUploadMiddleware.php b/src/Rebing/GraphQL/GraphQLUploadMiddleware.php index 629f831f..bb6f17c2 100644 --- a/src/Rebing/GraphQL/GraphQLUploadMiddleware.php +++ b/src/Rebing/GraphQL/GraphQLUploadMiddleware.php @@ -34,7 +34,7 @@ public function handle(Request $request, Closure $next) * * @return \Illuminate\Http\Request */ - public function processRequest(Request $request) + public function processRequest(Request $request): Request { $contentType = $request->header('content-type') ?: ''; @@ -53,7 +53,7 @@ public function processRequest(Request $request) * * @return \Illuminate\Http\Request */ - private function parseUploadedFiles(Request $request) + private function parseUploadedFiles(Request $request): Request { $bodyParams = $request->all(); if (! isset($bodyParams['map'])) { @@ -90,8 +90,9 @@ private function parseUploadedFiles(Request $request) * Validates that the request meet our expectations. * * @param \Illuminate\Http\Request $request + * @return void */ - private function validateParsedBody(Request $request) + private function validateParsedBody(Request $request): void { $bodyParams = $request->all(); diff --git a/src/Rebing/GraphQL/Support/Field.php b/src/Rebing/GraphQL/Support/Field.php index ba7abff1..dd8d19bd 100644 --- a/src/Rebing/GraphQL/Support/Field.php +++ b/src/Rebing/GraphQL/Support/Field.php @@ -4,6 +4,7 @@ namespace Rebing\GraphQL\Support; +use Closure; use Validator; use Illuminate\Support\Arr; use Illuminate\Support\Fluent; @@ -13,6 +14,7 @@ use Rebing\GraphQL\Error\ValidationError; use GraphQL\Type\Definition\InputObjectType; use Rebing\GraphQL\Error\AuthorizationError; +use GraphQL\Type\Definition\Type as GraphqlType; class Field extends Fluent { @@ -20,21 +22,21 @@ class Field extends Fluent * Override this in your queries or mutations * to provide custom authorization. */ - public function authorize(array $args) + public function authorize(array $args): bool { return true; } - public function attributes() + public function attributes(): array { return []; } - public function type() + public function type(): GraphqlType { } - public function args() + public function args(): array { return []; } @@ -46,17 +48,17 @@ public function args() * * @return array */ - public function validationErrorMessages(array $args = []) + public function validationErrorMessages(array $args = []): array { return []; } - protected function rules(array $args = []) + protected function rules(array $args = []): array { return []; } - public function getRules() + public function getRules(): array { $arguments = func_get_args(); @@ -80,7 +82,12 @@ public function getRules() return array_merge($argsRules, $rules); } - public function resolveRules($rules, $arguments) + /** + * @param array|callable $rules + * @param array $arguments + * @return array + */ + public function resolveRules($rules, array $arguments): array { if (is_callable($rules)) { return call_user_func_array($rules, $arguments); @@ -89,7 +96,7 @@ public function resolveRules($rules, $arguments) return $rules; } - public function inferRulesFromType($type, $prefix, $resolutionArguments) + public function inferRulesFromType(GraphqlType $type, string $prefix, array $resolutionArguments): array { $rules = []; @@ -119,7 +126,7 @@ public function inferRulesFromType($type, $prefix, $resolutionArguments) return $rules; } - public function getInputTypeRules(InputObjectType $input, $prefix, $resolutionArguments) + public function getInputTypeRules(InputObjectType $input, string $prefix, array $resolutionArguments): array { $rules = []; @@ -146,10 +153,10 @@ public function getInputTypeRules(InputObjectType $input, $prefix, $resolutionAr return $rules; } - protected function getResolver() + protected function getResolver(): ?Closure { if (! method_exists($this, 'resolve')) { - return; + return null; } $resolver = [$this, 'resolve']; @@ -210,10 +217,7 @@ public function getAttributes() 'args' => $this->args(), ], $attributes); - $type = $this->type(); - if (isset($type)) { - $attributes['type'] = $type; - } + $attributes['type'] = $this->type(); $resolver = $this->getResolver(); if (isset($resolver)) { @@ -252,7 +256,7 @@ public function __get($key) * * @param string $key * - * @return void + * @return bool */ public function __isset($key) { diff --git a/src/Rebing/GraphQL/Support/InterfaceType.php b/src/Rebing/GraphQL/Support/InterfaceType.php index 60dc667c..b452547d 100644 --- a/src/Rebing/GraphQL/Support/InterfaceType.php +++ b/src/Rebing/GraphQL/Support/InterfaceType.php @@ -4,14 +4,16 @@ namespace Rebing\GraphQL\Support; +use Closure; +use GraphQL\Type\Definition\Type as GraphqlType; use GraphQL\Type\Definition\InterfaceType as BaseInterfaceType; class InterfaceType extends Type { - protected function getTypeResolver() + protected function getTypeResolver(): ?Closure { if (! method_exists($this, 'resolveType')) { - return; + return null; } $resolver = [$this, 'resolveType']; @@ -40,7 +42,7 @@ public function getAttributes() return $attributes; } - public function toType() + public function toType(): GraphqlType { return new BaseInterfaceType($this->toArray()); } diff --git a/src/Rebing/GraphQL/Support/PaginationType.php b/src/Rebing/GraphQL/Support/PaginationType.php index 64913af0..d5fedca4 100644 --- a/src/Rebing/GraphQL/Support/PaginationType.php +++ b/src/Rebing/GraphQL/Support/PaginationType.php @@ -4,6 +4,7 @@ namespace Rebing\GraphQL\Support; +use Illuminate\Support\Collection; use GraphQL\Type\Definition\ObjectType; use Rebing\GraphQL\Support\Facades\GraphQL; use Illuminate\Pagination\LengthAwarePaginator; @@ -11,7 +12,7 @@ class PaginationType extends ObjectType { - public function __construct($typeName, $customName = null) + public function __construct(string $typeName, string $customName = null) { $name = $customName ?: $typeName.'Pagination'; @@ -23,20 +24,20 @@ public function __construct($typeName, $customName = null) parent::__construct($config); } - protected function getPaginationFields($typeName) + protected function getPaginationFields(string $typeName): array { return [ 'data' => [ 'type' => GraphQLType::listOf(GraphQL::type($typeName)), 'description' => 'List of items on the current page', - 'resolve' => function (LengthAwarePaginator $data) { + 'resolve' => function (LengthAwarePaginator $data): Collection { return $data->getCollection(); }, ], 'total' => [ 'type' => GraphQLType::nonNull(GraphQLType::int()), 'description' => 'Number of total items selected by the query', - 'resolve' => function (LengthAwarePaginator $data) { + 'resolve' => function (LengthAwarePaginator $data): int { return $data->total(); }, 'selectable' => false, @@ -44,7 +45,7 @@ protected function getPaginationFields($typeName) 'per_page' => [ 'type' => GraphQLType::nonNull(GraphQLType::int()), 'description' => 'Number of items returned per page', - 'resolve' => function (LengthAwarePaginator $data) { + 'resolve' => function (LengthAwarePaginator $data): int { return $data->perPage(); }, 'selectable' => false, @@ -52,7 +53,7 @@ protected function getPaginationFields($typeName) 'current_page' => [ 'type' => GraphQLType::nonNull(GraphQLType::int()), 'description' => 'Current page of the cursor', - 'resolve' => function (LengthAwarePaginator $data) { + 'resolve' => function (LengthAwarePaginator $data): int { return $data->currentPage(); }, 'selectable' => false, @@ -60,7 +61,7 @@ protected function getPaginationFields($typeName) 'from' => [ 'type' => GraphQLType::int(), 'description' => 'Number of the first item returned', - 'resolve' => function (LengthAwarePaginator $data) { + 'resolve' => function (LengthAwarePaginator $data): ?int { return $data->firstItem(); }, 'selectable' => false, @@ -68,7 +69,7 @@ protected function getPaginationFields($typeName) 'to' => [ 'type' => GraphQLType::int(), 'description' => 'Number of the last item returned', - 'resolve' => function (LengthAwarePaginator $data) { + 'resolve' => function (LengthAwarePaginator $data): ?int { return $data->lastItem(); }, 'selectable' => false, @@ -76,7 +77,7 @@ protected function getPaginationFields($typeName) 'last_page' => [ 'type' => GraphQLType::nonNull(GraphQLType::int()), 'description' => 'The last page (number of pages)', - 'resolve' => function (LengthAwarePaginator $data) { + 'resolve' => function (LengthAwarePaginator $data): int { return $data->lastPage(); }, 'selectable' => false, @@ -84,7 +85,7 @@ protected function getPaginationFields($typeName) 'has_more_pages' => [ 'type' => GraphQLType::nonNull(GraphQLType::boolean()), 'description' => 'Determines if cursor has more pages after the current page', - 'resolve' => function (LengthAwarePaginator $data) { + 'resolve' => function (LengthAwarePaginator $data): bool { return $data->hasMorePages(); }, 'selectable' => false, diff --git a/src/Rebing/GraphQL/Support/SelectFields.php b/src/Rebing/GraphQL/Support/SelectFields.php index a06b6635..de25084b 100644 --- a/src/Rebing/GraphQL/Support/SelectFields.php +++ b/src/Rebing/GraphQL/Support/SelectFields.php @@ -13,6 +13,8 @@ use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\WrappingType; +use GraphQL\Type\Definition\FieldDefinition; +use GraphQL\Type\Definition\Type as GraphqlType; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; @@ -40,11 +42,11 @@ class SelectFields const FOREIGN_KEY = 'foreignKey'; /** - * @param ResolveInfo $info - * @param $parentType - * @param array $args - arguments given with the query + * @param ResolveInfo $info + * @param GraphqlType $parentType + * @param array $args - arguments given with the query */ - public function __construct(ResolveInfo $info, $parentType, array $args) + public function __construct(ResolveInfo $info, GraphqlType $parentType, array $args) { if ($parentType instanceof WrappingType) { $parentType = $parentType->getWrappedType(true); @@ -115,11 +117,15 @@ private function foldSelectionSet(SelectionSetNode $selectionSet, $descend) * Retrieve the fields (top level) and relations that * will be selected with the query. * - * @return array | Closure - if first recursion, return an array, + * @param array $requestedFields + * @param GraphqlType $parentType + * @param Closure|null $customQuery + * @param bool $topLevel + * @return array|Closure - if first recursion, return an array, * where the first key is 'select' array and second is 'with' array. * On other recursions return a closure that will be used in with */ - public static function getSelectableFieldsAndRelations(array $requestedFields, $parentType, $customQuery = null, $topLevel = true) + public static function getSelectableFieldsAndRelations(array $requestedFields, GraphqlType $parentType, ?Closure $customQuery = null, bool $topLevel = true) { $select = []; $with = []; @@ -163,8 +169,12 @@ public static function getSelectableFieldsAndRelations(array $requestedFields, $ /** * Get the selects and withs from the given fields * and recurse if necessary. + * @param array $requestedFields + * @param GraphqlType $parentType + * @param array $select Passed by reference, adds further fields to select + * @param array $with Passed by reference, adds further relations */ - protected static function handleFields(array $requestedFields, $parentType, array &$select, array &$with) + protected static function handleFields(array $requestedFields, GraphqlType $parentType, array &$select, array &$with): void { $parentTable = self::isMongodbInstance($parentType) ? null : self::getTableNameFromParentType($parentType); @@ -289,10 +299,11 @@ protected static function handleFields(array $requestedFields, $parentType, arra /** * Check the privacy status, if it's given. * - * @return bool | null - true, if selectable; false, if not selectable, but allowed; + * @param FieldDefinition $fieldObject + * @return bool|null - true, if selectable; false, if not selectable, but allowed; * null, if not allowed */ - protected static function validateField($fieldObject) + protected static function validateField(FieldDefinition $fieldObject): ?bool { $selectable = true; @@ -329,19 +340,24 @@ protected static function validateField($fieldObject) /** * Determines whether the fieldObject is queryable. * - * @param $fieldObject + * @param array $fieldObject * * @return bool */ - private static function isQueryable($fieldObject) + private static function isQueryable(array $fieldObject): bool { return Arr::get($fieldObject, 'is_relation', true) === true; } /** * Add selects that are given by the 'always' attribute. + * + * @param FieldDefinition $fieldObject + * @param array $select Passed by reference, adds further fields to select + * @param string|null $parentTable + * @param bool $forRelation */ - protected static function addAlwaysFields($fieldObject, array &$select, $parentTable, $forRelation = false) + protected static function addAlwaysFields(FieldDefinition $fieldObject, array &$select, ?string $parentTable, bool $forRelation = false): void { if (isset($fieldObject->config['always'])) { $always = $fieldObject->config['always']; @@ -357,7 +373,13 @@ protected static function addAlwaysFields($fieldObject, array &$select, $parentT } } - protected static function addFieldToSelect($field, &$select, $parentTable, $forRelation) + /** + * @param string $field + * @param array $select Passed by reference, adds further fields to select + * @param string|null $parentTable + * @param bool $forRelation + */ + protected static function addFieldToSelect(string $field, array &$select, ?string $parentTable, bool $forRelation): void { if ($forRelation && ! array_key_exists($field, $select)) { $select[$field] = true; @@ -369,34 +391,34 @@ protected static function addFieldToSelect($field, &$select, $parentTable, $forR } } - private static function getPrimaryKeyFromParentType($parentType) + private static function getPrimaryKeyFromParentType(GraphqlType $parentType): ?string { return isset($parentType->config['model']) ? app($parentType->config['model'])->getKeyName() : null; } - private static function getTableNameFromParentType($parentType) + private static function getTableNameFromParentType(GraphqlType $parentType): ?string { return isset($parentType->config['model']) ? app($parentType->config['model'])->getTable() : null; } - private static function isMongodbInstance($parentType) + private static function isMongodbInstance(GraphqlType $parentType): bool { $mongoType = 'Jenssegers\Mongodb\Eloquent\Model'; return isset($parentType->config['model']) ? app($parentType->config['model']) instanceof $mongoType : false; } - public function getSelect() + public function getSelect(): array { return $this->select; } - public function getRelations() + public function getRelations(): array { return $this->relations; } - public function getResolveInfo() + public function getResolveInfo(): ResolveInfo { return $this->info; } diff --git a/src/Rebing/GraphQL/Support/Type.php b/src/Rebing/GraphQL/Support/Type.php index 1fc0f2c0..25644db4 100644 --- a/src/Rebing/GraphQL/Support/Type.php +++ b/src/Rebing/GraphQL/Support/Type.php @@ -10,31 +10,37 @@ use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Type\Definition\InputObjectType; +use GraphQL\Type\Definition\Type as GraphqlType; class Type extends Fluent { - protected static $instances = []; - + /** + * Set to `true` in your type when it should reflect an InputObject. + * @var bool + */ protected $inputObject = false; + /** + * Set to `true` in your type when it should reflect an Enum. + * @var bool + */ protected $enumObject = false; - protected $unionType = false; - public function attributes() + public function attributes(): array { return []; } - public function fields() + public function fields(): array { return []; } - public function interfaces() + public function interfaces(): array { return []; } - protected function getFieldResolver($name, $field) + protected function getFieldResolver(string $name, array $field): ?callable { if (isset($field['resolve'])) { return $field['resolve']; @@ -59,9 +65,11 @@ protected function getFieldResolver($name, $field) return $type->{$alias}; }; } + + return null; } - public function getFields() + public function getFields(): array { $fields = $this->fields(); $allFields = []; @@ -117,7 +125,7 @@ public function toArray() return $this->getAttributes(); } - public function toType() + public function toType(): GraphqlType { if ($this->inputObject) { return new InputObjectType($this->toArray()); diff --git a/src/Rebing/GraphQL/Support/UnionType.php b/src/Rebing/GraphQL/Support/UnionType.php index 8f4e8810..b14ab5e8 100644 --- a/src/Rebing/GraphQL/Support/UnionType.php +++ b/src/Rebing/GraphQL/Support/UnionType.php @@ -4,6 +4,7 @@ namespace Rebing\GraphQL\Support; +use GraphQL\Type\Definition\Type as GraphqlType; use GraphQL\Type\Definition\UnionType as BaseUnionType; class UnionType extends Type @@ -34,7 +35,7 @@ public function getAttributes() return $attributes; } - public function toType() + public function toType(): GraphqlType { return new BaseUnionType($this->toArray()); } diff --git a/src/Rebing/GraphQL/Support/UploadType.php b/src/Rebing/GraphQL/Support/UploadType.php index ab84ec8e..1f39f1b3 100644 --- a/src/Rebing/GraphQL/Support/UploadType.php +++ b/src/Rebing/GraphQL/Support/UploadType.php @@ -5,7 +5,6 @@ namespace Rebing\GraphQL\Support; use GraphQL\Error\Error; -use GraphQL\Language\AST\Node; use GraphQL\Error\InvariantViolation; use GraphQL\Type\Definition\ScalarType; @@ -22,7 +21,7 @@ class UploadType extends ScalarType 'The `Upload` special type represents a file to be uploaded in the same HTTP request as specified by [graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec).'; - public function __construct($name = 'Upload') + public function __construct(string $name = 'Upload') { $this->name = $name; @@ -30,11 +29,7 @@ public function __construct($name = 'Upload') } /** - * Serializes an internal value to include in a response. - * - * @param mixed $value - * - * @return mixed + * {@inheritdoc} */ public function serialize($value) { @@ -42,11 +37,7 @@ public function serialize($value) } /** - * Parses an externally provided value (query variable) to use as an input. - * - * @param mixed $value - * - * @return mixed + * {@inheritdoc} */ public function parseValue($value) { @@ -54,11 +45,7 @@ public function parseValue($value) } /** - * Parses an externally provided literal value (hardcoded in GraphQL query) to use as an input. - * - * @param Node $valueNode - * - * @return mixed + * {@inheritdoc} */ public function parseLiteral($valueNode, ?array $variables = null) { diff --git a/src/Rebing/GraphQL/routes.php b/src/Rebing/GraphQL/routes.php index bf3d810a..e89f39d7 100644 --- a/src/Rebing/GraphQL/routes.php +++ b/src/Rebing/GraphQL/routes.php @@ -4,6 +4,7 @@ use Illuminate\Support\Arr; use Rebing\GraphQL\Helpers; +use Illuminate\Routing\Router; use Rebing\GraphQL\GraphQLController; $router = app('router'); @@ -11,7 +12,7 @@ $router->group(array_merge([ 'prefix' => config('graphql.prefix'), 'middleware' => config('graphql.middleware', []), -], config('graphql.route_group_attributes', [])), function ($router) { +], config('graphql.route_group_attributes', [])), function (Router $router): void { // Routes $routes = config('graphql.routes'); $queryRoute = null; @@ -149,7 +150,7 @@ $router->group([ 'prefix' => config('graphql.graphiql.prefix', 'graphiql'), 'middleware' => config('graphql.graphiql.middleware', []), - ], function ($router) { + ], function (Router $router): void { $graphiqlController = config('graphql.graphiql.controller', GraphQLController::class.'@graphiql'); $schemaParameterPattern = '/\{\s*graphql\_schema\s*\?\s*\}/'; $graphiqlAction = ['uses' => $graphiqlController]; diff --git a/src/resources/views/graphiql.php b/src/resources/views/graphiql.php index 6e59d05b..7e51e645 100644 --- a/src/resources/views/graphiql.php +++ b/src/resources/views/graphiql.php @@ -79,6 +79,8 @@ function updateURL() { history.replaceState(null, null, newSearch); } + var xcrsfToken = null; + // Defines a GraphQL fetcher using the fetch API. function graphQLFetcher(graphQLParams) { return fetch('', { @@ -86,11 +88,13 @@ function graphQLFetcher(graphQLParams) { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': '' + 'X-CSRF-TOKEN': xcrsfToken || '' }, body: JSON.stringify(graphQLParams), credentials: 'include', }).then(function (response) { + xcrsfToken = response.headers.get('x-csrf-token'); + return response.text(); }).then(function (responseBody) { try { @@ -116,4 +120,4 @@ function graphQLFetcher(graphQLParams) { ); - \ No newline at end of file + diff --git a/tests/Database/MutationValidationUniqueWithCustomRulesTests/MutationValidationUniqueWithCustomRulesTest.php b/tests/Database/MutationValidationUniqueWithCustomRulesTests/MutationValidationUniqueWithCustomRulesTest.php new file mode 100644 index 00000000..292e7971 --- /dev/null +++ b/tests/Database/MutationValidationUniqueWithCustomRulesTests/MutationValidationUniqueWithCustomRulesTest.php @@ -0,0 +1,161 @@ +create([ + 'name' => 'name_unique', + ]); + + $graphql = <<<'GRAPHQL' +mutation Mutate($arg_unique_rule_pass: String) { + mutationWithCustomRuleWithRuleObject(arg_unique_rule_pass: $arg_unique_rule_pass) +} +GRAPHQL; + + $this->sqlCounterReset(); + + $result = GraphQL::query($graphql, [ + 'arg_unique_rule_pass' => 'another_name', + ]); + + $this->assertSqlQueries(<<<'SQL' +select count(*) as aggregate from "users" where "name" = ?; +SQL + ); + + $expectedResult = [ + 'data' => [ + 'mutationWithCustomRuleWithRuleObject' => 'mutation result', + ], + ]; + $this->assertSame($expectedResult, $result); + } + + public function testUniqueFailRulePass(): void + { + /* @var User $user */ + factory(User::class) + ->create([ + 'name' => 'name_unique', + ]); + + $graphql = <<<'GRAPHQL' +mutation Mutate($arg_unique_rule_pass: String) { + mutationWithCustomRuleWithRuleObject(arg_unique_rule_pass: $arg_unique_rule_pass) +} +GRAPHQL; + + $this->sqlCounterReset(); + + $result = GraphQL::query($graphql, [ + 'arg_unique_rule_pass' => 'name_unique', + ]); + + $this->assertSqlQueries(<<<'SQL' +select count(*) as aggregate from "users" where "name" = ?; +SQL + ); + + $this->assertCount(1, $result['errors']); + $this->assertSame('validation', $result['errors'][0]['message']); + /** @var MessageBag $messageBag */ + $messageBag = $result['errors'][0]['extensions']['validation']; + $expectedMessages = [ + 'The arg unique rule pass has already been taken.', + ]; + $this->assertSame($expectedMessages, $messageBag->all()); + } + + public function testUniquePassRuleFail(): void + { + /* @var User $user */ + factory(User::class) + ->create([ + 'name' => 'name_unique', + ]); + + $graphql = <<<'GRAPHQL' +mutation Mutate($arg_unique_rule_fail: String) { + mutationWithCustomRuleWithRuleObject(arg_unique_rule_fail: $arg_unique_rule_fail) +} +GRAPHQL; + + $this->sqlCounterReset(); + + $result = GraphQL::query($graphql, [ + 'arg_unique_rule_fail' => 'another_name', + ]); + + $this->assertCount(1, $result['errors']); + $this->assertSame('validation', $result['errors'][0]['message']); + /** @var MessageBag $messageBag */ + $messageBag = $result['errors'][0]['extensions']['validation']; + $expectedMessages = [ + 'rule object validation fails', + ]; + $this->assertSame($expectedMessages, $messageBag->all()); + } + + public function testUniqueFailRuleFail(): void + { + /* @var User $user */ + factory(User::class) + ->create([ + 'name' => 'name_unique', + ]); + + $graphql = <<<'GRAPHQL' +mutation Mutate($arg_unique_rule_fail: String) { + mutationWithCustomRuleWithRuleObject(arg_unique_rule_fail: $arg_unique_rule_fail) +} +GRAPHQL; + + $this->sqlCounterReset(); + + $result = GraphQL::query($graphql, [ + 'arg_unique_rule_fail' => 'name_unique', + ]); + + $this->assertSqlQueries(<<<'SQL' +select count(*) as aggregate from "users" where "name" = ?; +SQL + ); + + $this->assertCount(1, $result['errors']); + $this->assertSame('validation', $result['errors'][0]['message']); + /** @var MessageBag $messageBag */ + $messageBag = $result['errors'][0]['extensions']['validation']; + $expectedMessages = [ + 'The arg unique rule fail has already been taken.', + 'rule object validation fails', + ]; + $this->assertSame($expectedMessages, $messageBag->all()); + } + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + $app['config']->set('graphql.schemas.default', [ + 'mutation' => [ + MutationWithCustomRuleWithRuleObject::class, + ], + ]); + } +} diff --git a/tests/Database/MutationValidationUniqueWithCustomRulesTests/MutationWithCustomRuleWithRuleObject.php b/tests/Database/MutationValidationUniqueWithCustomRulesTests/MutationWithCustomRuleWithRuleObject.php new file mode 100644 index 00000000..aa9e33d6 --- /dev/null +++ b/tests/Database/MutationValidationUniqueWithCustomRulesTests/MutationWithCustomRuleWithRuleObject.php @@ -0,0 +1,51 @@ + 'mutationWithCustomRuleWithRuleObject', + ]; + + public function type(): Type + { + return Type::nonNull(Type::string()); + } + + protected function rules(array $args = []): array + { + return [ + 'arg_unique_rule_pass' => [ + 'unique:users,name', + new RuleObjectPass(), + ], + 'arg_unique_rule_fail' => [ + 'unique:users,name', + new RuleObjectFail(), + ], + ]; + } + + public function args(): array + { + return [ + 'arg_unique_rule_pass' => [ + 'type' => Type::string(), + ], + 'arg_unique_rule_fail' => [ + 'type' => Type::string(), + ], + ]; + } + + public function resolve($root, $args): string + { + return 'mutation result'; + } +} diff --git a/tests/Database/MutationValidationUniqueWithCustomRulesTests/RuleObjectFail.php b/tests/Database/MutationValidationUniqueWithCustomRulesTests/RuleObjectFail.php new file mode 100644 index 00000000..304cc0b8 --- /dev/null +++ b/tests/Database/MutationValidationUniqueWithCustomRulesTests/RuleObjectFail.php @@ -0,0 +1,20 @@ + 'exampleInterfaceQuery', ]; - public function type() + public function type(): Type { return Type::listOf(GraphQL::type('ExampleInterface')); } diff --git a/tests/Database/SelectFields/InterfaceTests/ExampleInterfaceType.php b/tests/Database/SelectFields/InterfaceTests/ExampleInterfaceType.php index 65bd39b9..b6e52ede 100644 --- a/tests/Database/SelectFields/InterfaceTests/ExampleInterfaceType.php +++ b/tests/Database/SelectFields/InterfaceTests/ExampleInterfaceType.php @@ -14,7 +14,7 @@ class ExampleInterfaceType extends InterfaceType 'name' => 'ExampleInterface', ]; - public function fields() + public function fields(): array { return [ 'title' => [ diff --git a/tests/Database/SelectFields/InterfaceTests/InterfaceImpl1Type.php b/tests/Database/SelectFields/InterfaceTests/InterfaceImpl1Type.php index fe22f1a0..a571dfd3 100644 --- a/tests/Database/SelectFields/InterfaceTests/InterfaceImpl1Type.php +++ b/tests/Database/SelectFields/InterfaceTests/InterfaceImpl1Type.php @@ -14,7 +14,7 @@ class InterfaceImpl1Type extends GraphQLType 'name' => 'InterfaceImpl1', ]; - public function fields() + public function fields(): array { return [ 'title' => [ @@ -23,7 +23,7 @@ public function fields() ]; } - public function interfaces() + public function interfaces(): array { return [ GraphQL::type('ExampleInterface'), diff --git a/tests/Database/SelectFields/NestedRelationLoadingTests/CommentType.php b/tests/Database/SelectFields/NestedRelationLoadingTests/CommentType.php index 9b6794c1..f14e6d2b 100644 --- a/tests/Database/SelectFields/NestedRelationLoadingTests/CommentType.php +++ b/tests/Database/SelectFields/NestedRelationLoadingTests/CommentType.php @@ -15,7 +15,7 @@ class CommentType extends GraphQLType 'model' => Comment::class, ]; - public function fields() + public function fields(): array { return [ 'id' => [ diff --git a/tests/Database/SelectFields/NestedRelationLoadingTests/PostType.php b/tests/Database/SelectFields/NestedRelationLoadingTests/PostType.php index a1317e2e..822ed6d1 100644 --- a/tests/Database/SelectFields/NestedRelationLoadingTests/PostType.php +++ b/tests/Database/SelectFields/NestedRelationLoadingTests/PostType.php @@ -17,7 +17,7 @@ class PostType extends GraphQLType 'model' => Post::class, ]; - public function fields() + public function fields(): array { return [ 'body' => [ diff --git a/tests/Database/SelectFields/NestedRelationLoadingTests/UserType.php b/tests/Database/SelectFields/NestedRelationLoadingTests/UserType.php index b6f9675f..44525eab 100644 --- a/tests/Database/SelectFields/NestedRelationLoadingTests/UserType.php +++ b/tests/Database/SelectFields/NestedRelationLoadingTests/UserType.php @@ -17,7 +17,7 @@ class UserType extends GraphQLType 'model' => User::class, ]; - public function fields() + public function fields(): array { return [ 'id' => [ diff --git a/tests/Database/SelectFields/NestedRelationLoadingTests/UsersQuery.php b/tests/Database/SelectFields/NestedRelationLoadingTests/UsersQuery.php index 4d249f78..3ae962d1 100644 --- a/tests/Database/SelectFields/NestedRelationLoadingTests/UsersQuery.php +++ b/tests/Database/SelectFields/NestedRelationLoadingTests/UsersQuery.php @@ -16,7 +16,7 @@ class UsersQuery extends Query 'name' => 'users', ]; - public function args() + public function args(): array { return [ 'select' => [ @@ -28,7 +28,7 @@ public function args() ]; } - public function type() + public function type(): Type { return Type::nonNull(Type::listOf(Type::nonNull(GraphQL::type('User')))); } diff --git a/tests/Support/Objects/CustomExampleType.php b/tests/Support/Objects/CustomExampleType.php index ab590e3f..dec9b456 100644 --- a/tests/Support/Objects/CustomExampleType.php +++ b/tests/Support/Objects/CustomExampleType.php @@ -14,7 +14,7 @@ class CustomExampleType extends GraphQLType 'description' => 'An example', ]; - public function fields() + public function fields(): array { return [ 'test' => [ diff --git a/tests/Support/Objects/ExampleEnumType.php b/tests/Support/Objects/ExampleEnumType.php index 959da7e2..11ff4c34 100644 --- a/tests/Support/Objects/ExampleEnumType.php +++ b/tests/Support/Objects/ExampleEnumType.php @@ -22,7 +22,7 @@ class ExampleEnumType extends GraphQLType ], ]; - public function fields() + public function fields(): array { return [ 'test' => [ diff --git a/tests/Support/Objects/ExampleField.php b/tests/Support/Objects/ExampleField.php index 974ff715..04836a0c 100644 --- a/tests/Support/Objects/ExampleField.php +++ b/tests/Support/Objects/ExampleField.php @@ -13,12 +13,12 @@ class ExampleField extends Field 'name' => 'example', ]; - public function type() + public function type(): Type { return Type::listOf(Type::string()); } - public function args() + public function args(): array { return [ 'index' => [ diff --git a/tests/Support/Objects/ExampleFilterInputType.php b/tests/Support/Objects/ExampleFilterInputType.php index 971c6ec8..b3a80088 100644 --- a/tests/Support/Objects/ExampleFilterInputType.php +++ b/tests/Support/Objects/ExampleFilterInputType.php @@ -17,7 +17,7 @@ class ExampleFilterInputType extends GraphQLType 'description' => 'A nested filter input with self reference', ]; - public function fields() + public function fields(): array { return [ 'AND' => [ diff --git a/tests/Support/Objects/ExampleInputType.php b/tests/Support/Objects/ExampleInputType.php index 2edd6fcd..afccee2c 100644 --- a/tests/Support/Objects/ExampleInputType.php +++ b/tests/Support/Objects/ExampleInputType.php @@ -16,7 +16,7 @@ class ExampleInputType extends GraphQLType 'description' => 'An example input', ]; - public function fields() + public function fields(): array { return [ 'test' => [ diff --git a/tests/Support/Objects/ExampleInterfaceType.php b/tests/Support/Objects/ExampleInterfaceType.php index b51e8c13..f5299eba 100644 --- a/tests/Support/Objects/ExampleInterfaceType.php +++ b/tests/Support/Objects/ExampleInterfaceType.php @@ -20,7 +20,7 @@ public function resolveType($root): StringType return Type::string(); } - public function fields() + public function fields(): array { return [ 'test' => [ diff --git a/tests/Support/Objects/ExampleNestedValidationInputObject.php b/tests/Support/Objects/ExampleNestedValidationInputObject.php index a3a62fee..1a2757b7 100644 --- a/tests/Support/Objects/ExampleNestedValidationInputObject.php +++ b/tests/Support/Objects/ExampleNestedValidationInputObject.php @@ -21,7 +21,7 @@ public function type(): ListOfType return Type::listOf(Type::string()); } - public function fields() + public function fields(): array { return [ 'email' => [ diff --git a/tests/Support/Objects/ExampleType.php b/tests/Support/Objects/ExampleType.php index 2b3da387..f44ef2a4 100644 --- a/tests/Support/Objects/ExampleType.php +++ b/tests/Support/Objects/ExampleType.php @@ -14,7 +14,7 @@ class ExampleType extends GraphQLType 'description' => 'An example', ]; - public function fields() + public function fields(): array { return [ 'test' => [ diff --git a/tests/Support/Objects/ExampleUnionType.php b/tests/Support/Objects/ExampleUnionType.php index b351b9cf..fc343eee 100644 --- a/tests/Support/Objects/ExampleUnionType.php +++ b/tests/Support/Objects/ExampleUnionType.php @@ -27,7 +27,7 @@ public function resolveType($root) return GraphQL::type('Example'); } - public function fields() + public function fields(): array { return [ 'test' => [ diff --git a/tests/Support/Objects/ExampleValidationField.php b/tests/Support/Objects/ExampleValidationField.php index a082c3ad..f1d97774 100644 --- a/tests/Support/Objects/ExampleValidationField.php +++ b/tests/Support/Objects/ExampleValidationField.php @@ -13,12 +13,12 @@ class ExampleValidationField extends Field 'name' => 'example_validation', ]; - public function type() + public function type(): Type { return Type::listOf(Type::string()); } - public function args() + public function args(): array { return [ 'index' => [ diff --git a/tests/Support/Objects/ExampleValidationInputObject.php b/tests/Support/Objects/ExampleValidationInputObject.php index 59fb943b..06e9da48 100644 --- a/tests/Support/Objects/ExampleValidationInputObject.php +++ b/tests/Support/Objects/ExampleValidationInputObject.php @@ -22,7 +22,7 @@ public function type(): ListOfType return Type::listOf(Type::string()); } - public function fields() + public function fields(): array { return [ 'val' => [ diff --git a/tests/Support/Objects/ExamplesAuthorizeQuery.php b/tests/Support/Objects/ExamplesAuthorizeQuery.php index a20a4da3..354ccef9 100644 --- a/tests/Support/Objects/ExamplesAuthorizeQuery.php +++ b/tests/Support/Objects/ExamplesAuthorizeQuery.php @@ -14,17 +14,17 @@ class ExamplesAuthorizeQuery extends Query 'name' => 'Examples authorize query', ]; - public function authorize(array $args) + public function authorize(array $args): bool { return false; } - public function type() + public function type(): Type { return Type::listOf(GraphQL::type('Example')); } - public function args() + public function args(): array { return [ 'index' => ['name' => 'index', 'type' => Type::int()], diff --git a/tests/Support/Objects/ExamplesFilteredQuery.php b/tests/Support/Objects/ExamplesFilteredQuery.php index 8dbd9eb4..f13d462c 100644 --- a/tests/Support/Objects/ExamplesFilteredQuery.php +++ b/tests/Support/Objects/ExamplesFilteredQuery.php @@ -14,12 +14,12 @@ class ExamplesFilteredQuery extends Query 'name' => 'Filtered examples', ]; - public function type() + public function type(): Type { return Type::listOf(GraphQL::type('Example')); } - public function args() + public function args(): array { return [ 'filter' => [ diff --git a/tests/Support/Objects/ExamplesPaginationQuery.php b/tests/Support/Objects/ExamplesPaginationQuery.php index f13988d5..b5d5c55d 100644 --- a/tests/Support/Objects/ExamplesPaginationQuery.php +++ b/tests/Support/Objects/ExamplesPaginationQuery.php @@ -15,12 +15,12 @@ class ExamplesPaginationQuery extends Query 'name' => 'Examples with pagination', ]; - public function type() + public function type(): Type { return GraphQL::paginate('Example'); } - public function args() + public function args(): array { return [ 'take' => [ diff --git a/tests/Support/Objects/ExamplesQuery.php b/tests/Support/Objects/ExamplesQuery.php index e8a77aea..604c75d8 100644 --- a/tests/Support/Objects/ExamplesQuery.php +++ b/tests/Support/Objects/ExamplesQuery.php @@ -14,12 +14,12 @@ class ExamplesQuery extends Query 'name' => 'examples', ]; - public function type() + public function type(): Type { return Type::listOf(GraphQL::type('Example')); } - public function args() + public function args(): array { return [ 'index' => ['name' => 'index', 'type' => Type::int()], diff --git a/tests/Support/Objects/UpdateExampleMutation.php b/tests/Support/Objects/UpdateExampleMutation.php index f749f93a..d9710cc4 100644 --- a/tests/Support/Objects/UpdateExampleMutation.php +++ b/tests/Support/Objects/UpdateExampleMutation.php @@ -15,19 +15,19 @@ class UpdateExampleMutation extends Mutation 'name' => 'updateExample', ]; - public function type() + public function type(): Type { return GraphQL::type('Example'); } - public function rules(array $args = []) + protected function rules(array $args = []): array { return [ 'test' => ['required'], ]; } - public function args() + public function args(): array { return [ 'test' => [ diff --git a/tests/Support/Objects/UpdateExampleMutationWithInputType.php b/tests/Support/Objects/UpdateExampleMutationWithInputType.php index 267ec77c..7e34d8f0 100644 --- a/tests/Support/Objects/UpdateExampleMutationWithInputType.php +++ b/tests/Support/Objects/UpdateExampleMutationWithInputType.php @@ -15,19 +15,19 @@ class UpdateExampleMutationWithInputType extends Mutation 'name' => 'updateExample', ]; - public function type() + public function type(): Type { return GraphQL::type('Example'); } - public function rules(array $args = []) + protected function rules(array $args = []): array { return [ 'test' => ['required'], ]; } - public function args() + public function args(): array { return [ 'test' => [ diff --git a/tests/Support/Queries/PostNonNullWithSelectFieldsAndModelQuery.php b/tests/Support/Queries/PostNonNullWithSelectFieldsAndModelQuery.php index da55b847..ec81e0fe 100644 --- a/tests/Support/Queries/PostNonNullWithSelectFieldsAndModelQuery.php +++ b/tests/Support/Queries/PostNonNullWithSelectFieldsAndModelQuery.php @@ -16,12 +16,12 @@ class PostNonNullWithSelectFieldsAndModelQuery extends Query 'name' => 'postNonNullWithSelectFieldsAndModel', ]; - public function type() + public function type(): Type { return Type::nonNull(GraphQL::type('PostWithModel')); } - public function args() + public function args(): array { return [ 'id' => [ diff --git a/tests/Support/Queries/PostQuery.php b/tests/Support/Queries/PostQuery.php index f501d53e..ea548e42 100644 --- a/tests/Support/Queries/PostQuery.php +++ b/tests/Support/Queries/PostQuery.php @@ -16,12 +16,12 @@ class PostQuery extends Query 'name' => 'post', ]; - public function type() + public function type(): Type { return GraphQL::type('Post'); } - public function args() + public function args(): array { return [ 'id' => [ diff --git a/tests/Support/Queries/PostWithSelectFieldsAndModelAndAliasAndCustomResolverQuery.php b/tests/Support/Queries/PostWithSelectFieldsAndModelAndAliasAndCustomResolverQuery.php index 7f5b3ae7..25f81362 100644 --- a/tests/Support/Queries/PostWithSelectFieldsAndModelAndAliasAndCustomResolverQuery.php +++ b/tests/Support/Queries/PostWithSelectFieldsAndModelAndAliasAndCustomResolverQuery.php @@ -16,12 +16,12 @@ class PostWithSelectFieldsAndModelAndAliasAndCustomResolverQuery extends Query 'name' => 'postWithSelectFieldsAndModelAndAliasAndCustomResolver', ]; - public function type() + public function type(): Type { return GraphQL::type('PostWithModelAndAliasAndCustomResolver'); } - public function args() + public function args(): array { return [ 'id' => [ diff --git a/tests/Support/Queries/PostWithSelectFieldsAndModelAndAliasQuery.php b/tests/Support/Queries/PostWithSelectFieldsAndModelAndAliasQuery.php index 443890fd..f999cdf0 100644 --- a/tests/Support/Queries/PostWithSelectFieldsAndModelAndAliasQuery.php +++ b/tests/Support/Queries/PostWithSelectFieldsAndModelAndAliasQuery.php @@ -16,12 +16,12 @@ class PostWithSelectFieldsAndModelAndAliasQuery extends Query 'name' => 'postWithSelectFieldsAndModelAndAlias', ]; - public function type() + public function type(): Type { return GraphQL::type('PostWithModelAndAlias'); } - public function args() + public function args(): array { return [ 'id' => [ diff --git a/tests/Support/Queries/PostWithSelectFieldsAndModelQuery.php b/tests/Support/Queries/PostWithSelectFieldsAndModelQuery.php index 338fa1ce..fffb74d0 100644 --- a/tests/Support/Queries/PostWithSelectFieldsAndModelQuery.php +++ b/tests/Support/Queries/PostWithSelectFieldsAndModelQuery.php @@ -16,12 +16,12 @@ class PostWithSelectFieldsAndModelQuery extends Query 'name' => 'postWithSelectFieldsAndModel', ]; - public function type() + public function type(): Type { return GraphQL::type('PostWithModel'); } - public function args() + public function args(): array { return [ 'id' => [ diff --git a/tests/Support/Queries/PostWithSelectFieldsNoModelQuery.php b/tests/Support/Queries/PostWithSelectFieldsNoModelQuery.php index d4e7d08e..a532b852 100644 --- a/tests/Support/Queries/PostWithSelectFieldsNoModelQuery.php +++ b/tests/Support/Queries/PostWithSelectFieldsNoModelQuery.php @@ -16,12 +16,12 @@ class PostWithSelectFieldsNoModelQuery extends Query 'name' => 'postWithSelectFieldsNoModel', ]; - public function type() + public function type(): Type { return GraphQL::type('Post'); } - public function args() + public function args(): array { return [ 'id' => [ diff --git a/tests/Support/Queries/PostsListOfWithSelectFieldsAndModelQuery.php b/tests/Support/Queries/PostsListOfWithSelectFieldsAndModelQuery.php index 47af7579..98895371 100644 --- a/tests/Support/Queries/PostsListOfWithSelectFieldsAndModelQuery.php +++ b/tests/Support/Queries/PostsListOfWithSelectFieldsAndModelQuery.php @@ -16,7 +16,7 @@ class PostsListOfWithSelectFieldsAndModelQuery extends Query 'name' => 'postsListOfWithSelectFieldsAndModel', ]; - public function type() + public function type(): Type { return Type::listOf(GraphQL::type('PostWithModel')); } diff --git a/tests/Support/Queries/PostsNonNullAndListAndNonNullOfWithSelectFieldsAndModelQuery.php b/tests/Support/Queries/PostsNonNullAndListAndNonNullOfWithSelectFieldsAndModelQuery.php index aa12d9e9..0dde3de6 100644 --- a/tests/Support/Queries/PostsNonNullAndListAndNonNullOfWithSelectFieldsAndModelQuery.php +++ b/tests/Support/Queries/PostsNonNullAndListAndNonNullOfWithSelectFieldsAndModelQuery.php @@ -16,7 +16,7 @@ class PostsNonNullAndListAndNonNullOfWithSelectFieldsAndModelQuery extends Query 'name' => 'postsNonNullAndListAndNonNullOfWithSelectFieldsAndModel', ]; - public function type() + public function type(): Type { return Type::nonNull(Type::listOf(Type::nonNull(GraphQL::type('PostWithModel')))); } diff --git a/tests/Support/Queries/PostsNonNullAndListOfWithSelectFieldsAndModelQuery.php b/tests/Support/Queries/PostsNonNullAndListOfWithSelectFieldsAndModelQuery.php index cdd5317b..682820f5 100644 --- a/tests/Support/Queries/PostsNonNullAndListOfWithSelectFieldsAndModelQuery.php +++ b/tests/Support/Queries/PostsNonNullAndListOfWithSelectFieldsAndModelQuery.php @@ -16,7 +16,7 @@ class PostsNonNullAndListOfWithSelectFieldsAndModelQuery extends Query 'name' => 'postsNonNullAndListOfWithSelectFieldsAndModel', ]; - public function type() + public function type(): Type { return Type::nonNull(Type::listOf(GraphQL::type('PostWithModel'))); } diff --git a/tests/Support/Types/PostType.php b/tests/Support/Types/PostType.php index f7d74b56..5af81897 100644 --- a/tests/Support/Types/PostType.php +++ b/tests/Support/Types/PostType.php @@ -13,7 +13,7 @@ class PostType extends GraphQLType 'name' => 'Post', ]; - public function fields() + public function fields(): array { return [ 'id' => [ diff --git a/tests/Support/Types/PostWithModelAndAliasAndCustomResolverType.php b/tests/Support/Types/PostWithModelAndAliasAndCustomResolverType.php index 2ecdddc1..ad18695e 100644 --- a/tests/Support/Types/PostWithModelAndAliasAndCustomResolverType.php +++ b/tests/Support/Types/PostWithModelAndAliasAndCustomResolverType.php @@ -15,7 +15,7 @@ class PostWithModelAndAliasAndCustomResolverType extends GraphQLType 'model' => Post::class, ]; - public function fields() + public function fields(): array { return [ 'id' => [ diff --git a/tests/Support/Types/PostWithModelAndAliasType.php b/tests/Support/Types/PostWithModelAndAliasType.php index d6be2ebf..2894f91c 100644 --- a/tests/Support/Types/PostWithModelAndAliasType.php +++ b/tests/Support/Types/PostWithModelAndAliasType.php @@ -15,7 +15,7 @@ class PostWithModelAndAliasType extends GraphQLType 'model' => Post::class, ]; - public function fields() + public function fields(): array { return [ 'id' => [ diff --git a/tests/Support/Types/PostWithModelType.php b/tests/Support/Types/PostWithModelType.php index 5b406241..7f1adb83 100644 --- a/tests/Support/Types/PostWithModelType.php +++ b/tests/Support/Types/PostWithModelType.php @@ -15,7 +15,7 @@ class PostWithModelType extends GraphQLType 'model' => Post::class, ]; - public function fields() + public function fields(): array { return [ 'id' => [ diff --git a/tests/Unit/EndpointTest.php b/tests/Unit/EndpointTest.php index 26853d07..bf5e5141 100644 --- a/tests/Unit/EndpointTest.php +++ b/tests/Unit/EndpointTest.php @@ -139,4 +139,15 @@ public function testBatchedQueries(): void ], ]); } + + public function testGetGraphqiQL(): void + { + $response = $this->call('GET', '/graphiql'); + + // Are we seeing the right template? + $response->assertSee('This GraphiQL example illustrates how to use some of GraphiQL\'s props'); + // The argument to fetch is extracted from the configuration + $response->assertSee('return fetch(\'/graphql\', {'); + $response->assertSee("'X-CSRF-TOKEN': xcrsfToken || ''"); + } } diff --git a/tests/Unit/LaravelValidatorTests/LaravelValidatorTest.php b/tests/Unit/LaravelValidatorTests/LaravelValidatorTest.php new file mode 100644 index 00000000..5139215c --- /dev/null +++ b/tests/Unit/LaravelValidatorTests/LaravelValidatorTest.php @@ -0,0 +1,94 @@ + [ + 'in:valid_name', + new RuleObjectPass(), + ], + ]; + + $data = [ + 'arg' => 'valid_name', + ]; + + /** @var Validator $validator */ + $validator = \Validator::make($data, $rules); + + $this->assertSame([], $validator->errors()->all()); + } + + public function testInPassRuleFail(): void + { + $rules = [ + 'arg' => [ + 'in:valid_name', + new RuleObjectFail(), + ], + ]; + + $data = [ + 'arg' => 'valid_name', + ]; + + /** @var Validator $validator */ + $validator = \Validator::make($data, $rules); + + $expectedMessages = [ + 'rule object validation fails', + ]; + $this->assertSame($expectedMessages, $validator->errors()->all()); + } + + public function testInFailRulePass(): void + { + $rules = [ + 'arg' => [ + 'in:valid_name', + new RuleObjectPass(), + ], + ]; + + $data = [ + 'arg' => 'invalid_name', + ]; + + /** @var Validator $validator */ + $validator = \Validator::make($data, $rules); + + $expectedMessages = [ + 'The selected arg is invalid.', + ]; + $this->assertSame($expectedMessages, $validator->errors()->all()); + } + + public function testInFailRuleFail(): void + { + $rules = [ + 'arg' => [ + 'in:valid_name', + new RuleObjectFail(), + ], + ]; + + $data = [ + 'The selected arg is invalid.', + 'rule object validation fails', + ]; + + /** @var Validator $validator */ + $validator = \Validator::make($data, $rules); + + $this->assertSame([], $validator->errors()->all()); + } +} diff --git a/tests/Unit/LaravelValidatorTests/RuleObjectFail.php b/tests/Unit/LaravelValidatorTests/RuleObjectFail.php new file mode 100644 index 00000000..bffb2ef6 --- /dev/null +++ b/tests/Unit/LaravelValidatorTests/RuleObjectFail.php @@ -0,0 +1,20 @@ + 'Test argument 1', + ]); + + $this->assertCount(1, $result['errors']); + $this->assertSame('validation', $result['errors'][0]['message']); + /** @var MessageBag $messageBag */ + $messageBag = $result['errors'][0]['extensions']['validation']; + $this->assertSame(['arg1 is invalid'], $messageBag->all()); + } + + public function testMutationWithCustomRuleWithRuleObject(): void + { + $graphql = <<<'GRAPHQL' +mutation Mutate($arg1: String) { + mutationWithCustomRuleWithRuleObject(arg1: $arg1) +} +GRAPHQL; + + $result = GraphQL::query($graphql, [ + 'arg1' => 'Test argument 1', + ]); + + $this->assertCount(1, $result['errors']); + $this->assertSame('validation', $result['errors'][0]['message']); + /** @var MessageBag $messageBag */ + $messageBag = $result['errors'][0]['extensions']['validation']; + $this->assertSame(['arg1 is invalid'], $messageBag->all()); + } + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + $app['config']->set('graphql.schemas.default', [ + 'mutation' => [ + MutationWithCustomRuleWithClosure::class, + MutationWithCustomRuleWithRuleObject::class, + ], + ]); + } +} diff --git a/tests/Unit/MutationCustomRulesTests/MutationWithCustomRuleWithClosure.php b/tests/Unit/MutationCustomRulesTests/MutationWithCustomRuleWithClosure.php new file mode 100644 index 00000000..57cc5ce0 --- /dev/null +++ b/tests/Unit/MutationCustomRulesTests/MutationWithCustomRuleWithClosure.php @@ -0,0 +1,46 @@ + 'mutationWithCustomRuleWithClosure', + ]; + + public function type(): Type + { + return Type::nonNull(Type::string()); + } + + protected function rules(array $args = []): array + { + return [ + 'arg1' => [ + 'required', + function (string $attribute, $value, $fail) { + $fail($attribute.' is invalid'); + }, + ], + ]; + } + + public function args(): array + { + return [ + 'arg1' => [ + 'type' => Type::string(), + ], + ]; + } + + public function resolve($root, $args): string + { + return 'mutation result'; + } +} diff --git a/tests/Unit/MutationCustomRulesTests/MutationWithCustomRuleWithRuleObject.php b/tests/Unit/MutationCustomRulesTests/MutationWithCustomRuleWithRuleObject.php new file mode 100644 index 00000000..dc5b16d1 --- /dev/null +++ b/tests/Unit/MutationCustomRulesTests/MutationWithCustomRuleWithRuleObject.php @@ -0,0 +1,44 @@ + 'mutationWithCustomRuleWithRuleObject', + ]; + + public function type(): Type + { + return Type::nonNull(Type::string()); + } + + protected function rules(array $args = []): array + { + return [ + 'arg1' => [ + 'required', + new RuleObject(), + ], + ]; + } + + public function args(): array + { + return [ + 'arg1' => [ + 'type' => Type::string(), + ], + ]; + } + + public function resolve($root, $args): string + { + return 'mutation result'; + } +} diff --git a/tests/Unit/MutationCustomRulesTests/RuleObject.php b/tests/Unit/MutationCustomRulesTests/RuleObject.php new file mode 100644 index 00000000..ca70e5ea --- /dev/null +++ b/tests/Unit/MutationCustomRulesTests/RuleObject.php @@ -0,0 +1,20 @@ + 'valid_name', + ]); + + $expectedResult = [ + 'data' => [ + 'mutationWithCustomRuleWithRuleObject' => 'mutation result', + ], + ]; + $this->assertSame($expectedResult, $result); + } + + public function testInPassRuleFail(): void + { + $graphql = <<<'GRAPHQL' +mutation Mutate($arg_in_rule_fail: String) { + mutationWithCustomRuleWithRuleObject(arg_in_rule_fail: $arg_in_rule_fail) +} +GRAPHQL; + + $result = GraphQL::query($graphql, [ + 'arg_in_rule_fail' => 'valid_name', + ]); + + $this->assertCount(1, $result['errors']); + $this->assertSame('validation', $result['errors'][0]['message']); + /** @var MessageBag $messageBag */ + $messageBag = $result['errors'][0]['extensions']['validation']; + $expectedMessages = [ + 'rule object validation fails', + ]; + $this->assertSame($expectedMessages, $messageBag->all()); + } + + public function testInFailRulePass(): void + { + $graphql = <<<'GRAPHQL' +mutation Mutate($arg_in_rule_pass: String) { + mutationWithCustomRuleWithRuleObject(arg_in_rule_pass: $arg_in_rule_pass) +} +GRAPHQL; + + $result = GraphQL::query($graphql, [ + 'arg_in_rule_pass' => 'invalid_name', + ]); + + $this->assertCount(1, $result['errors']); + $this->assertSame('validation', $result['errors'][0]['message']); + /** @var MessageBag $messageBag */ + $messageBag = $result['errors'][0]['extensions']['validation']; + $expectedMessages = [ + 'The selected arg in rule pass is invalid.', + ]; + $this->assertSame($expectedMessages, $messageBag->all()); + } + + public function testInFailRuleFail(): void + { + $graphql = <<<'GRAPHQL' +mutation Mutate($arg_in_rule_fail: String) { + mutationWithCustomRuleWithRuleObject(arg_in_rule_fail: $arg_in_rule_fail) +} +GRAPHQL; + + $result = GraphQL::query($graphql, [ + 'arg_in_rule_fail' => 'invalid_name', + ]); + + $this->assertCount(1, $result['errors']); + $this->assertSame('validation', $result['errors'][0]['message']); + /** @var MessageBag $messageBag */ + $messageBag = $result['errors'][0]['extensions']['validation']; + $expectedMessages = [ + 'The selected arg in rule fail is invalid.', + 'rule object validation fails', + ]; + $this->assertSame($expectedMessages, $messageBag->all()); + } + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + $app['config']->set('graphql.schemas.default', [ + 'mutation' => [ + MutationWithCustomRuleWithRuleObject::class, + ], + ]); + } +} diff --git a/tests/Unit/MutationValidationInWithCustomRulesTests/MutationWithCustomRuleWithRuleObject.php b/tests/Unit/MutationValidationInWithCustomRulesTests/MutationWithCustomRuleWithRuleObject.php new file mode 100644 index 00000000..41b70cfb --- /dev/null +++ b/tests/Unit/MutationValidationInWithCustomRulesTests/MutationWithCustomRuleWithRuleObject.php @@ -0,0 +1,51 @@ + 'mutationWithCustomRuleWithRuleObject', + ]; + + public function type(): Type + { + return Type::nonNull(Type::string()); + } + + protected function rules(array $args = []): array + { + return [ + 'arg_in_rule_pass' => [ + 'in:valid_name', + new RuleObjectPass(), + ], + 'arg_in_rule_fail' => [ + 'in:valid_name', + new RuleObjectFail(), + ], + ]; + } + + public function args(): array + { + return [ + 'arg_in_rule_pass' => [ + 'type' => Type::string(), + ], + 'arg_in_rule_fail' => [ + 'type' => Type::string(), + ], + ]; + } + + public function resolve($root, $args): string + { + return 'mutation result'; + } +} diff --git a/tests/Unit/MutationValidationInWithCustomRulesTests/RuleObjectFail.php b/tests/Unit/MutationValidationInWithCustomRulesTests/RuleObjectFail.php new file mode 100644 index 00000000..2fdabfd9 --- /dev/null +++ b/tests/Unit/MutationValidationInWithCustomRulesTests/RuleObjectFail.php @@ -0,0 +1,20 @@ + Date: Tue, 18 Jun 2019 15:33:21 +0000 Subject: [PATCH 08/17] Apply fixes from StyleCI --- src/Rebing/GraphQL/Support/SelectFields.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rebing/GraphQL/Support/SelectFields.php b/src/Rebing/GraphQL/Support/SelectFields.php index de25084b..b5e0c1a5 100644 --- a/src/Rebing/GraphQL/Support/SelectFields.php +++ b/src/Rebing/GraphQL/Support/SelectFields.php @@ -13,12 +13,12 @@ use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\WrappingType; -use GraphQL\Type\Definition\FieldDefinition; -use GraphQL\Type\Definition\Type as GraphqlType; use GraphQL\Language\AST\SelectionSetNode; use GraphQL\Language\AST\FragmentSpreadNode; use GraphQL\Language\AST\InlineFragmentNode; +use GraphQL\Type\Definition\FieldDefinition; use GraphQL\Language\AST\FragmentDefinitionNode; +use GraphQL\Type\Definition\Type as GraphqlType; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphTo; From 76a6a0a1f856c22ffeb15cb7cb0b212f0d3a777d Mon Sep 17 00:00:00 2001 From: "xingshenqiang@uniondrug.cn" Date: Wed, 19 Jun 2019 13:37:34 +0800 Subject: [PATCH 09/17] strict mode --- src/Rebing/GraphQL/Support/SelectFields.php | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Rebing/GraphQL/Support/SelectFields.php b/src/Rebing/GraphQL/Support/SelectFields.php index b5e0c1a5..7b6c985f 100644 --- a/src/Rebing/GraphQL/Support/SelectFields.php +++ b/src/Rebing/GraphQL/Support/SelectFields.php @@ -53,23 +53,21 @@ public function __construct(ResolveInfo $info, GraphqlType $parentType, array $a } $this->info = $info; - if (! is_null($info->fieldNodes[0]->selectionSet)) { - self::$args = $args; - - $fields = self::getSelectableFieldsAndRelations($this->getFieldSelection(5), $parentType); - - $this->select = $fields[0]; - $this->relations = $fields[1]; - } + self::$args = $args; + $fields = self::getSelectableFieldsAndRelations($this->getFieldSelection(5), $parentType); + $this->select = $fields[0]; + $this->relations = $fields[1]; } public function getFieldSelection($depth = 0) { $data = []; - /** @var FieldNode $fieldNode */ - foreach ($this->info->fieldNodes as $fieldNode) { - $data = array_merge_recursive($data, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); + if (! is_null($this->info->fieldNodes[0]->selectionSet)) { + /** @var FieldNode $fieldNode */ + foreach ($this->info->fieldNodes as $fieldNode) { + $data = array_merge_recursive($data, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); + } } $fields['fields'] = $data; From 9add6ac65a6f18c4e4cfa6576d72c0ce33bbe724 Mon Sep 17 00:00:00 2001 From: "xingshenqiang@uniondrug.cn" Date: Wed, 19 Jun 2019 21:56:33 +0800 Subject: [PATCH 10/17] strict mode --- src/Support/SelectFields.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index 416fb937..298e0e4c 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -66,7 +66,9 @@ public function getFieldSelection($depth = 0) if (! is_null($this->info->fieldNodes[0]->selectionSet)) { /** @var FieldNode $fieldNode */ foreach ($this->info->fieldNodes as $fieldNode) { - $data = array_merge_recursive($data, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); + if (! is_null($fieldNode->selectionSet)) { + $data = array_merge_recursive($data, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); + } } } From 8b43a63573b463e738977bb97ebadc4ab8ab8dc7 Mon Sep 17 00:00:00 2001 From: Markus Fischer Date: Thu, 20 Jun 2019 00:03:20 +0200 Subject: [PATCH 11/17] Move field and argument extraction logic into separate class --- phpstan.neon | 2 + src/Support/ResolveInfoFieldsAndArguments.php | 146 ++++++++++++++++++ src/Support/SelectFields.php | 74 +-------- 3 files changed, 156 insertions(+), 66 deletions(-) create mode 100644 src/Support/ResolveInfoFieldsAndArguments.php diff --git a/phpstan.neon b/phpstan.neon index 2faa7f68..b24e7353 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -34,3 +34,5 @@ parameters: - '/Parameter #4 \$currentPage of class Illuminate\\Pagination\\LengthAwarePaginator constructor expects int\|null, float\|int given/' - '/Parameter #1 \$offset of method Illuminate\\Support\\Collection::slice\(\) expects int, float\|int given/' - '/Parameter #1 \$abstract of function app expects string\|null, mixed given/' + # \Rebing\GraphQL\Support\ResolveInfoFieldsAndArguments::getValue + - '/Access to an undefined property GraphQL\\Language\\AST\\ValueNode::\$value/' diff --git a/src/Support/ResolveInfoFieldsAndArguments.php b/src/Support/ResolveInfoFieldsAndArguments.php new file mode 100644 index 00000000..4ec42ed4 --- /dev/null +++ b/src/Support/ResolveInfoFieldsAndArguments.php @@ -0,0 +1,146 @@ +info = $info; + } + + /** + * Helper method that returns names of all fields with attributes selected in query for + * $this->fieldName up to $depth levels + * + * Example: + * query MyQuery{ + * { + * root { + * nested(input:value) { + * nested1 + * nested2 { + * nested3(input:value) + * } + * } + * } + * } + * + * Given this ResolveInfo instance is a part of "root" field resolution, and $depth === 1, + * method will return: + * [ + * 'nested' => [ + * 'args' => [ + * 'input' => 'value', + * ], + * 'fields' => [ + * 'nested1' => [ + * 'args' => [], + * 'fields' => true, + * ], + * 'nested2' => [ + * 'args' => [], + * 'fields' => [ + * 'nested3' => [ + * 'args' => [ + * 'input' => 'value', + * ], + * 'fields' => true, + * ], + * ], + * ], + * ], + * ], + * ], + * + * Warning: this method it is a naive implementation which does not take into account + * conditional typed fragments. So use it with care for fields of interface and union types. + * + * @param int $depth How many levels to include in output + * @return array + * @see \GraphQL\Type\Definition\ResolveInfo::getFieldSelection + */ + public function getFieldsAndArgumentsSelection(int $depth = 0): array + { + $fields = []; + + foreach ($this->info->fieldNodes as $fieldNode) { + if (! $fieldNode->selectionSet) { + continue; + } + + $fields = array_merge_recursive($fields, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); + } + + return $fields; + } + + /** + * @param SelectionSetNode $selectionSet + * @param int $descend + * @return array + * @see \GraphQL\Type\Definition\ResolveInfo::foldSelectionSet + */ + private function foldSelectionSet(SelectionSetNode $selectionSet, int $descend): array + { + $fields = []; + + foreach ($selectionSet->selections as $selectionNode) { + if ($selectionNode instanceof FieldNode) { + $name = $selectionNode->name->value; + + $fields[$name] = [ + 'args' => [], + 'fields' => $descend > 0 && ! empty($selectionNode->selectionSet) + ? $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1) + : true, + ]; + + foreach ($selectionNode->arguments ?? [] as $argumentNode) { + $fields[$name]['args'][$argumentNode->name->value] = $this->getValue($argumentNode); + } + } elseif ($selectionNode instanceof FragmentSpreadNode) { + $spreadName = $selectionNode->name->value; + if (isset($this->info->fragments[$spreadName])) { + $fragment = $this->info->fragments[$spreadName]; + $fields = array_merge_recursive($this->foldSelectionSet($fragment->selectionSet, $descend), + $fields); + } + } elseif ($selectionNode instanceof InlineFragmentNode) { + $fields = array_merge_recursive($this->foldSelectionSet($selectionNode->selectionSet, $descend), + $fields); + } + } + + return $fields; + } + + private function getValue(ArgumentNode $argumentNode) + { + $value = $argumentNode->value; + if ($value instanceof VariableNode) { + $variableName = $value->name->value; + + return $this->info->variableValues[$variableName] ?? null; + } + + return $argumentNode->value->value; + } +} diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index 298e0e4c..c890b24c 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -6,18 +6,11 @@ use Closure; use Illuminate\Support\Arr; -use GraphQL\Language\AST\FieldNode; use GraphQL\Error\InvariantViolation; -use GraphQL\Language\AST\ArgumentNode; -use GraphQL\Language\AST\VariableNode; use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Type\Definition\WrappingType; -use GraphQL\Language\AST\SelectionSetNode; -use GraphQL\Language\AST\FragmentSpreadNode; -use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Type\Definition\FieldDefinition; -use GraphQL\Language\AST\FragmentDefinitionNode; use GraphQL\Type\Definition\Type as GraphqlType; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -36,8 +29,6 @@ class SelectFields private $relations = []; /** @var array */ private static $privacyValidations = []; - /** @var ResolveInfo */ - private $info; const FOREIGN_KEY = 'foreignKey'; @@ -52,65 +43,21 @@ public function __construct(ResolveInfo $info, GraphqlType $parentType, array $a $parentType = $parentType->getWrappedType(true); } - $this->info = $info; self::$args = $args; - $fields = self::getSelectableFieldsAndRelations($this->getFieldSelection(5), $parentType); + $requestedFields = $this->getFieldSelection($info, $args, 5); + $fields = self::getSelectableFieldsAndRelations($requestedFields, $parentType); $this->select = $fields[0]; $this->relations = $fields[1]; } - public function getFieldSelection($depth = 0) + private function getFieldSelection(ResolveInfo $resolveInfo, array $args, int $depth): array { - $data = []; + $resolveInfoFieldsAndArguments = new ResolveInfoFieldsAndArguments($resolveInfo); - if (! is_null($this->info->fieldNodes[0]->selectionSet)) { - /** @var FieldNode $fieldNode */ - foreach ($this->info->fieldNodes as $fieldNode) { - if (! is_null($fieldNode->selectionSet)) { - $data = array_merge_recursive($data, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); - } - } - } - - $fields['fields'] = $data; - $fields['args'] = self::$args; - - return $fields; - } - - private function foldSelectionSet(SelectionSetNode $selectionSet, $descend) - { - $fields = []; - - foreach ($selectionSet->selections as $selectionNode) { - if ($selectionNode instanceof FieldNode) { - $fields[$selectionNode->name->value]['fields'] = $descend > 0 && ! empty($selectionNode->selectionSet) - ? $this->foldSelectionSet($selectionNode->selectionSet, $descend - 1) - : true; - $fields[$selectionNode->name->value]['args'] = []; - foreach ($selectionNode->arguments as $argument) { - if ($argument->value instanceof VariableNode) { - $value = $this->info->variableValues[$argument->value->name->value] ?? null; - } else { - $value = $argument->value->value; - } - if ($argument instanceof ArgumentNode) { - $fields[$selectionNode->name->value]['args'][$argument->name->value] = $value; - } - } - } elseif ($selectionNode instanceof FragmentSpreadNode) { - $spreadName = $selectionNode->name->value; - if (isset($this->info->fragments[$spreadName])) { - /** @var FragmentDefinitionNode $fragment */ - $fragment = $this->info->fragments[$spreadName]; - $fields = array_merge_recursive($this->foldSelectionSet($fragment->selectionSet, $descend), $fields); - } - } elseif ($selectionNode instanceof InlineFragmentNode) { - $fields = array_merge_recursive($this->foldSelectionSet($selectionNode->selectionSet, $descend), $fields); - } - } - - return $fields; + return [ + 'args' => $args, + 'fields' => $resolveInfoFieldsAndArguments->getFieldsAndArgumentsSelection($depth), + ]; } /** @@ -417,9 +364,4 @@ public function getRelations(): array { return $this->relations; } - - public function getResolveInfo(): ResolveInfo - { - return $this->info; - } } From bd883cff5f138783a06ca9589a732e77722ce07f Mon Sep 17 00:00:00 2001 From: Markus Fischer Date: Thu, 20 Jun 2019 00:04:55 +0200 Subject: [PATCH 12/17] Remove null coalesce fallback - there's always an 'args' (it may be empty) - it's logically wrong to provide a nested query with the top level arguments; that's the bug we're trying to fix here --- 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 c890b24c..f1b70746 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -104,7 +104,7 @@ public static function getSelectableFieldsAndRelations(array $requestedFields, G } else { return function ($query) use ($with, $select, $customQuery, $requestedFields) { if ($customQuery) { - $query = $customQuery($requestedFields['args'] ?? self::$args, $query); + $query = $customQuery($requestedFields['args'], $query); } $query->select($select); From 2171688e673611cb33091d0941340050999389be Mon Sep 17 00:00:00 2001 From: Markus Fischer Date: Thu, 20 Jun 2019 00:06:41 +0200 Subject: [PATCH 13/17] Pass field arguments instead of top level arguments to privacy check methods It's the same as with arguments for custom queries, the fallback to the top level arguments are logically wrong. This removes the ::$args field for good. --- src/Support/SelectFields.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Support/SelectFields.php b/src/Support/SelectFields.php index f1b70746..c82f19c3 100644 --- a/src/Support/SelectFields.php +++ b/src/Support/SelectFields.php @@ -21,8 +21,6 @@ class SelectFields { - /** @var array */ - private static $args = []; /** @var array */ private $select = []; /** @var array */ @@ -43,7 +41,6 @@ public function __construct(ResolveInfo $info, GraphqlType $parentType, array $a $parentType = $parentType->getWrappedType(true); } - self::$args = $args; $requestedFields = $this->getFieldSelection($info, $args, 5); $fields = self::getSelectableFieldsAndRelations($requestedFields, $parentType); $this->select = $fields[0]; @@ -149,7 +146,7 @@ protected static function handleFields(array $requestedFields, GraphqlType $pare } // First check if the field is even accessible - $canSelect = self::validateField($fieldObject); + $canSelect = self::validateField($fieldObject, $field['args']); if ($canSelect === true) { // Add a query, if it exists $customQuery = Arr::get($fieldObject->config, 'query'); @@ -247,10 +244,11 @@ protected static function handleFields(array $requestedFields, GraphqlType $pare * Check the privacy status, if it's given. * * @param FieldDefinition $fieldObject + * @param array $fieldArgs Arguments given with the field * @return bool|null - true, if selectable; false, if not selectable, but allowed; * null, if not allowed */ - protected static function validateField(FieldDefinition $fieldObject): ?bool + protected static function validateField(FieldDefinition $fieldObject, array $fieldArgs): ?bool { $selectable = true; @@ -263,7 +261,7 @@ protected static function validateField(FieldDefinition $fieldObject): ?bool $privacyClass = $fieldObject->config['privacy']; // If privacy given as a closure - if (is_callable($privacyClass) && call_user_func($privacyClass, self::$args) === false) { + if (is_callable($privacyClass) && call_user_func($privacyClass, $fieldArgs) === false) { $selectable = null; } // If Privacy class given @@ -271,7 +269,7 @@ protected static function validateField(FieldDefinition $fieldObject): ?bool if (Arr::has(self::$privacyValidations, $privacyClass)) { $validated = self::$privacyValidations[$privacyClass]; } else { - $validated = call_user_func([app($privacyClass), 'fire'], self::$args); + $validated = call_user_func([app($privacyClass), 'fire'], $fieldArgs); self::$privacyValidations[$privacyClass] = $validated; } From d4444338daa59f41cdcfd286aab41b68c74e0520 Mon Sep 17 00:00:00 2001 From: Markus Fischer Date: Thu, 20 Jun 2019 00:10:30 +0200 Subject: [PATCH 14/17] Adapt phpstan ignoreErrors due to code changes --- phpstan.neon | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index b24e7353..0f8bed09 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -18,7 +18,8 @@ parameters: - '/Strict comparison using === between null and array will always evaluate to false/' - '/Cannot access offset . on array\|Closure/' - '/Call to function is_array\(\) with string will always evaluate to false/' # TODO: fix \Rebing\GraphQL\Support\SelectFields::getSelectableFieldsAndRelations - - '/Parameter #1 \$function of function call_user_func expects callable\(\): mixed, array\(mixed, string\) given/' + # \Rebing\GraphQL\Support\SelectFields::handleFields + - '/Parameter #1 \$function of function call_user_func expects callable\(\): mixed, array\(mixed, mixed\) given/' - '/Binary operation "." between string and array\|string\|null results in an error/' - '/Parameter #1 \$key of function array_key_exists expects int\|string, string\|false given/' - "/Parameter #1 \\$function of function call_user_func expects callable\\(\\): mixed, array\\(mixed, 'fire'\\) given/" From 9bca4296f747cc6cd078edf16e7cfd0636c65638 Mon Sep 17 00:00:00 2001 From: Markus Fischer Date: Thu, 20 Jun 2019 00:36:29 +0200 Subject: [PATCH 15/17] Adapt test description now that the bug is fixed --- .../NestedRelationLoadingTest.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php b/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php index ff4cc55d..046fa0f6 100644 --- a/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php +++ b/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php @@ -572,12 +572,9 @@ public function testQuerySelectAndWith(): void } /** - * The test is expected to break and needs adaption once the referenced - * issue is fixed: - * - SQL queries - * - actual posts returned (only 1 post per user). + * Created to show the bug for https://github.com/rebing/graphql-laravel/issues/314 * - * @see https://github.com/rebing/graphql-laravel/issues/314 + * Fixed with https://github.com/rebing/graphql-laravel/pull/327 */ public function testQuerySelectAndWithAndSubArgs(): void { @@ -691,12 +688,9 @@ public function testQuerySelectAndWithAndSubArgs(): void } /** - * The test is expected to break and needs adaption once the referenced - * issue is fixed: - * - SQL queries - * - actual posts AND comments returned (only 1 post and 1 comment per user). + * Created to show the bug for https://github.com/rebing/graphql-laravel/issues/314 * - * @see https://github.com/rebing/graphql-laravel/issues/314 + * Fixed with https://github.com/rebing/graphql-laravel/pull/327 */ public function testQuerySelectAndWithAndNestedSubArgs(): void { From 12381b17acbdd6dfee1870e3b5819e1d027c3084 Mon Sep 17 00:00:00 2001 From: Markus Fischer Date: Thu, 20 Jun 2019 00:39:10 +0200 Subject: [PATCH 16/17] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7771bf9d..a6037e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ Next release - Replace global helper `is_lumen` with static class call `\Rebing\GraphQL\Helpers::isLumen` ### Fixed +- SelectFields correctly passes field arguments to the custom query [\#327](https://github.com/rebing/graphql-laravel/pull/327) + - This also applies to privacy checks on fields, the callback now receives the field arguments too + - Previously the initial query arguments would be used everywhere - SelectFields now works with wrapped types (nonNull, listOf) ### Removed From 204e591717c8d6d47c973e3495b2ca373eb87ffd Mon Sep 17 00:00:00 2001 From: Markus Fischer Date: Thu, 20 Jun 2019 00:45:14 +0200 Subject: [PATCH 17/17] Apply fixes from StyleCI --- src/Support/ResolveInfoFieldsAndArguments.php | 4 ++-- .../NestedRelationLoadingTests/NestedRelationLoadingTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Support/ResolveInfoFieldsAndArguments.php b/src/Support/ResolveInfoFieldsAndArguments.php index 4ec42ed4..ee3101ae 100644 --- a/src/Support/ResolveInfoFieldsAndArguments.php +++ b/src/Support/ResolveInfoFieldsAndArguments.php @@ -9,8 +9,8 @@ use GraphQL\Language\AST\VariableNode; use GraphQL\Type\Definition\ResolveInfo; use GraphQL\Language\AST\SelectionSetNode; -use GraphQL\Language\AST\InlineFragmentNode; use GraphQL\Language\AST\FragmentSpreadNode; +use GraphQL\Language\AST\InlineFragmentNode; /** * This adapts \GraphQL\Type\Definition\ResolveInfo::getFieldSelection @@ -28,7 +28,7 @@ public function __construct(ResolveInfo $info) /** * Helper method that returns names of all fields with attributes selected in query for - * $this->fieldName up to $depth levels + * $this->fieldName up to $depth levels. * * Example: * query MyQuery{ diff --git a/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php b/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php index 046fa0f6..3a8668c8 100644 --- a/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php +++ b/tests/Database/SelectFields/NestedRelationLoadingTests/NestedRelationLoadingTest.php @@ -572,7 +572,7 @@ public function testQuerySelectAndWith(): void } /** - * Created to show the bug for https://github.com/rebing/graphql-laravel/issues/314 + * Created to show the bug for https://github.com/rebing/graphql-laravel/issues/314. * * Fixed with https://github.com/rebing/graphql-laravel/pull/327 */ @@ -688,7 +688,7 @@ public function testQuerySelectAndWithAndSubArgs(): void } /** - * Created to show the bug for https://github.com/rebing/graphql-laravel/issues/314 + * Created to show the bug for https://github.com/rebing/graphql-laravel/issues/314. * * Fixed with https://github.com/rebing/graphql-laravel/pull/327 */