Skip to content
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

Add method getFieldSelectionWithAliases to class ResolveInfo #1648

Merged
merged 10 commits into from
Dec 4, 2024
179 changes: 179 additions & 0 deletions src/Type/Definition/ResolveInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Values;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;

/**
Expand Down Expand Up @@ -214,6 +216,126 @@
return $fields;
}

/**
* Helper method that returns names of all fields selected in query for
* $this->fieldName up to $depth levels.
* For each field there is an "aliases" key regrouping all aliases of this field
* (if a field is not aliased, its name is present here as a key)
* For each of those "alias" you can find "args" key containing the arguments of the alias and the "fields" key
* containing the subfield of this field/alias. Each of those field have the same structure as described above.
*
* Example:
* query MyQuery{
spawnia marked this conversation as resolved.
Show resolved Hide resolved
* {
* root {
* id,
spawnia marked this conversation as resolved.
Show resolved Hide resolved
* nested {
* nested1(myArg:1)
* nested1Bis:nested1
* }
* alias1:nested {
* nested1(myArg:2, mySecondAg:"test")
* }
spawnia marked this conversation as resolved.
Show resolved Hide resolved
* }
* }
*
* Given this ResolveInfo instance is a part of "root" field resolution, and $depth === 1,
* method will return:
* [
* 'id' => [
* 'aliases' => [
* 'id' => [
* [args] => []
* ]
* ]
* ],
* 'nested' => [
* 'aliases' => [
* 'nested' => [
* ['args'] => [],
spawnia marked this conversation as resolved.
Show resolved Hide resolved
* ['fields'] => [
* 'nested1' => [
* 'aliases' => [
* 'nested1' => [
* ['args'] => [
* 'myArg' => 1
* ]
* ],
* 'nested1Bis' => [
* ['args'] => []
* ]
* ]
* ]
* ]
* ],
* 'alias1' => [
* ['args'] => [],
* ['fields'] => [
* 'nested1' => [
* 'aliases' => [
* 'nested1' => [
* ['args'] => [
* 'myArg' => 2,
* 'mySecondAg' => "test"
* ]
* ]
* ]
* ]
* ]
* ]
* ]
* ]
* ]
*
* 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.
* You still can alias the union type fields with the same name in order to extract their corresponding args.
*
* Example:
* query MyQuery{
spawnia marked this conversation as resolved.
Show resolved Hide resolved
* {
* root {
* id,
spawnia marked this conversation as resolved.
Show resolved Hide resolved
* unionPerson {
* ...on Child {
* name
* birthdate(format:"d/m/Y")
* }
* ...on Adult {
* adultName:name
* adultBirthDate:birthdate(format:"Y-m-d")
* job
* }
* }
* }
* }
*
* @param int $depth How many levels to include in output
*
* @throws \Exception
* @throws Error
* @throws InvariantViolation
*
* @return array<string, mixed>
*
* @api
*/
public function getFieldSelectionWithAlias(int $depth = 0): array
spawnia marked this conversation as resolved.
Show resolved Hide resolved
{
$fields = [];

foreach ($this->fieldNodes as $fieldNode) {
if (isset($fieldNode->selectionSet)) {
$fields = \array_merge_recursive(
$fields,
$this->foldSelectionWithAlias($fieldNode->selectionSet, $depth, $this->parentType->getField($fieldNode->name->value)->getType())
);
}
}

return $fields;
}

