Completed
Pull Request — master (#200)
by Quang
08:11
created

FieldCollector::shouldIncludeNode()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 3
nop 1
1
<?php
2
3
namespace Digia\GraphQL\Execution;
4
5
use Digia\GraphQL\Error\InvalidTypeException;
6
use Digia\GraphQL\Language\Node\FieldNode;
7
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
8
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
9
use Digia\GraphQL\Language\Node\InlineFragmentNode;
10
use Digia\GraphQL\Language\Node\NodeInterface;
11
use Digia\GraphQL\Language\Node\SelectionSetNode;
12
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
13
use Digia\GraphQL\Type\Definition\ObjectType;
14
use function Digia\GraphQL\Util\typeFromAST;
15
16
/**
17
 * Class FieldCollector
18
 * @package Digia\GraphQL\Execution
19
 */
20
class FieldCollector
21
{
22
    /**
23
     * @var ExecutionContext
24
     */
25
    protected $context;
26
27
    /**
28
     * FieldCollector constructor.
29
     * @param ExecutionContext $context
30
     */
31
    public function __construct(ExecutionContext $context)
32
    {
33
        $this->context = $context;
34
    }
35
36
    /**
37
     * @param ObjectType       $runtimeType
38
     * @param SelectionSetNode $selectionSet
39
     * @param                  $fields
40
     * @param                  $visitedFragmentNames
41
     * @return mixed
42
     * @throws InvalidTypeException
43
     * @throws \Digia\GraphQL\Error\ExecutionException
44
     * @throws \Digia\GraphQL\Error\InvariantException
45
     */
46
    public function collectFields(
47
        ObjectType $runtimeType,
48
        SelectionSetNode $selectionSet,
49
        &$fields,
50
        &$visitedFragmentNames
51
    ) {
52
        foreach ($selectionSet->getSelections() as $selection) {
53
            // Check if this Node should be included first
54
            if (!$this->shouldIncludeNode($selection)) {
55
                continue;
56
            }
57
            // Collect fields
58
            if ($selection instanceof FieldNode) {
59
                $fieldName = $this->getFieldNameKey($selection);
60
61
                if (!isset($fields[$fieldName])) {
62
                    $fields[$fieldName] = [];
63
                }
64
65
                $fields[$fieldName][] = $selection;
66
            } elseif ($selection instanceof InlineFragmentNode) {
67
                if (!$this->doesFragmentConditionMatch($selection, $runtimeType)) {
68
                    continue;
69
                }
70
71
                $this->collectFields($runtimeType, $selection->getSelectionSet(), $fields, $visitedFragmentNames);
72
            } elseif ($selection instanceof FragmentSpreadNode) {
73
                $fragmentName = $selection->getNameValue();
74
75
                if (!empty($visitedFragmentNames[$fragmentName])) {
76
                    continue;
77
                }
78
79
                $visitedFragmentNames[$fragmentName] = true;
80
                /** @var FragmentDefinitionNode $fragment */
81
                $fragment = $this->context->getFragments()[$fragmentName];
82
                $this->collectFields($runtimeType, $fragment->getSelectionSet(), $fields, $visitedFragmentNames);
83
            }
84
        }
85
86
        return $fields;
87
    }
88
89
    /**
90
     * @TODO: consider to move this to FieldNode
91
     * @param FieldNode $node
92
     * @return string
93
     */
94
    private function getFieldNameKey(FieldNode $node): string
95
    {
96
        return $node->getAlias() ? $node->getAlias()->getValue() : $node->getNameValue();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node->getAlias()...: $node->getNameValue() could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
97
    }
98
99
    /**
100
     * @param $node
101
     * @return bool
102
     * @throws InvalidTypeException
103
     * @throws \Digia\GraphQL\Error\ExecutionException
104
     * @throws \Digia\GraphQL\Error\InvariantException
105
     */
106
    private function shouldIncludeNode(NodeInterface $node): bool
107
    {
108
109
        $contextVariables = $this->context->getVariableValues();
110
111
        $skip = coerceDirectiveValues(SkipDirective(), $node, $contextVariables);
112
113
        if ($skip && $skip['if'] === true) {
114
            return false;
115
        }
116
117
        $include = coerceDirectiveValues(IncludeDirective(), $node, $contextVariables);
118
119
        if ($include && $include['if'] === false) {
120
            return false;
121
        }
122
123
        return true;
124
    }
125
126
    /**
127
     * @param FragmentDefinitionNode|InlineFragmentNode $fragment
128
     * @param ObjectType                                $type
129
     * @return bool
130
     * @throws InvalidTypeException
131
     */
132
    private function doesFragmentConditionMatch(
133
        NodeInterface $fragment,
134
        ObjectType $type
135
    ): bool {
136
        $typeConditionNode = $fragment->getTypeCondition();
0 ignored issues
show
Bug introduced by
The method getTypeCondition() does not exist on Digia\GraphQL\Language\Node\NodeInterface. It seems like you code against a sub-type of Digia\GraphQL\Language\Node\NodeInterface such as Digia\GraphQL\Language\Node\InlineFragmentNode or Digia\GraphQL\Language\Node\FragmentDefinitionNode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

136
        /** @scrutinizer ignore-call */ 
137
        $typeConditionNode = $fragment->getTypeCondition();
Loading history...
137
138
        if (!$typeConditionNode) {
139
            return true;
140
        }
141
142
        $conditionalType = typeFromAST($this->context->getSchema(), $typeConditionNode);
143
144
        if ($conditionalType === $type) {
145
            return true;
146
        }
147
148
        if ($conditionalType instanceof AbstractTypeInterface) {
149
            return $this->context->getSchema()->isPossibleType($conditionalType, $type);
150
        }
151
152
        return false;
153
    }
154
155
}