Issues (167)

src/Util/ValueASTConverter.php (2 issues)

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\Language\Node\EnumValueNode;
8
use Digia\GraphQL\Language\Node\ListValueNode;
9
use Digia\GraphQL\Language\Node\NodeInterface;
10
use Digia\GraphQL\Language\Node\NullValueNode;
11
use Digia\GraphQL\Language\Node\ObjectFieldNode;
12
use Digia\GraphQL\Language\Node\ObjectValueNode;
13
use Digia\GraphQL\Language\Node\ValueNodeInterface;
14
use Digia\GraphQL\Language\Node\VariableNode;
15
use Digia\GraphQL\Type\Definition\EnumType;
16
use Digia\GraphQL\Type\Definition\InputObjectType;
17
use Digia\GraphQL\Type\Definition\ListType;
18
use Digia\GraphQL\Type\Definition\NonNullType;
19
use Digia\GraphQL\Type\Definition\ScalarType;
20
use Digia\GraphQL\Type\Definition\TypeInterface;
21
22
class ValueASTConverter
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
     * Throws a `ConversionException` when the value could not be validly converted 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
     * @param NodeInterface $node
44
     * @param TypeInterface $type
45
     * @param array         $variables
46
     * @return mixed|null
47
     * @throws InvalidTypeException
48
     * @throws InvariantException
49
     * @throws ConversionException
50
     */
51
    public static function convert(NodeInterface $node, TypeInterface $type, array $variables = [])
52
    {
53
        if ($type instanceof NonNullType) {
54
            return self::convertNonNullType($node, $type, $variables);
55
        }
56
57
        if ($node instanceof NullValueNode) {
58
            return null;
59
        }
60
61
        if ($node instanceof VariableNode) {
62
            return self::convertVariable($node, $type, $variables);
63
        }
64
65
        if ($type instanceof ListType) {
66
            return self::convertListType($node, $type, $variables);
67
        }
68
69
        if ($type instanceof InputObjectType) {
70
            return self::convertInputObjectType($node, $type, $variables);
71
        }
72
73
        if ($type instanceof EnumType) {
74
            return self::convertEnumType($node, $type);
75
        }
76
77
        if ($type instanceof ScalarType) {
78
            return self::convertScalarType($node, $type, $variables);
79
        }
80
81
        throw new InvalidTypeException(\sprintf('Unknown type: %s', (string)$type));
82
    }
83
84
    /**
85
     * @param NodeInterface|ValueNodeInterface $node
86
     * @param NonNullType                      $type
87
     * @param array                            $variables
88
     * @return mixed|null
89
     * @throws InvalidTypeException
90
     * @throws InvariantException
91
     * @throws ConversionException
92
     */
93
    protected static function convertNonNullType(NodeInterface $node, NonNullType $type, array $variables)
94
    {
95
        if ($node instanceof NullValueNode) {
96
            throw new ConversionException('Cannot convert non-null values from null value node.');
97
        }
98
99
        return self::convert($node, $type->getOfType(), $variables);
100
    }
101
102
    /**
103
     * @param VariableNode  $node
104
     * @param TypeInterface $type
105
     * @param array         $variables
106
     * @return mixed
107
     * @throws ConversionException
108
     */
109
    protected static function convertVariable(VariableNode $node, TypeInterface $type, array $variables)
110
    {
111
        $variableName = $node->getNameValue();
112
113
        if (!isset($variables[$variableName])) {
114
            throw new ConversionException(
115
                \sprintf('Cannot convert value for missing variable "%s".', $variableName)
116
            );
117
        }
118
119
        // Note: we're not doing any checking that this variable is correct. We're
120
        // assuming that this query has been validated and the variable usage here
121
        // is of the correct type.
122
        $variableValue = $variables[$variableName];
123
124
        if (null === $variableValue && $type instanceof NonNullType) {
125
            throw new ConversionException(
126
                \sprintf('Cannot convert invalid value "%s" for variable "%s".', $variableValue, $variableName)
127
            );
128
        }
129
130
        return $variableValue;
131
    }
132
133
    /**
134
     * @param NodeInterface|ValueNodeInterface $node
135
     * @param ListType                         $type
136
     * @param array                            $variables
137
     * @return array|null
138
     * @throws ConversionException
139
     * @throws InvalidTypeException
140
     * @throws InvariantException
141
     */
142
    protected static function convertListType(NodeInterface $node, ListType $type, array $variables): ?array
