Passed
Pull Request — master (#169)
by Quang
02:29
created

ValueHelper::resolveInputObjectType()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 17
nc 6
nop 3
1
<?php
2
3
namespace Digia\GraphQL\Util;
4
5
use Digia\GraphQL\Error\InvalidTypeException;
6
use Digia\GraphQL\Error\InvariantException;
7
use Digia\GraphQL\Error\ResolutionException;
8
use Digia\GraphQL\Language\Node\ArgumentNode;
9
use Digia\GraphQL\Language\Node\EnumValueNode;
10
use Digia\GraphQL\Language\Node\ListValueNode;
11
use Digia\GraphQL\Language\Node\NodeInterface;
12
use Digia\GraphQL\Language\Node\NullValueNode;
13
use Digia\GraphQL\Language\Node\ObjectFieldNode;
14
use Digia\GraphQL\Language\Node\ObjectValueNode;
15
use Digia\GraphQL\Language\Node\ValueNodeInterface;
16
use Digia\GraphQL\Language\Node\VariableNode;
17
use Digia\GraphQL\Type\Definition\EnumType;
18
use Digia\GraphQL\Type\Definition\InputObjectType;
19
use Digia\GraphQL\Type\Definition\InputTypeInterface;
20
use Digia\GraphQL\Type\Definition\ListType;
21
use Digia\GraphQL\Type\Definition\NonNullType;
22
use Digia\GraphQL\Type\Definition\ScalarType;
23
use Digia\GraphQL\Type\Definition\TypeInterface;
24
use function Digia\GraphQL\printNode;
25
26
class ValueHelper
27
{
28
    /**
29
     * @param array $argumentsA
30
     * @param array $argumentsB
31
     * @return bool
32
     */
33
    function compareArguments(array $argumentsA, array $argumentsB): bool
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
34
    {
35
        if (\count($argumentsA) !== \count($argumentsB)) {
36
            return false;
37
        }
38
39
        return arrayEvery($argumentsA, function (ArgumentNode $argumentA) use ($argumentsB) {
40
            $argumentB = find($argumentsB, function (ArgumentNode $argument) use ($argumentA) {
41
                return $argument->getNameValue() === $argumentA->getNameValue();
42
            });
43
44
            if (null === $argumentB) {
45
                return false;
46
            }
47
48
            return $this->compareValues($argumentA->getValue(), $argumentB->getValue());
49
        });
50
    }
51
52
    /**
53
     * @param $valueA
54
     * @param $valueB
55
     * @return bool
56
     */
57
    public function compareValues($valueA, $valueB): bool
58
    {
59
        return printNode($valueA) === printNode($valueB);
60
    }
61
62
    /**
63
     * Produces a PHP value given a GraphQL Value AST.
64
     *
65
     * A GraphQL type must be provided, which will be used to interpret different
66
     * GraphQL Value literals.
67
     *
68
     * Returns `undefined` when the value could not be validly resolved according to
69
     * the provided type.
70
     *
71
     * | GraphQL Value        | JSON Value    |
72
     * | -------------------- | ------------- |
73
     * | Input Object         | Object        |
74
     * | List                 | Array         |
75
     * | Boolean              | Boolean       |
76
     * | String               | String        |
77
     * | Int / Float          | Number        |
78
     * | Enum Value           | Mixed         |
79
     * | NullValue            | null          |
80
     *
81
     * @param NodeInterface|ValueNodeInterface|null   $node
82
     * @param InputTypeInterface|NonNullType|ListType $type
83
     * @param array                                   $variables
84
     * @return mixed|null
85
     * @throws InvalidTypeException
86
     * @throws InvariantException
87
     * @throws ResolutionException
88
     */
89
    public function fromAST(?NodeInterface $node, TypeInterface $type, array $variables = [])
90
    {
91
        if (null === $node) {
92
            throw new ResolutionException('Node is not defined.');
93
        }
94
95
        if ($type instanceof NonNullType) {
96
            return $this->resolveNonNullType($node, $type, $variables);
97
        }
98
99
        if ($node instanceof NullValueNode) {
100
            // This is explicitly returning the value null.
101
            return null;
102
        }
103
104
        if ($node instanceof VariableNode) {
105
            $variableName = $node->getNameValue();
106
107
            if (!isset($variables[$variableName])) {
108
                throw new ResolutionException(
109
                    \sprintf('Cannot resolve value for missing variable "%s".', $variableName)
110
                );
111
            }
112
113
            // Note: we're not doing any checking that this variable is correct. We're
114
            // assuming that this query has been validated and the variable usage here
115
            // is of the correct type.
116
            return $variables[$variableName];
117
        }
118
119
        if ($type instanceof ListType) {
120
            return $this->resolveListType($node, $type, $variables);
121
        }
122
123
        if ($type instanceof InputObjectType) {
124
            return $this->resolveInputObjectType($node, $type, $variables);
125
        }
126
127
        if ($type instanceof EnumType) {
128
            return $this->resolveEnumType($node, $type);
129
        }
130
131
        if ($type instanceof ScalarType) {
132
            return $this->resolveScalarType($node, $type, $variables);
133
        }
134
135
        throw new InvalidTypeException(\sprintf('Unknown type: %s', $type));
0 ignored issues
show
Bug introduced by
$type of type Digia\GraphQL\Type\Definition\TypeInterface is incompatible with the type string expected by parameter $args of sprintf(). ( Ignorable by Annotation )

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

135
        throw new InvalidTypeException(\sprintf('Unknown type: %s', /** @scrutinizer ignore-type */ $type));
Loading history...
136
    }
137
138
    /**
139
     * @param NodeInterface|ValueNodeInterface $node
140
     * @param NonNullType                      $type
141
     * @param array                            $variables
142
     * @return mixed|null
143
     * @throws InvalidTypeException
144
     * @throws InvariantException
145
     * @throws ResolutionException
146
     */
147
    protected function resolveNonNullType(NodeInterface $node, NonNullType $type, array $variables = [])
148
    {
149
        if ($node instanceof NullValueNode) {
150
            throw new ResolutionException('Cannot resolve non-null values from null value node.');
151
        }
152
153
        return $this->fromAST($node, $type->getOfType(), $variables);
154
    }
155
156
    /**
157
     * @param NodeInterface|ValueNodeInterface $node
158
     * @param ListType                         $type
159
     * @return array|null
160
     * @throws InvalidTypeException
161
     * @throws InvariantException
162
     * @throws ResolutionException
163
     */
164
    protected function resolveListType(NodeInterface $node, ListType $type, array $variables = [])
165
    {
166
        $itemType = $type->getOfType();
167
168
        if ($node instanceof ListValueNode) {
169
            $resolvedValues = [];
170
171
            foreach ($node->getValues() as $value) {
172
                if ($this->isMissingVariable($value, $variables)) {
173
                    if ($itemType instanceof NonNullType) {
174
                        // If an array contains a missing variable, it is either resolved to
175
                        // null or if the item type is non-null, it considered invalid.
176
                        throw new ResolutionException('Cannot resolve value for missing non-null list item.');
177
                    }
178
179
                    $resolvedValues[] = null;
180
                } else {
181
                    $resolvedValues[] = $this->fromAST($value, $itemType, $variables);
182
                }
183
            }
184
185
            return $resolvedValues;
186
        }
187
188
        return [$this->fromAST($node, $itemType, $variables)];
189
    }
190
191
    /**
192
     * @param NodeInterface|ValueNodeInterface $node
193
     * @param InputObjectType                  $type
194
     * @param array                            $variables
195
     * @return array|null
196
     * @throws InvalidTypeException
197
     * @throws InvariantException
198
     * @throws ResolutionException
199
     */
200
    protected function resolveInputObjectType(NodeInterface $node, InputObjectType $type, array $variables = [])
201
    {
202
        if (!$node instanceof ObjectValueNode) {
203
            throw new ResolutionException('Input object values can only be resolved form object value nodes.');
204
        }
205
206
        $resolvedValues = [];
207
208
        /** @var ObjectFieldNode[] $fieldNodes */
209
        $fieldNodes = keyMap($node->getFields(), function (ObjectFieldNode $value) {
210
            return $value->getNameValue();
211
        });
212
213
        foreach ($type->getFields() as $field) {
214
            $name      = $field->getName();
215
            $fieldNode = $fieldNodes[$name] ?? null;
216
217
            if (null === $fieldNode || $this->isMissingVariable($fieldNode->getValue(), $variables)) {
218
                if (null !== $field->getDefaultValue()) {
219
                    $resolvedValues[$name] = $field->getDefaultValue();
220
                } elseif ($field->getType() instanceof NonNullType) {
221
                    throw new ResolutionException('Cannot resolve input object value for missing non-null field.');
222
                }
223
                continue;
224
            }
225
226
            $fieldValue = $this->fromAST($fieldNode->getValue(), $field->getType(), $variables);
227
228
            $resolvedValues[$name] = $fieldValue;
229
        }
230
231
        return $resolvedValues;
232
    }
233
234
    /**
235
     * @param NodeInterface|ValueNodeInterface $node
236
     * @param EnumType                         $type
237
     * @return mixed|null
238
     * @throws InvariantException
239
     * @throws ResolutionException
240
     */
241
    protected function resolveEnumType(NodeInterface $node, EnumType $type)
242
    {
243
        if (!$node instanceof EnumValueNode) {
244
            throw new ResolutionException('Enum values can only be resolved from enum value nodes.');
245
        }
246
247
        $name = $node->getValue();
248
249
        $enumValue = $type->getValue($name);
250
251
        if (null === $enumValue) {
252
            throw new ResolutionException(\sprintf('Cannot resolve enum value for missing value "%s".', $name));
253
        }
254
255
        return $enumValue->getValue();
256
    }
257
258
    /**
259
     * @param NodeInterface|ValueNodeInterface $node
260
     * @param ScalarType                       $type
261
     * @param array                            $variables
262
     * @return mixed|null
263
     * @throws ResolutionException
264
     */
265
    protected function resolveScalarType(NodeInterface $node, ScalarType $type, array $variables = [])
266
    {
267
        // Scalars fulfill parsing a literal value via parseLiteral().
268
        // Invalid values represent a failure to parse correctly, in which case
269
        // no value is returned.
270
        $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
271
272
        try {
273
            $result = $type->parseLiteral($node, $variables);
274
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
275
276
        }
277
278
        if (null === $result) {
279
            throw new ResolutionException(\sprintf('Failed to parse literal for scalar type "%s".', (string)$type));
280
        }
281
282
        return $result;
283
    }
284
285
    /**
286
     * @param ValueNodeInterface $node
287
     * @param array              $variables
288
     * @return bool
289
     */
290
    protected function isMissingVariable(ValueNodeInterface $node, array $variables): bool
291
    {
292
        return $node instanceof VariableNode && !isset($variables[$node->getNameValue()]);
293
    }
294
}
295