Passed
Pull Request — master (#321)
by Christoffer
04:21 queued 01:06
created

FieldCollector   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 49
dl 0
loc 153
rs 10
c 0
b 0
f 0
wmc 19

4 Methods

Rating   Name   Duplication   Size   Complexity  
B collectFields() 0 53 9
A doesFragmentConditionMatch() 0 19 4
A __construct() 0 6 1
A shouldIncludeNode() 0 17 5
1
<?php
2
3
namespace Digia\GraphQL\Execution\Strategy;
4
5
use Digia\GraphQL\Error\InvalidTypeException;
6
use Digia\GraphQL\Error\InvariantException;
7
use Digia\GraphQL\Execution\ExecutionContext;
8
use Digia\GraphQL\Execution\ExecutionException;
9
use Digia\GraphQL\Execution\ValuesResolver;
10
use Digia\GraphQL\Language\Node\FieldNode;
11
use Digia\GraphQL\Language\Node\FragmentDefinitionNode;
12
use Digia\GraphQL\Language\Node\FragmentSpreadNode;
13
use Digia\GraphQL\Language\Node\InlineFragmentNode;
14
use Digia\GraphQL\Language\Node\NodeInterface;
15
use Digia\GraphQL\Language\Node\SelectionSetNode;
16
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
17
use Digia\GraphQL\Type\Definition\Directive;
18
use Digia\GraphQL\Type\Definition\ObjectType;
19
use Digia\GraphQL\Util\ConversionException;
20
use Digia\GraphQL\Util\TypeASTConverter;
21
22
class FieldCollector
23
{
24
    /**
25
     * @var ExecutionContext
26
     */
27
    protected $context;
28
29
    /**
30
     * @var Directive
31
     */
32
    protected $skipDirective;
33
34
    /**
35
     * @var Directive
36
     */
37
    protected $includeDirective;
38
39
    /**
40
     * @var ValuesResolver
41
     */
42
    protected $valuesResolver;
43
44
    /**
45
     * FieldCollector constructor.
46
     * @param ExecutionContext $context
47
     * @param ValuesResolver   $valuesResolver
48
     */
49
    public function __construct(ExecutionContext $context, ValuesResolver $valuesResolver)
50
    {
51
        $this->context          = $context;
52
        $this->skipDirective    = SkipDirective();
53
        $this->includeDirective = IncludeDirective();
54
        $this->valuesResolver   = $valuesResolver;
55
    }
56
57
    /**
58
     * @param ObjectType       $runtimeType
59
     * @param SelectionSetNode $selectionSet
60
     * @param array            $fields
61
     * @param array            $visitedFragmentNames
62
     * @return array
63
     * @throws InvalidTypeException
64
     * @throws ExecutionException
65
     * @throws InvariantException
66
     * @throws ConversionException
67
     */
68
    public function collectFields(
69
        ObjectType $runtimeType,
70
        SelectionSetNode $selectionSet,
71
        array &$fields,
72
        array &$visitedFragmentNames
73
    ): array {
74
        foreach ($selectionSet->getSelections() as $selection) {
75
            // Check if this Node should be included first
76
            if (!$this->shouldIncludeNode($selection)) {
77
                continue;
78
            }
79
80
            // Collect fields
81
            if ($selection instanceof FieldNode) {
82
                $fieldName = $selection->getAliasOrNameValue();
83
84
                if (!isset($fields[$fieldName])) {
85
                    $fields[$fieldName] = [];
86
                }
87
88
                $fields[$fieldName][] = $selection;
89
90
                continue;
91
            }
92
93
            if ($selection instanceof InlineFragmentNode) {
94
                if (!$this->doesFragmentConditionMatch($selection, $runtimeType)) {
95
                    continue;
96
                }
97
98
                $this->collectFields($runtimeType, $selection->getSelectionSet(), $fields, $visitedFragmentNames);
0 ignored issues
show
Bug introduced by
It seems like $selection->getSelectionSet() can also be of type null; however, parameter $selectionSet of Digia\GraphQL\Execution\...lector::collectFields() does only seem to accept Digia\GraphQL\Language\Node\SelectionSetNode, maybe add an additional type check? ( Ignorable by Annotation )

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

98
                $this->collectFields($runtimeType, /** @scrutinizer ignore-type */ $selection->getSelectionSet(), $fields, $visitedFragmentNames);
Loading history...
99
100
                continue;
101
            }
102
103
            if ($selection instanceof FragmentSpreadNode) {
104
                $fragmentName = $selection->getNameValue();
105
106
                if (isset($visitedFragmentNames[$fragmentName])) {
107
                    continue;
108
                }
109
110
                $visitedFragmentNames[$fragmentName] = true;
111
112
                $fragment = $this->context->getFragments()[$fragmentName];
113
114
                $this->collectFields($runtimeType, $fragment->getSelectionSet(), $fields, $visitedFragmentNames);
115
116
                continue;
117
            }
118
        }
119
120
        return $fields;
121
    }
122
123
    /**
124
     * @param NodeInterface $node
125
     * @return bool
126
     * @throws ExecutionException
127
     * @throws InvalidTypeException
128
     * @throws InvariantException
129
     */
130
    protected function shouldIncludeNode(NodeInterface $node): bool
131
    {
132
        $contextVariables = $this->context->getVariableValues();
133
134
        $skip = $this->valuesResolver->coerceDirectiveValues($this->skipDirective, $node, $contextVariables);
135
136
        if ($skip && $skip['if'] === true) {
137
            return false;
138
        }
139
140
        $include = $this->valuesResolver->coerceDirectiveValues($this->includeDirective, $node, $contextVariables);
141
142
        if ($include && $include['if'] === false) {
143
            return false;
144
        }
145
146
        return true;
147
    }
148
149
    /**
150
     * @param FragmentDefinitionNode|InlineFragmentNode $fragment
151
     * @param ObjectType                                $type
152
     * @return bool
153
     * @throws InvariantException
154
     * @throws ConversionException
155
     */
156
    protected function doesFragmentConditionMatch($fragment, ObjectType $type): bool
157
    {
158
        $typeConditionNode = $fragment->getTypeCondition();
159
160
        if (null === $typeConditionNode) {
161
            return true;
162
        }
163
164
        $conditionalType = TypeASTConverter::convert($this->context->getSchema(), $typeConditionNode);
165
166
        if ($type === $conditionalType) {
0 ignored issues
show
introduced by
The condition $type === $conditionalType is always false.
Loading history...
167
            return true;
168
        }
169
170
        if ($conditionalType instanceof AbstractTypeInterface) {
171
            return $this->context->getSchema()->isPossibleType($conditionalType, $type);
172
        }
173
174
        return false;
175
    }
176
}
177