Passed
Pull Request — master (#209)
by Christoffer
02:27
created

ValueASTConverter::convertEnumType()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 2
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
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\ConversionException;
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\ListType;
19
use Digia\GraphQL\Type\Definition\NonNullType;
20
use Digia\GraphQL\Type\Definition\ScalarType;
21
use Digia\GraphQL\Type\Definition\TypeInterface;
22
23
class ValueASTConverter
24
{
25
    /**
26
     * Produces a PHP value given a GraphQL Value AST.
27
     *
28
     * A GraphQL type must be provided, which will be used to interpret different
29
     * GraphQL Value literals.
30
     *
31
     * Throws a `ConversionException` when the value could not be validly converted according to
32
     * the provided type.
33
     *
34
     * | GraphQL Value        | JSON Value    |
35
     * | -------------------- | ------------- |
36
     * | Input Object         | Object        |
37
     * | List                 | Array         |
38
     * | Boolean              | Boolean       |
39
     * | String               | String        |
40
     * | Int / Float          | Number        |
41
     * | Enum Value           | Mixed         |
42
     * | NullValue            | null          |
43
     *
44
     * @param NodeInterface|ValueNodeInterface|null $node
45
     * @param TypeInterface                         $type
46
     * @param array                                 $variables
47
     * @return mixed|null
48
     * @throws InvalidTypeException
49
     * @throws InvariantException
50
     * @throws ConversionException
51
     */
52
    public function convert(?NodeInterface $node, TypeInterface $type, array $variables = [])
53
    {
54
        if (null === $node) {
55
            throw new ConversionException('Node is not defined.');
56
        }
57
58
        if ($type instanceof NonNullType) {
59
            return $this->convertNonNullType($node, $type, $variables);
60
        }
61
62
        if ($node instanceof NullValueNode) {
63
            return null;
64
        }
65
66
        if ($node instanceof VariableNode) {
67
            return $this->convertVariable($node, $variables);
68
        }
69
70
        if ($type instanceof ListType) {
71
            return $this->convertListType($node, $type, $variables);
72
        }
73
74
        if ($type instanceof InputObjectType) {
75
            return $this->convertInputObjectType($node, $type, $variables);
76
        }
77
78
        if ($type instanceof EnumType) {
79
            return $this->convertEnumType($node, $type);
80
        }
81
82
        if ($type instanceof ScalarType) {
83
            return $this->convertScalarType($node, $type, $variables);
84
        }
85
86
        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

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