/**
* @param QueryPlanOptions $options
*
Expand Down Expand Up @@ -266,4 +388,61 @@

return $fields;
}

/**
* @throws InvariantViolation|Error|\Exception
*
* @return array<string>
*/
private function foldSelectionWithAlias(SelectionSetNode $selectionSet, int $descend, Type $parentType): array
{
/** @var array<string, bool> $fields */
$fields = [];

foreach ($selectionSet->selections as $selectionNode) {
if ($selectionNode instanceof FieldNode) {
$fieldName = $selectionNode->name->value;
$aliasName = $selectionNode->alias->value ?? $fieldName;

if ($fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
continue;

Check warning on line 408 in src/Type/Definition/ResolveInfo.php

View check run for this annotation

Codecov / codecov/patch

src/Type/Definition/ResolveInfo.php#L408

Added line #L408 was not covered by tests
}

assert($parentType instanceof HasFieldsType, 'ensured by query validation and the check above which excludes union types');
spawnia marked this conversation as resolved.
Show resolved Hide resolved

$fieldDef = $parentType->getField($fieldName);
$fieldType = Type::getNamedType($fieldDef->getType());
$fields[$fieldName]['aliases'][$aliasName]['args'] = Values::getArgumentValues($fieldDef, $selectionNode, $this->variableValues);

if ($descend > 0 && $selectionNode->selectionSet !== null) {
$fields[$fieldName]['aliases'][$aliasName]['fields'] = $this->foldSelectionWithAlias($selectionNode->selectionSet, $descend - 1, $fieldType);

Check failure on line 418 in src/Type/Definition/ResolveInfo.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (7.4)

Parameter #3 $parentType of method GraphQL\Type\Definition\ResolveInfo::foldSelectionWithAlias() expects GraphQL\Type\Definition\Type, (GraphQL\Type\Definition\NamedType&GraphQL\Type\Definition\Type)|null given.

Check failure on line 418 in src/Type/Definition/ResolveInfo.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (8.0)

Parameter #3 $parentType of method GraphQL\Type\Definition\ResolveInfo::foldSelectionWithAlias() expects GraphQL\Type\Definition\Type, (GraphQL\Type\Definition\NamedType&GraphQL\Type\Definition\Type)|null given.

Check failure on line 418 in src/Type/Definition/ResolveInfo.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (8.1)

Parameter #3 $parentType of method GraphQL\Type\Definition\ResolveInfo::foldSelectionWithAlias() expects GraphQL\Type\Definition\Type, (GraphQL\Type\Definition\NamedType&GraphQL\Type\Definition\Type)|null given.

Check failure on line 418 in src/Type/Definition/ResolveInfo.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (8.2)

Parameter #3 $parentType of method GraphQL\Type\Definition\ResolveInfo::foldSelectionWithAlias() expects GraphQL\Type\Definition\Type, (GraphQL\Type\Definition\NamedType&GraphQL\Type\Definition\Type)|null given.

Check failure on line 418 in src/Type/Definition/ResolveInfo.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (8.3)

Parameter #3 $parentType of method GraphQL\Type\Definition\ResolveInfo::foldSelectionWithAlias() expects GraphQL\Type\Definition\Type, (GraphQL\Type\Definition\NamedType&GraphQL\Type\Definition\Type)|null given.
}
} elseif ($selectionNode instanceof FragmentSpreadNode) {
$spreadName = $selectionNode->name->value;
if (isset($this->fragments[$spreadName])) {
$fragment = $this->fragments[$spreadName];
$fieldType = $this->schema->getType($fragment->typeCondition->name->value);
assert($fieldType instanceof Type, 'ensured by query validation');

$fields = \array_merge_recursive(
$this->foldSelectionWithAlias($fragment->selectionSet, $descend, $fieldType),
$fields
);
}
} elseif ($selectionNode instanceof InlineFragmentNode) {
$typeCondition = $selectionNode->typeCondition;
$fieldType = $typeCondition === null
? $parentType
: $this->schema->getType($typeCondition->name->value);
assert($fieldType instanceof Type, 'ensured by query validation');

Check warning on line 437 in src/Type/Definition/ResolveInfo.php

View check run for this annotation

Codecov / codecov/patch

src/Type/Definition/ResolveInfo.php#L432-L437

Added lines #L432 - L437 were not covered by tests

$fields = \array_merge_recursive(
$this->foldSelectionWithAlias($selectionNode->selectionSet, $descend, $fieldType),
$fields
);

Check warning on line 442 in src/Type/Definition/ResolveInfo.php

View check run for this annotation

Codecov / codecov/patch

src/Type/Definition/ResolveInfo.php#L439-L442

Added lines #L439 - L442 were not covered by tests
}
}

return $fields;
}
}
Loading
Loading