Passed
Pull Request — master (#158)
by Christoffer
04:31 queued 01:20
created

ValueHelper::resolveScalarType()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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