Failed Conditions
Push — master ( 48b44f...392b56 )
by Vladimir
04:42
created

QueryComplexity::getRawVariableValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
crap 1
1
<?php
2
namespace GraphQL\Validator\Rules;
3
4
use GraphQL\Error\Error;
5
use GraphQL\Executor\Values;
6
use GraphQL\Language\AST\FieldNode;
7
use GraphQL\Language\AST\FragmentSpreadNode;
8
use GraphQL\Language\AST\InlineFragmentNode;
9
use GraphQL\Language\AST\Node;
10
use GraphQL\Language\AST\NodeKind;
11
use GraphQL\Language\AST\OperationDefinitionNode;
12
use GraphQL\Language\AST\SelectionSetNode;
13
use GraphQL\Language\Visitor;
14
use GraphQL\Type\Definition\Directive;
15
use GraphQL\Type\Definition\FieldDefinition;
16
use GraphQL\Validator\ValidationContext;
17
18
class QueryComplexity extends AbstractQuerySecurity
19
{
20
    private $maxQueryComplexity;
21
22
    private $rawVariableValues = [];
23
24
    private $variableDefs;
25
26
    private $fieldNodeAndDefs;
27
28
    /**
29
     * @var ValidationContext
30
     */
31
    private $context;
32
33 2
    public function __construct($maxQueryComplexity)
34
    {
35 2
        $this->setMaxQueryComplexity($maxQueryComplexity);
36 2
    }
37
38 13
    public static function maxQueryComplexityErrorMessage($max, $count)
39
    {
40 13
        return sprintf('Max query complexity should be %d but got %d.', $max, $count);
41
    }
42
43
    /**
44
     * Set max query complexity. If equal to 0 no check is done. Must be greater or equal to 0.
45
     *
46
     * @param $maxQueryComplexity
47
     */
48 18
    public function setMaxQueryComplexity($maxQueryComplexity)
49
    {
50 18
        $this->checkIfGreaterOrEqualToZero('maxQueryComplexity', $maxQueryComplexity);
51
52 17
        $this->maxQueryComplexity = (int) $maxQueryComplexity;
53 17
    }
54
55 117
    public function getMaxQueryComplexity()
56
    {
57 117
        return $this->maxQueryComplexity;
58
    }
59
60 99
    public function setRawVariableValues(array $rawVariableValues = null)
61
    {
62 99
        $this->rawVariableValues = $rawVariableValues ?: [];
63 99
    }
64
65 16
    public function getRawVariableValues()
66
    {
67 16
        return $this->rawVariableValues;
68
    }
69
70 117
    public function getVisitor(ValidationContext $context)
71
    {
72 117
        $this->context = $context;
73
74 117
        $this->variableDefs = new \ArrayObject();
75 117
        $this->fieldNodeAndDefs = new \ArrayObject();
76 117
        $complexity = 0;
77
78 117
        return $this->invokeIfNeeded(
79 117
            $context,
80
            [
81
                NodeKind::SELECTION_SET => function (SelectionSetNode $selectionSet) use ($context) {
82 16
                    $this->fieldNodeAndDefs = $this->collectFieldASTsAndDefs(
83 16
                        $context,
84 16
                        $context->getParentType(),
85 16
                        $selectionSet,
86 16
                        null,
87 16
                        $this->fieldNodeAndDefs
88
                    );
89 117
                },
90
                NodeKind::VARIABLE_DEFINITION => function ($def) {
91 6
                    $this->variableDefs[] = $def;
92 6
                    return Visitor::skipNode();
93 117
                },
94
                NodeKind::OPERATION_DEFINITION => [
95
                    'leave' => function (OperationDefinitionNode $operationDefinition) use ($context, &$complexity) {
96 16
                        $errors = $context->getErrors();
97
98 16
                        if (empty($errors)) {
99 16
                            $complexity = $this->fieldComplexity($operationDefinition, $complexity);
100
101 15
                            if ($complexity > $this->getMaxQueryComplexity()) {
102 13
                                $context->reportError(
103 13
                                    new Error($this->maxQueryComplexityErrorMessage($this->getMaxQueryComplexity(), $complexity))
104
                                );
105
                            }
106
                        }
107 117
                    },
108
                ],
109
            ]
110
        );
111
    }
112
113 16
    private function fieldComplexity($node, $complexity = 0)
114
    {
115 16
        if (isset($node->selectionSet) && $node->selectionSet instanceof SelectionSetNode) {
116 16
            foreach ($node->selectionSet->selections as $childNode) {
117 16
                $complexity = $this->nodeComplexity($childNode, $complexity);
118
            }
119
        }
120
121 16
        return $complexity;
122
    }
123
124 16
    private function nodeComplexity(Node $node, $complexity = 0)
125
    {
126 16
        switch ($node->kind) {
127 16
            case NodeKind::FIELD:
128
                /* @var FieldNode $node */
129
                // default values
130 16
                $args = [];
131 16
                $complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
132
133
                // calculate children complexity if needed
134 16
                $childrenComplexity = 0;
135
136
                // node has children?
137 16
                if (isset($node->selectionSet)) {
138 16
                    $childrenComplexity = $this->fieldComplexity($node);
139
                }
140
141 16
                $astFieldInfo = $this->astFieldInfo($node);
142 16
                $fieldDef = $astFieldInfo[1];
143
144 16
                if ($fieldDef instanceof FieldDefinition) {
145 16
                    if ($this->directiveExcludesField($node)) {
146 3
                        break;
147
                    }
148
149 16
                    $args = $this->buildFieldArguments($node);
150
                    //get complexity fn using fieldDef complexity
151 16
                    if (method_exists($fieldDef, 'getComplexityFn')) {
152 16
                        $complexityFn = $fieldDef->getComplexityFn();
153
                    }
154
                }
155
156 16
                $complexity += call_user_func_array($complexityFn, [$childrenComplexity, $args]);
157 16
                break;
158
159 3
            case NodeKind::INLINE_FRAGMENT:
160
                /* @var InlineFragmentNode $node */
161
                // node has children?
162 1
                if (isset($node->selectionSet)) {
163 1
                    $complexity = $this->fieldComplexity($node, $complexity);
164
                }
165 1
                break;
166
167 2
            case NodeKind::FRAGMENT_SPREAD:
168
                /* @var FragmentSpreadNode $node */
169 2
                $fragment = $this->getFragment($node);
170
171 2
                if (null !== $fragment) {
172 2
                    $complexity = $this->fieldComplexity($fragment, $complexity);
173
                }
174 2
                break;
175
        }
176
177 16
        return $complexity;
178
    }
179
180 16
    private function astFieldInfo(FieldNode $field)
181
    {
182 16
        $fieldName = $this->getFieldName($field);
183 16
        $astFieldInfo = [null, null];
184 16
        if (isset($this->fieldNodeAndDefs[$fieldName])) {
185 16
            foreach ($this->fieldNodeAndDefs[$fieldName] as $astAndDef) {
186 16
                if ($astAndDef[0] == $field) {
187 16
                    $astFieldInfo = $astAndDef;
188 16
                    break;
189
                }
190
            }
191
        }
192
193 16
        return $astFieldInfo;
194
    }
195
196 16
    private function buildFieldArguments(FieldNode $node)
197
    {
198 16
        $rawVariableValues = $this->getRawVariableValues();
199 16
        $astFieldInfo = $this->astFieldInfo($node);
200 16
        $fieldDef = $astFieldInfo[1];
201
202 16
        $args = [];
203
204 16
        if ($fieldDef instanceof FieldDefinition) {
205 16
            $variableValuesResult = Values::getVariableValues(
206 16
                $this->context->getSchema(),
207 16
                $this->variableDefs,
208 16
                $rawVariableValues
209
            );
210
211 16
            if ($variableValuesResult['errors']) {
212
                throw new Error(implode("\n\n", array_map(
213
                    function ($error) {
214
                        return $error->getMessage();
215
                    }
216
                , $variableValuesResult['errors'])));
217
            }
218 16
            $variableValues = $variableValuesResult['coerced'];
219
220 16
            $args = Values::getArgumentValues($fieldDef, $node, $variableValues);
221
        }
222
223 16
        return $args;
224
    }
225
226 16
    private function directiveExcludesField(FieldNode $node) {
227 16
        foreach ($node->directives as $directiveNode) {
228 5
            if ($directiveNode->name->value === 'deprecated') {
229
                return false;
230
            }
231
232 5
            $variableValuesResult = Values::getVariableValues(
233 5
                $this->context->getSchema(),
234 5
                $this->variableDefs,
235 5
                $this->getRawVariableValues()
236
            );
237
238 5
            if ($variableValuesResult['errors']) {
239
                throw new Error(implode("\n\n", array_map(
240
                    function ($error) {
241
                        return $error->getMessage();
242
                    }
243
                    , $variableValuesResult['errors'])));
244
            }
245 5
            $variableValues = $variableValuesResult['coerced'];
246
247 5
            if ($directiveNode->name->value === 'include') {
248 3
                $directive = Directive::includeDirective();
249 3
                $directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
250
251 3
                return !$directiveArgs['if'];
252
            } else {
253 3
                $directive = Directive::skipDirective();
254 3
                $directiveArgs = Values::getArgumentValues($directive, $directiveNode, $variableValues);
255
256 3
                return $directiveArgs['if'];
257
            }
258
        }
259 16
    }
260
261 117
    protected function isEnabled()
262
    {
263 117
        return $this->getMaxQueryComplexity() !== static::DISABLED;
264
    }
265
}
266