143
    {
144
        $itemType = $type->getOfType();
145
146
        if ($node instanceof ListValueNode) {
147
            $values = [];
148
149
            foreach ($node->getValues() as $value) {
150
                if (self::isMissingVariable($value, $variables)) {
151
                    if ($itemType instanceof NonNullType) {
152
                        // If an array contains a missing variable, it is either converted to
153
                        // null or if the item type is non-null, it considered invalid.
154
                        throw new ConversionException('Cannot convert value for missing non-null list item.');
155
                    }
156
157
                    $values[] = null;
158
                } else {
159
                    $values[] = self::convert($value, $itemType, $variables);
160
                }
161
            }
162
163
            return $values;
164
        }
165
166
        return [self::convert($node, $itemType, $variables)];
167
    }
168
169
    /**
170
     * @param NodeInterface|ValueNodeInterface $node
171
     * @param InputObjectType                  $type
172
     * @param array                            $variables
173
     * @return array|null
174
     * @throws InvalidTypeException
175
     * @throws InvariantException
176
     * @throws ConversionException
177
     */
178
    protected static function convertInputObjectType(
179
        NodeInterface $node,
180
        InputObjectType $type,
181
        array $variables
182
    ): ?array {
183
        if (!$node instanceof ObjectValueNode) {
184
            throw new ConversionException('Input object values can only be converted form object value nodes.');
185
        }
186
187
        $values = [];
188
189
        /** @var ObjectFieldNode[] $fieldNodes */
190
        $fieldNodes = keyMap($node->getFields(), function (ObjectFieldNode $value) {
191
            return $value->getNameValue();
192
        });
193
194
        foreach ($type->getFields() as $field) {
195
            $name      = $field->getName();
196
            $fieldNode = $fieldNodes[$name] ?? null;
197
198
            if (null === $fieldNode || self::isMissingVariable($fieldNode->getValue(), $variables)) {
199
                if (null !== $field->getDefaultValue()) {
200
                    $values[$name] = $field->getDefaultValue();
201
                } elseif ($field->getType() instanceof NonNullType) {
202
                    throw new ConversionException('Cannot convert input object value for missing non-null field.');
203
                }
204
                continue;
205
            }
206
207
            $fieldValue = self::convert($fieldNode->getValue(), $field->getType(), $variables);
0 ignored issues
show
It seems like $field->getType() can also be of type null; however, parameter $type of Digia\GraphQL\Util\ValueASTConverter::convert() does only seem to accept Digia\GraphQL\Type\Definition\TypeInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

207
            $fieldValue = self::convert($fieldNode->getValue(), /** @scrutinizer ignore-type */ $field->getType(), $variables);
Loading history...
208
209
            $values[$name] = $fieldValue;
210
        }
211
212
        return $values;
213
    }
214
215
    /**
216
     * @param NodeInterface|ValueNodeInterface $node
217
     * @param EnumType                         $type
218
     * @return mixed|null
219
     * @throws InvariantException
220
     * @throws ConversionException
221
     */
222
    protected static function convertEnumType(NodeInterface $node, EnumType $type)
223
    {
224
        if (!$node instanceof EnumValueNode) {
225
            throw new ConversionException('Enum values can only be converted from enum value nodes.');
226
        }
227
228
        $name = $node->getValue();
229
230
        $enumValue = $type->getValue($name);
0 ignored issues
show
It seems like $name can also be of type null; however, parameter $name of Digia\GraphQL\Type\Definition\EnumType::getValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

230
        $enumValue = $type->getValue(/** @scrutinizer ignore-type */ $name);
Loading history...
231
232
        if (null === $enumValue) {
233
            throw new ConversionException(\sprintf('Cannot convert enum value for missing value "%s".', $name));
234
        }
235
236
        return $enumValue->getValue();
237
    }
238
239
    /**
240
     * @param NodeInterface|ValueNodeInterface $node
241
     * @param ScalarType                       $type
242
     * @param array                            $variables
243
     * @return mixed|null
244
     * @throws ConversionException
245
     */
246
    protected static function convertScalarType(NodeInterface $node, ScalarType $type, array $variables)
247
    {
248
        // Scalars fulfill parsing a literal value via parseLiteral().
249
        // Invalid values represent a failure to parse correctly, in which case
250
        // no value is returned.
251
        try {
252
            $result = $type->parseLiteral($node, $variables);
253
        } catch (\Exception $e) {
254
            // This will be handled by the next if-statement.
255
        }
256
257
        if (!isset($result)) {
258
            throw new ConversionException(\sprintf('Failed to parse literal for scalar type "%s".', (string)$type));
259
        }
260
261
        return $result;
262
    }
263
264
    /**
265
     * @param ValueNodeInterface $node
266
     * @param array              $variables
267
     * @return bool
268
     */
269
    protected static function isMissingVariable(ValueNodeInterface $node, array $variables): bool
270
    {
271
        return $node instanceof VariableNode && !isset($variables[$node->getNameValue()]);
272
    }
273
}
274