Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/Type/Definition/QueryPlan.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,25 +161,30 @@ private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes)
*
* @throws Error
*/
private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType, array &$implementors = []) : array
private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType, array &$implementors) : array
{
$fields = [];
$fields = [];
$implementors = [];
foreach ($selectionSet->selections as $selectionNode) {
if ($selectionNode instanceof FieldNode) {
$fieldName = $selectionNode->name->value;
$type = $parentType->getField($fieldName);
$selectionType = $type->getType();

$subfields = [];
$subfields = [];
$subImplementors = [];
if ($selectionNode->selectionSet) {
$subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet);
$subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet, $subImplementors);
}

$fields[$fieldName] = [
'type' => $selectionType,
'fields' => $subfields ?? [],
'args' => Values::getArgumentValues($type, $selectionNode, $this->variableValues),
];
if ($this->groupImplementorFields && $subImplementors) {
$fields[$fieldName]['implementors'] = $subImplementors;
}
} elseif ($selectionNode instanceof FragmentSpreadNode) {
$spreadName = $selectionNode->name->value;
if (isset($this->fragments[$spreadName])) {
Expand All @@ -199,17 +204,19 @@ private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $paren
}

/**
* @param mixed[] $implementors
*
* @return mixed[]
*/
private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet) : array
private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet, array &$implementors = []) : array
{
if ($type instanceof WrappingType) {
$type = $type->getWrappedType();
}

$subfields = [];
if ($type instanceof ObjectType) {
$subfields = $this->analyzeSelectionSet($selectionSet, $type);
if ($type instanceof ObjectType || $type instanceof AbstractType) {
$subfields = $this->analyzeSelectionSet($selectionSet, $type, $implementors);
$this->types[$type->name] = array_unique(array_merge(
array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [],
array_keys($subfields)
Expand Down
196 changes: 75 additions & 121 deletions tests/Type/QueryPlanTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ public function testMergedFragmentsQueryPlan() : void
self::assertFalse($queryPlan->hasType('Test'));
}

public function testQueryPlanOnInterfaceGroupingImplementorFields() : void
public function testQueryPlanGroupingImplementorFieldsForAbstractTypes() : void
{
$car = null;

Expand All @@ -715,13 +715,38 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void
},
]);

$manualTransmission = new ObjectType([
'name' => 'ManualTransmission',
'fields' => [
'speed' => Type::int(),
'overdrive' => Type::boolean(),
],
]);

$automaticTransmission = new ObjectType([
'name' => 'AutomaticTransmission',
'fields' => [
'speed' => Type::int(),
'sportMode' => Type::boolean(),
],
]);

$transmission = new UnionType([
'name' => 'Transmission',
'types' => [$manualTransmission, $automaticTransmission],
'resolveType' => static function () use ($manualTransmission) {
return $manualTransmission;
},
]);

$car = new ObjectType([
'name' => 'Car',
'fields' => [
'id' => Type::int(),
'owner' => Type::string(),
'mark' => Type::string(),
'model' => Type::string(),
'transmission' => $transmission,
],
'interfaces' => [$item],
]);
Expand All @@ -744,6 +769,16 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void
... on Car {
mark
model
transmission {
... on ManualTransmission {
speed
overdrive
}
... on AutomaticTransmission {
speed
sportMode
}
}
}
... on Building {
city
Expand Down Expand Up @@ -786,6 +821,43 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void
'fields' => [],
'args' => [],
],
'transmission' => [
'type' => $transmission,
'fields' => [],
'args' => [],
'implementors' => [
'ManualTransmission' => [
'type' => $manualTransmission,
'fields' => [
'speed' => [
'type' => Type::int(),
'fields' => [],
'args' => [],
],
'overdrive' => [
'type' => Type::boolean(),
'fields' => [],
'args' => [],
],
],
],
'AutomaticTransmission' => [
'type' => $automaticTransmission,
'fields' => [
'speed' => [
'type' => Type::int(),
'fields' => [],
'args' => [],
],
'sportMode' => [
'type' => Type::boolean(),
'fields' => [],
'args' => [],
],
],
],
],
],
],
],
'Building' => [
Expand All @@ -806,9 +878,9 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void
],
];

$expectedReferencedTypes = ['Car', 'Building', 'Item'];
$expectedReferencedTypes = ['ManualTransmission', 'AutomaticTransmission', 'Transmission', 'Car', 'Building', 'Item'];

$expectedReferencedFields = ['mark', 'model', 'city', 'address', 'id', 'owner'];
$expectedReferencedFields = ['speed', 'overdrive', 'sportMode', 'mark', 'model', 'transmission', 'city', 'address', 'id', 'owner'];

$expectedItemSubFields = ['id', 'owner'];
$expectedBuildingSubFields = ['city', 'address'];
Expand Down Expand Up @@ -846,122 +918,4 @@ public function testQueryPlanOnInterfaceGroupingImplementorFields() : void
self::assertEquals($expectedItemSubFields, $queryPlan->subFields('Item'));
self::assertEquals($expectedBuildingSubFields, $queryPlan->subFields('Building'));
}

public function testQueryPlanOnUnionGroupingImplementorFields() : void
{
$car = new ObjectType([
'name' => 'Car',
'fields' => [
'mark' => Type::string(),
'model' => Type::string(),
],
]);

$building = new ObjectType([
'name' => 'Building',
'fields' => [
'city' => Type::string(),
'address' => Type::string(),
],
]);

$item = new UnionType([
'name' => 'Item',
'types' => [$car, $building],
'resolveType' => static function () use ($car) {
return $car;
},
]);

$query = '{
item {
... on Car {
mark
model
}
... on Building {
city
}
...BuildingFragment
}
}
fragment BuildingFragment on Building {
address
}';

$expectedResult = [
'data' => ['item' => null],
];

$expectedQueryPlan = [
'fields' => [],
'implementors' => [
'Car' => [
'type' => $car,
'fields' => [
'mark' => [
'type' => Type::string(),
'fields' => [],
'args' => [],
],
'model' => [
'type' => Type::string(),
'fields' => [],
'args' => [],
],
],
],
'Building' => [
'type' => $building,
'fields' => [
'city' => [
'type' => Type::string(),
'fields' => [],
'args' => [],
],
'address' => [
'type' => Type::string(),
'fields' => [],
'args' => [],
],
],
],
],
];

$expectedReferencedTypes = ['Car', 'Building', 'Item'];

$expectedReferencedFields = ['mark', 'model', 'city', 'address'];

$expectedBuildingSubFields = ['city', 'address'];

$hasCalled = false;
/** @var QueryPlan $queryPlan */
$queryPlan = null;

$root = new ObjectType([
'name' => 'Query',
'fields' => [
'item' => [
'type' => $item,
'resolve' => static function ($value, $args, $context, ResolveInfo $info) use (&$hasCalled, &$queryPlan) {
$hasCalled = true;
$queryPlan = $info->lookAhead(['group-implementor-fields']);

return null;
},
],
],
]);

$schema = new Schema(['query' => $root]);
$result = GraphQL::executeQuery($schema, $query)->toArray();

self::assertTrue($hasCalled);
self::assertEquals($expectedResult, $result);
self::assertEquals($expectedQueryPlan, $queryPlan->queryPlan());
self::assertEquals($expectedReferencedTypes, $queryPlan->getReferencedTypes());
self::assertEquals($expectedReferencedFields, $queryPlan->getReferencedFields());
self::assertEquals($expectedBuildingSubFields, $queryPlan->subFields('Building'));
}
}