Passed
Push — master ( 4528ae...3744db )
by Christoffer
03:19
created

ValueHelper::compareValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 2
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 function Digia\GraphQL\printNode;
18
use Digia\GraphQL\Type\Definition\EnumType;
19
use Digia\GraphQL\Type\Definition\InputObjectType;
20
use Digia\GraphQL\Type\Definition\InputTypeInterface;
21
use Digia\GraphQL\Type\Definition\ListType;
22
use Digia\GraphQL\Type\Definition\NonNullType;
23
use Digia\GraphQL\Type\Definition\ScalarType;
24
use Digia\GraphQL\Type\Definition\TypeInterface;
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
            return null;
101
        }
102
103
        if ($node instanceof VariableNode) {
104
            $variableName = $node->getNameValue();
105
106
            if (!isset($variables[$variableName])) {
107
                throw new ResolutionException(
108
                    \sprintf('Cannot resolve value for missing variable "%s".', $variableName)
109
                );
110
            }
111
112
            // Note: we're not doing any checking that this variable is correct. We're
113
            // assuming that this query has been validated and the variable usage here
114
            // is of the correct type.
115
            return $variables[$variableName];
116
        }
117
118
        if ($type instanceof ListType) {
119
            return $this->resolveListType($node, $type, $variables);
120
        }
121
122
        if ($type instanceof InputObjectType) {
123
            return $this->resolveInputObjectType($node, $type, $variables);
124
        }
125
126
        if ($type instanceof EnumType) {
127
            return $this->resolveEnumType($node, $type);
128
        }
129
130
        if ($type instanceof ScalarType) {
131
            return $this->resolveScalarType($node, $type, $variables);
132
        }
133
134
        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

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