Passed
Pull Request — master (#153)
by Christoffer
02:21
created

ValueNodeResolver::resolveNonNullType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
c 0
b 0
f 0
rs 9.4285
cc 2
eloc 3
nc 2
nop 3
1
<?php
2
3
namespace Digia\GraphQL\Util;
4
5
use Digia\GraphQL\Error\ResolutionException;
6
use Digia\GraphQL\Error\InvalidTypeException;
7
use Digia\GraphQL\Error\InvariantException;
8
use Digia\GraphQL\Language\Node\EnumValueNode;
9
use Digia\GraphQL\Language\Node\ListValueNode;
10
use Digia\GraphQL\Language\Node\NodeInterface;
11
use Digia\GraphQL\Language\Node\NullValueNode;
12
use Digia\GraphQL\Language\Node\ObjectFieldNode;
13
use Digia\GraphQL\Language\Node\ObjectValueNode;
14
use Digia\GraphQL\Language\Node\ValueNodeInterface;
15
use Digia\GraphQL\Language\Node\VariableNode;
16
use Digia\GraphQL\Type\Definition\EnumType;
17
use Digia\GraphQL\Type\Definition\InputObjectType;
18
use Digia\GraphQL\Type\Definition\InputTypeInterface;
19
use Digia\GraphQL\Type\Definition\ListType;
20
use Digia\GraphQL\Type\Definition\NonNullType;
21
use Digia\GraphQL\Type\Definition\ScalarType;
22
use Digia\GraphQL\Type\Definition\TypeInterface;
23
24
/**
25
 * Produces a PHP value given a GraphQL Value AST.
26
 *
27
 * A GraphQL type must be provided, which will be used to interpret different
28
 * GraphQL Value literals.
29
 *
30
 * Returns `undefined` when the value could not be validly resolved according to
31
 * the provided type.
32
 *
33
 * | GraphQL Value        | JSON Value    |
34
 * | -------------------- | ------------- |
35
 * | Input Object         | Object        |
36
 * | List                 | Array         |
37
 * | Boolean              | Boolean       |
38
 * | String               | String        |
39
 * | Int / Float          | Number        |
40
 * | Enum Value           | Mixed         |
41
 * | NullValue            | null          |
42
 */
43
class ValueNodeResolver
44
{
45
    /**
46
     * @param NodeInterface|ValueNodeInterface|null   $node
47
     * @param InputTypeInterface|NonNullType|ListType $type
48
     * @param array                                   $variables
49
     * @return mixed|null
50
     * @throws InvalidTypeException
51
     * @throws InvariantException
52
     * @throws ResolutionException
53
     */
54
    public function resolve(?NodeInterface $node, TypeInterface $type, array $variables = [])
55
    {
56
        if (null === $node) {
57
            throw new ResolutionException('Node is not defined.');
58
        }
59
60
        if ($type instanceof NonNullType) {
61
            return $this->resolveNonNullType($node, $type, $variables);
62
        }
63
64
        if ($node instanceof NullValueNode) {
65
            return null;
66
        }
67
68
        if ($node instanceof VariableNode) {
69
            $variableName = $node->getNameValue();
70
71
            if (!isset($variables[$variableName])) {
72
                throw new ResolutionException(\sprintf('Cannot resolve value for missing variable "%s".', $variableName));
73
            }
74
75
            // Note: we're not doing any checking that this variable is correct. We're
76
            // assuming that this query has been validated and the variable usage here
77
            // is of the correct type.
78
            return $variables[$variableName];
79
        }
80
81
        if ($type instanceof ListType) {
82
            return $this->resolveListType($node, $type, $variables);
83
        }
84
85
        if ($type instanceof InputObjectType) {
86
            return $this->resolveInputObjectType($node, $type, $variables);
87
        }
88
89
        if ($type instanceof EnumType) {
90
            return $this->resolveEnumType($node, $type);
91
        }
92
93
        if ($type instanceof ScalarType) {
94
            return $this->resolveScalarType($node, $type, $variables);
95
        }
96
97
        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

97
        throw new InvalidTypeException(\sprintf('Unknown type: %s', /** @scrutinizer ignore-type */ $type));
Loading history...
98
    }
99
100
    /**
101
     * @param NodeInterface|ValueNodeInterface $node
102
     * @param NonNullType                      $type
103
     * @param array                            $variables
104
     * @return mixed|null
105
     * @throws InvalidTypeException
106
     * @throws InvariantException
107
     * @throws ResolutionException
108
     */
109
    protected function resolveNonNullType(NodeInterface $node, NonNullType $type, array $variables = [])
110
    {
111
        if ($node instanceof NullValueNode) {
112
            throw new ResolutionException('Cannot resolve non-null values from null value node.');
113
        }
114
115
        return $this->resolve($node, $type->getOfType(), $variables);
116
    }
117
118
    /**
119
     * @param NodeInterface|ValueNodeInterface $node
120
     * @param ListType                         $type
121
     * @return array|null
122
     * @throws InvalidTypeException
123
     * @throws InvariantException
124
     * @throws ResolutionException
125
     */
126
    protected function resolveListType(NodeInterface $node, ListType $type, array $variables = [])
127
    {
128
        $itemType = $type->getOfType();
129
130
        if ($node instanceof ListValueNode) {
131
            $resolvedValues = [];
132
133
            foreach ($node->getValues() as $value) {
134
                if ($this->isMissingVariable($value, $variables)) {
135
                    if ($itemType instanceof NonNullType) {
136
                        // If an array contains a missing variable, it is either resolved to
137
                        // null or if the item type is non-null, it considered invalid.
138
                        throw new ResolutionException('Cannot resolve value for missing non-null list item.');
139
                    }
140
141
                    $resolvedValues[] = null;
142
                } else {
143
                    $resolvedValues[] = $this->resolve($value, $itemType, $variables);
144
                }
145
            }
146
147
            return $resolvedValues;
148
        }
149
150
        return [$this->resolve($node, $itemType, $variables)];
151
    }
152
153
    /**
154
     * @param NodeInterface|ValueNodeInterface $node
155
     * @param InputObjectType                  $type
156
     * @param array                            $variables
157
     * @return array|null
158
     * @throws InvalidTypeException
159
     * @throws InvariantException
160
     * @throws ResolutionException
161
     */
162
    protected function resolveInputObjectType(NodeInterface $node, InputObjectType $type, array $variables = [])
163
    {
164
        if (!$node instanceof ObjectValueNode) {
165
            throw new ResolutionException('Input object values can only be resolved form object value nodes.');
166
        }
167
168
        $resolvedValues = [];
169
170
        /** @var ObjectFieldNode[] $fieldNodes */
171
        $fieldNodes = keyMap($node->getFields(), function (ObjectFieldNode $value) {
172
            return $value->getNameValue();
173
        });
174
175
        foreach ($type->getFields() as $field) {
176
            $name      = $field->getName();
177
            $fieldNode = $fieldNodes[$name] ?? null;
178
179
            if (null === $fieldNode || $this->isMissingVariable($fieldNode->getValue(), $variables)) {
180
                if (null !== $field->getDefaultValue()) {
181
                    $resolvedValues[$name] = $field->getDefaultValue();
182
                } elseif ($field->getType() instanceof NonNullType) {
183
                    throw new ResolutionException('Cannot resolve input object value for missing non-null field.');
184
                }
185
                continue;
186
            }
187
188
            $fieldValue = $this->resolve($fieldNode->getValue(), $field->getType(), $variables);
189
190
            $resolvedValues[$name] = $fieldValue;
191
        }
192
193
        return $resolvedValues;
194
    }
195
196
    /**
197
     * @param NodeInterface|ValueNodeInterface $node
198
     * @param EnumType                         $type
199
     * @return mixed|null
200
     * @throws InvariantException
201
     * @throws ResolutionException
202
     */
203
    protected function resolveEnumType(NodeInterface $node, EnumType $type)
204
    {
205
        if (!$node instanceof EnumValueNode) {
206
            throw new ResolutionException('Enum values can only be resolved from enum value nodes.');
207
        }
208
209
        $name = $node->getValue();
210
211
        $enumValue = $type->getValue($name);
212
213
        if (null === $enumValue) {
214
            throw new ResolutionException(\sprintf('Cannot resolve enum value for missing value "%s".', $name));
215
        }
216
217
        return $enumValue->getValue();
218
    }
219
220
    /**
221
     * @param NodeInterface|ValueNodeInterface $node
222
     * @param ScalarType                       $type
223
     * @param array                            $variables
224
     * @return mixed|null
225
     * @throws ResolutionException
226
     */
227
    protected function resolveScalarType(NodeInterface $node, ScalarType $type, array $variables = [])
228
    {
229
        // Scalars fulfill parsing a literal value via parseLiteral().
230
        // Invalid values represent a failure to parse correctly, in which case
231
        // no value is returned.
232
        $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
233
234
        try {
235
            $result = $type->parseLiteral($node, $variables);
0 ignored issues
show
Unused Code introduced by
The call to Digia\GraphQL\Type\Defin...larType::parseLiteral() has too many arguments starting with $variables. ( Ignorable by Annotation )

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

235
            /** @scrutinizer ignore-call */ 
236
            $result = $type->parseLiteral($node, $variables);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
236
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
237
238
        }
239
240
        if (null === $result) {
241
            throw new ResolutionException(\sprintf('Failed to parse literal for scalar type "%s".', (string)$type));
242
        }
243
244
        return $result;
245
    }
246
247
    /**
248
     * @param ValueNodeInterface $node
249
     * @param array              $variables
250
     * @return bool
251
     */
252
    protected function isMissingVariable(ValueNodeInterface $node, array $variables): bool
253
    {
254
        return $node instanceof VariableNode && isset($variables[$node->getNameValue()]);
255
    }
256
}
257