-
-
Notifications
You must be signed in to change notification settings - Fork 266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix args in sub queries #327
Changes from 20 commits
4867c97
2d89c71
a5f5995
771ff00
c7e7cd8
646920c
11375af
5750416
cf1f570
917b1e5
a6076a9
93fea9c
f5047ae
ad71319
5aefa3e
76a6a0a
b10017b
441df57
9add6ac
ff7db90
8b43a63
bd883cf
2171688
d444433
9bca429
12381b1
204e591
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,11 +6,18 @@ | |
|
||
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; | ||
|
@@ -29,6 +36,8 @@ class SelectFields | |
private $relations = []; | ||
/** @var array */ | ||
private static $privacyValidations = []; | ||
/** @var ResolveInfo */ | ||
private $info; | ||
|
||
const FOREIGN_KEY = 'foreignKey'; | ||
|
||
|
@@ -43,14 +52,65 @@ public function __construct(ResolveInfo $info, GraphqlType $parentType, array $a | |
$parentType = $parentType->getWrappedType(true); | ||
} | ||
|
||
if (! is_null($info->fieldNodes[0]->selectionSet)) { | ||
self::$args = $args; | ||
$this->info = $info; | ||
self::$args = $args; | ||
$fields = self::getSelectableFieldsAndRelations($this->getFieldSelection(5), $parentType); | ||
$this->select = $fields[0]; | ||
$this->relations = $fields[1]; | ||
} | ||
|
||
public function getFieldSelection($depth = 0) | ||
{ | ||
$data = []; | ||
|
||
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 = self::getSelectableFieldsAndRelations($info->getFieldSelection(5), $parentType); | ||
$fields['fields'] = $data; | ||
$fields['args'] = self::$args; | ||
|
||
$this->select = $fields[0]; | ||
$this->relations = $fields[1]; | ||
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; | ||
} | ||
|
||
/** | ||
|
@@ -95,9 +155,9 @@ public static function getSelectableFieldsAndRelations(array $requestedFields, G | |
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fallback to self::$args doesn't look right. First: in what case when Second: why should this default to self::$args? This is in fact the bug we're trying to fix; passing none or wrong arguments to the custom query. IMHO the whole |
||
} | ||
|
||
$query->select($select); | ||
|
@@ -118,7 +178,7 @@ protected static function handleFields(array $requestedFields, GraphqlType $pare | |
{ | ||
$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; | ||
|
@@ -155,7 +215,7 @@ protected static function handleFields(array $requestedFields, GraphqlType $pare | |
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') | ||
|
@@ -193,7 +253,7 @@ protected static function handleFields(array $requestedFields, GraphqlType $pare | |
$segments = explode('.', $foreignKey); | ||
$foreignKey = end($segments); | ||
if (! array_key_exists($foreignKey, $field)) { | ||
$field[$foreignKey] = self::FOREIGN_KEY; | ||
$field['fields'][$foreignKey] = self::FOREIGN_KEY; | ||
} | ||
} | ||
|
||
|
@@ -357,4 +417,9 @@ public function getRelations(): array | |
{ | ||
return $this->relations; | ||
} | ||
|
||
public function getResolveInfo(): ResolveInfo | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't necessary: ResolveInfo is solely here consumed by the class; any query/mutation resolver it as a separate argument anyway |
||
{ | ||
return $this->info; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole logic shouldn't be intermixed within this class.
Extracting the field/argument datastructure is a separate concern.