Skip to content

Grouping implementor fields for abstract types in QueryPlan #513

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

Merged
merged 16 commits into from
Oct 13, 2019
Merged
77 changes: 59 additions & 18 deletions src/Type/Definition/QueryPlan.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Type\Schema;
use function array_diff_key;
use function array_filter;
use function array_intersect_key;
use function array_key_exists;
use function array_keys;
use function array_merge;
Expand Down Expand Up @@ -41,16 +43,21 @@ class QueryPlan
/** @var FragmentDefinitionNode[] */
private $fragments;

/** @var bool */
private $groupImplementorFields;

/**
* @param FieldNode[] $fieldNodes
* @param mixed[] $variableValues
* @param FragmentDefinitionNode[] $fragments
* @param mixed[] $options
*/
public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments)
public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments, array $options = [])
{
$this->schema = $schema;
$this->variableValues = $variableValues;
$this->fragments = $fragments;
$this->schema = $schema;
$this->variableValues = $variableValues;
$this->fragments = $fragments;
$this->groupImplementorFields = in_array('group-implementor-fields', $options, true);
$this->analyzeQueryPlan($parentType, $fieldNodes);
}

Expand Down Expand Up @@ -109,7 +116,8 @@ public function subFields(string $typename) : array
*/
private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) : void
{
$queryPlan = [];
$queryPlan = [];
$implementors = [];
/** @var FieldNode $fieldNode */
foreach ($fieldNodes as $fieldNode) {
if (! $fieldNode->selectionSet) {
Expand All @@ -121,7 +129,7 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes)
$type = $type->getWrappedType();
}

$subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type);
$subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type, $implementors);

$this->types[$type->name] = array_unique(array_merge(
array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [],
Expand All @@ -134,17 +142,26 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes)
);
}

$this->queryPlan = $queryPlan;
if ($this->groupImplementorFields) {
$this->queryPlan = ['fields' => $queryPlan];

if ($implementors) {
$this->queryPlan['implementors'] = $implementors;
}
} else {
$this->queryPlan = $queryPlan;
}
}

/**
* @param InterfaceType|ObjectType $parentType
* @param mixed[] $implementors
*
* @return mixed[]
*
* @throws Error
*/
private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType) : array
private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType, array &$implementors = []) : array
{
$fields = [];
foreach ($selectionSet->selections as $selectionNode) {
Expand All @@ -169,20 +186,12 @@ private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $paren
$fragment = $this->fragments[$spreadName];
$type = $this->schema->getType($fragment->typeCondition->name->value);
$subfields = $this->analyzeSubFields($type, $fragment->selectionSet);

$fields = $this->arrayMergeDeep(
$subfields,
$fields
);
$fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors);
}
} elseif ($selectionNode instanceof InlineFragmentNode) {
$type = $this->schema->getType($selectionNode->typeCondition->name->value);
$subfields = $this->analyzeSubFields($type, $selectionNode->selectionSet);

$fields = $this->arrayMergeDeep(
$subfields,
$fields
);
$fields = $this->mergeFields($parentType, $type, $fields, $subfields, $implementors);
}
}

Expand Down Expand Up @@ -210,6 +219,38 @@ private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet) :
return $subfields;
}

/**
* @param mixed[] $fields
* @param mixed[] $subfields
* @param mixed[] $implementors
*
* @return mixed[]
*/
private function mergeFields(Type $parentType, Type $type, array $fields, array $subfields, array &$implementors) : array
{
if ($this->groupImplementorFields && $parentType instanceof AbstractType && ! $type instanceof AbstractType) {
$implementors[$type->name] = [
'type' => $type,
'fields' => $this->arrayMergeDeep(
$implementors[$type->name]['fields'] ?? [],
array_diff_key($subfields, $fields)
),
];

$fields = $this->arrayMergeDeep(
$fields,
array_intersect_key($subfields, $fields)
);
} else {
$fields = $this->arrayMergeDeep(
$subfields,
$fields
);
}

return $fields;
}

/**
* similar to array_merge_recursive this merges nested arrays, but handles non array values differently
* while array_merge_recursive tries to merge non array values, in this implementation they will be overwritten
Expand Down
8 changes: 6 additions & 2 deletions src/Type/Definition/ResolveInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,19 @@ public function getFieldSelection($depth = 0)
return $fields;
}

public function lookAhead() : QueryPlan
/**
* @param mixed[] $options
*/
public function lookAhead(array $options = []) : QueryPlan
{
if ($this->queryPlan === null) {
$this->queryPlan = new QueryPlan(
$this->parentType,
$this->schema,
$this->fieldNodes,
$this->variableValues,
$this->fragments
$this->fragments,
$options
);
}

Expand Down
Loading