Completed
Pull Request — master (#349)
by Christoffer
03:36 queued 38s
created

ValuesResolver::coerceValueForVariableNode()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 29
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 13
c 3
b 0
f 0
nc 4
nop 5
dl 0
loc 29
rs 9.5222
1
<?php
2
3
namespace Digia\GraphQL\Execution;
4
5
use Digia\GraphQL\Error\GraphQLException;
6
use Digia\GraphQL\Error\InvalidTypeException;
7
use Digia\GraphQL\Error\InvariantException;
8
use Digia\GraphQL\Language\Node\ArgumentNode;
9
use Digia\GraphQL\Language\Node\ArgumentsAwareInterface;
10
use Digia\GraphQL\Language\Node\NameAwareInterface;
11
use Digia\GraphQL\Language\Node\NodeInterface;
12
use Digia\GraphQL\Language\Node\NodeKindEnum;
13
use Digia\GraphQL\Language\Node\NullValueNode;
14
use Digia\GraphQL\Language\Node\VariableDefinitionNode;
15
use Digia\GraphQL\Language\Node\VariableNode;
16
use Digia\GraphQL\Schema\Schema;
17
use Digia\GraphQL\Type\Coercer\CoercingException;
18
use Digia\GraphQL\Type\Definition\Directive;
19
use Digia\GraphQL\Type\Definition\EnumType;
20
use Digia\GraphQL\Type\Definition\EnumValue;
21
use Digia\GraphQL\Type\Definition\Field;
22
use Digia\GraphQL\Type\Definition\InputObjectType;
23
use Digia\GraphQL\Type\Definition\ListType;
24
use Digia\GraphQL\Type\Definition\NonNullType;
25
use Digia\GraphQL\Type\Definition\ScalarType;
26
use Digia\GraphQL\Type\Definition\TypeInterface;
27
use Digia\GraphQL\Type\Definition\WrappingTypeInterface;
28
use Digia\GraphQL\Util\ConversionException;
29
use Digia\GraphQL\Util\TypeASTConverter;
30
use Digia\GraphQL\Util\ValueASTConverter;
31
use function Digia\GraphQL\printNode;
32
use function Digia\GraphQL\Test\jsonEncode;
33
use function Digia\GraphQL\Util\find;
34
use function Digia\GraphQL\Util\invariant;
35
use function Digia\GraphQL\Util\keyMap;
36
use function Digia\GraphQL\Util\suggestionList;
37
38
/**
39
 * TODO: Make this class static
40
 */
41
class ValuesResolver
42
{
43
    /**
44
     * Prepares an object map of argument values given a list of argument
45
     * definitions and list of argument AST nodes.
46
     *
47
     * @see https://facebook.github.io/graphql/October2016/#CoerceArgumentValues()
48
     *
49
     * @param Field|Directive         $definition
50
     * @param ArgumentsAwareInterface $node
51
     * @param array                   $variableValues
52
     * @return array
53
     * @throws ExecutionException
54
     * @throws InvalidTypeException
55
     * @throws InvariantException
56
     */
57
    public function coerceArgumentValues($definition, ArgumentsAwareInterface $node, array $variableValues = []): array
58
    {
59
        $coercedValues       = [];
60
        $argumentDefinitions = $definition->getArguments();
61
        $argumentNodes       = $node->getArguments();
62
63
        if (empty($argumentDefinitions)) {
64
            return $coercedValues;
65
        }
66
67
        /** @var ArgumentNode[] $argumentNodeMap */
68
        $argumentNodeMap = keyMap($argumentNodes, function (ArgumentNode $value) {
69
            return $value->getNameValue();
70
        });
71
72
        foreach ($argumentDefinitions as $argumentDefinition) {
73
            $argumentName  = $argumentDefinition->getName();
74
            $argumentType  = $argumentDefinition->getType();
75
            $argumentNode  = $argumentNodeMap[$argumentName] ?? null;
76
            $defaultValue  = $argumentDefinition->getDefaultValue();
77
            $argumentValue = null !== $argumentNode ? $argumentNode->getValue() : null;
78
79
            if (null !== $argumentNode && $argumentValue instanceof VariableNode) {
80
                $variableName = $argumentValue->getNameValue();
81
                $hasValue     = !empty($variableValues) && \array_key_exists($variableName, $variableValues);
82
                $isNull       = $hasValue && null === $variableValues[$variableName];
83
            } else {
84
                $hasValue = null !== $argumentNode;
85
                $isNull   = $hasValue && $argumentValue instanceof NullValueNode;
86
            }
87
88
            if (!$hasValue && null !== $defaultValue) {
89
                // If no argument was provided where the definition has a default value,
90
                // use the default value.
91
                $coercedValues[$argumentName] = $defaultValue;
92
            } elseif ((!$hasValue || $isNull) && $argumentType instanceof NonNullType) {
93
                // If no argument or a null value was provided to an argument with a
94
                // non-null type (required), produce a field error.
95
                if ($isNull) {
96
                    throw new ExecutionException(
97
                        \sprintf(
98
                            'Argument "%s" of non-null type "%s" must not be null.',
99
                            $argumentName,
100
                            $argumentType
101
                        ),
102
                        [$argumentValue]
103
                    );
104
                } elseif (null !== $argumentNode && $argumentValue instanceof VariableNode) {
105
                    $variableName = $argumentValue->getNameValue();
106
                    throw new ExecutionException(
107
                        \sprintf(
108
                            'Argument "%s" of required type "%s" was provided the variable "$%s" '
109
                            . 'which was not provided a runtime value.',
110
                            $argumentName,
111
                            $argumentType,
112
                            $variableName
113
                        ),
114
                        [$argumentValue]
115
                    );
116
                } else {
117
                    throw new ExecutionException(
118
                        \sprintf(
119
                            'Argument "%s" of required type "%s" was not provided.',
120
                            $argumentName,
121
                            $argumentType
122
                        ),
123
                        [$node]
124
                    );
125
                }
126
            } elseif ($hasValue) {
127
                if ($argumentValue instanceof NullValueNode) {
128
                    // If the explicit value `null` was provided, an entry in the coerced
129
                    // values must exist as the value `null`.
130
                    $coercedValues[$argumentName] = null;
131
                } elseif ($argumentValue instanceof VariableNode) {
132
                    $variableName = $argumentValue->getNameValue();
133
                    invariant(!empty($variableValues), 'Must exist for hasValue to be true.');
134
                    // Note: This does no further checking that this variable is correct.
135
                    // This assumes that this query has been validated and the variable
136
                    // usage here is of the correct type.
137
                    $coercedValues[$argumentName] = $variableValues[$variableName];
138
                } else {
139
                    $valueNode = $argumentNode->getValue();
0 ignored issues
show
Bug introduced by
The method getValue() does not exist on null. ( Ignorable by Annotation )

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

139
                    /** @scrutinizer ignore-call */ 
140
                    $valueNode = $argumentNode->getValue();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
140
                    try {
141
                        // Value nodes that cannot be resolved should be treated as invalid values
142
                        // because there is no undefined value in PHP so that we throw an exception
143
                        $coercedValue = ValueASTConverter::convert($valueNode, $argumentType, $variableValues);
0 ignored issues
show
Bug introduced by
It seems like $argumentType 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

143
                        $coercedValue = ValueASTConverter::convert($valueNode, /** @scrutinizer ignore-type */ $argumentType, $variableValues);
Loading history...
144
                    } catch (\Exception $ex) {
145
                        // Note: ValuesOfCorrectType validation should catch this before
146
                        // execution. This is a runtime check to ensure execution does not
147
                        // continue with an invalid argument value.
148
                        throw new ExecutionException(
149
                            \sprintf(
150
                                'Argument "%s" has invalid value %s.',
151
                                $argumentName,
152
                                (string)$argumentValue
153
                            ),
154
                            [$argumentValue],
155
                            null,
156
                            null,
157
                            null,
158
                            null,
159
                            $ex
160
                        );
161
                    }
162
                    $coercedValues[$argumentName] = $coercedValue;
163
                }
164
            }
165
        }
166
167
        return $coercedValues;
168
    }
169
170
    /**
171
     * Prepares an object map of argument values given a directive definition
172
     * and a AST node which may contain directives. Optionally also accepts a map
173
     * of variable values.
174
     *
175
     * If the directive does not exist on the node, returns null.
176
     *
177
     * @param Directive $directive
178
     * @param mixed     $node
179
     * @param array     $variableValues
180
     * @return array|null
181
     * @throws ExecutionException
182
     * @throws InvalidTypeException
183
     * @throws InvariantException
184
     */
185
    public function coerceDirectiveValues(
186
        Directive $directive,
187
        $node,
188
        array $variableValues = []
189
    ): ?array {
190
        $directiveNode = $node->hasDirectives()
191
            ? find($node->getDirectives(), function (NameAwareInterface $value) use ($directive) {
192
                return $value->getNameValue() === $directive->getName();
193
            }) : null;
194
195
        if (null !== $directiveNode) {
196
            return $this->coerceArgumentValues($directive, $directiveNode, $variableValues);
197
        }
198
199
        return null;
200
    }
201
202
    /**
203
     * Prepares an object map of variableValues of the correct type based on the
204
     * provided variable definitions and arbitrary input. If the input cannot be
205
     * parsed to match the variable definitions, a GraphQLError will be thrown.
206
     *
207
     * @param Schema                         $schema
208
     * @param array|VariableDefinitionNode[] $variableDefinitionNodes
209
     * @param array                          $inputs
210
     * @return CoercedValue
211
     * @throws GraphQLException
212
     * @throws InvariantException
213
     * @throws ConversionException
214
     */
215
    public function coerceVariableValues(
216
        Schema $schema,
217
        array $variableDefinitionNodes,
218
        array $inputs
219
    ): CoercedValue {
220
        $coercedValues = [];
221
        $errors        = [];
222
223
        foreach ($variableDefinitionNodes as $variableDefinitionNode) {
224
            $variableName     = $variableDefinitionNode->getVariable()->getNameValue();
225
            $variableType     = TypeASTConverter::convert($schema, $variableDefinitionNode->getType());
226
            $variableTypeName = (string)$variableType;
227
228
            if ($variableTypeName === '') {
229
                $variableTypeName = (string)$variableDefinitionNode;
230
            }
231
232
            if (!$this->isInputType($variableType)) {
233
                // Must use input types for variables. This should be caught during
234
                // validation, however is checked again here for safety.
235
                $errors[] = $this->buildCoerceException(
236
                    \sprintf(
237
                        'Variable "$%s" expected value of type "%s" which cannot be used as an input type',
238
                        $variableName,
239
                        $variableTypeName
240
                    ),
241
                    $variableDefinitionNode,
242
                    null
243
                );
244
            } else {
245
                $hasValue = \array_key_exists($variableName, $inputs);
246
                $value    = $hasValue ? $inputs[$variableName] : null;
247
                if (!$hasValue && $variableDefinitionNode->hasDefaultValue()) {
248
                    // If no value was provided to a variable with a default value,
249
                    // use the default value.
250
                    $coercedValues[$variableName] = ValueASTConverter::convert(
251
                        $variableDefinitionNode->getDefaultValue(),
252
                        $variableType
253
                    );
254
                } elseif ((!$hasValue || null === $value) && $variableType instanceof NonNullType) {
255
                    // If no value or a nullish value was provided to a variable with a
256
                    // non-null type (required), produce an error.
257
                    $errors[] = $this->buildCoerceException(
258
                        \sprintf(
259
                            $value
260
                                ? 'Variable "$%s" of non-null type "%s" must not be null'
261
                                : 'Variable "$%s" of required type "%s" was not provided',
262
                            $variableName,
263
                            $variableTypeName
264
                        ),
265
                        $variableDefinitionNode,
266
                        null
267
                    );
268
                } elseif ($hasValue) {
269
                    if (null === $value) {
270
                        // If the explicit value `null` was provided, an entry in the coerced
271
                        // values must exist as the value `null`.
272
                        $coercedValues[$variableName] = null;
273
                    } else {
274
                        // Otherwise, a non-null value was provided, coerce it to the expected
275
                        // type or report an error if coercion fails.
276
                        $coercedValue = $this->coerceValue($value, $variableType, $variableDefinitionNode);
277
                        if ($coercedValue->hasErrors()) {
278
                            $message = \sprintf(
279
                                'Variable "$%s" got invalid value %s',
280
                                $variableName,
281
                                jsonEncode($value)
282
                            );
283
                            foreach ($coercedValue->getErrors() as $error) {
284
                                $errors[] = $this->buildCoerceException(
285
                                    $message,
286
                                    $variableDefinitionNode,
287
                                    null,
288
                                    $error->getMessage(),
289
                                    $error
290
                                );
291
                            }
292
                        } else {
293
                            $coercedValues[$variableName] = $coercedValue->getValue();
294
                        }
295
                    }
296
                }
297
            }
298
        }
299
300
        return new CoercedValue($coercedValues, $errors);
301
    }
302
303
    /**
304
     * @param TypeInterface|null $type
305
     * @return bool
306
     */
307
    protected function isInputType(?TypeInterface $type)
308
    {
309
        return ($type instanceof ScalarType) ||
310
            ($type instanceof EnumType) ||
311
            ($type instanceof InputObjectType) ||
312
            (($type instanceof WrappingTypeInterface) && $this->isInputType($type->getOfType()));
313
    }
314
315
    /**
316
     * Returns either a value which is valid for the provided type or a list of
317
     * encountered coercion errors.
318
     *
319
     * @param mixed|array   $value
320
     * @param mixed         $type
321
     * @param NodeInterface $blameNode
322
     * @param Path|null     $path
323
     * @return CoercedValue
324
     * @throws GraphQLException
325
     * @throws InvariantException
326
     */
327
    private function coerceValue($value, $type, $blameNode, ?Path $path = null): CoercedValue
328
    {
329
        if ($type instanceof NonNullType) {
330
            return $this->coerceValueForNonNullType($value, $type, $blameNode, $path);
331
        }
332
333
        if (null === $value) {
334
            return new CoercedValue(null);
335
        }
336
337
        if ($type instanceof ScalarType) {
338
            return $this->coerceValueForScalarType($value, $type, $blameNode, $path);
339
        }
340
341
        if ($type instanceof EnumType) {
342
            return $this->coerceValueForEnumType($value, $type, $blameNode, $path);
343
        }
344
345
        if ($type instanceof ListType) {
346
            return $this->coerceValueForListType($value, $type, $blameNode, $path);
347
        }
348
349
        if ($type instanceof InputObjectType) {
350
            return $this->coerceValueForInputObjectType($value, $type, $blameNode, $path);
351
        }
352
353
        throw new GraphQLException('Unexpected type.');
354
    }
355
356
    /**
357
     * @param mixed         $value
358
     * @param NonNullType   $type
359
     * @param NodeInterface $blameNode
360
     * @param Path|null     $path
361
     * @return CoercedValue
362
     * @throws GraphQLException
363
     * @throws InvariantException
364
     */
365
    protected function coerceValueForNonNullType(
366
        $value,
367
        NonNullType $type,
368
        NodeInterface $blameNode,
369
        ?Path $path
370
    ): CoercedValue {
371
        if (null === $value) {
372
            return new CoercedValue(null, [
373
                $this->buildCoerceException(
374
                    sprintf('Expected non-nullable type %s not to be null', (string)$type),
375
                    $blameNode,
376
                    $path
377
                )
378
            ]);
379
        }
380
        return $this->coerceValue($value, $type->getOfType(), $blameNode, $path);
381
    }
382
383
    /**
384
     * Scalars determine if a value is valid via parseValue(), which can
385
     * throw to indicate failure. If it throws, maintain a reference to
386
     * the original error.
387
     *
388
     * @param mixed         $value
389
     * @param ScalarType    $type
390
     * @param NodeInterface $blameNode
391
     * @param Path|null     $path
392
     * @return CoercedValue
393
     */
394
    protected function coerceValueForScalarType(
395
        $value,
396
        ScalarType $type,
397
        NodeInterface $blameNode,
398
        ?Path $path
399
    ): CoercedValue {
400
        try {
401
            $parseResult = $type->parseValue($value);
402
            if (null === $parseResult) {
403
                return new CoercedValue(null, [
404
                    new GraphQLException(sprintf('Expected type %s', (string)$type))
405
                ]);
406
            }
407
            return new CoercedValue($parseResult);
408
        } catch (InvalidTypeException|CoercingException $ex) {
409
            return new CoercedValue(null, [
410
                $this->buildCoerceException(
411
                    sprintf('Expected type %s', (string)$type),
412
                    $blameNode,
413
                    $path,
414
                    $ex->getMessage(),
415
                    $ex
416
                )
417
            ]);
418
        }
419
    }
420
421
    /**
422
     * @param mixed         $value
423
     * @param EnumType      $type
424
     * @param NodeInterface $blameNode
425
     * @param Path|null     $path
426
     * @return CoercedValue
427
     * @throws InvariantException
428
     */
429
    protected function coerceValueForEnumType(
430
        $value,
431
        EnumType $type,
432
        NodeInterface $blameNode,
433
        ?Path $path
434
    ): CoercedValue {
435
        if (\is_string($value) && null !== ($enumValue = $type->getValue($value))) {
436
            return new CoercedValue($enumValue);
437
        }
438
439
        $suggestions = suggestionList((string)$value, \array_map(function (EnumValue $enumValue) {
440
            return $enumValue->getName();
441
        }, $type->getValues()));
442
443
        $didYouMean = (!empty($suggestions))
444
            ? 'did you mean' . \implode(',', $suggestions)
445
            : null;
446
447
        return new CoercedValue(null, [
448
            $this->buildCoerceException(\sprintf('Expected type %s', $type->getName()), $blameNode, $path, $didYouMean)
449
        ]);
450
    }
451
452
    /**
453
     * @param mixed           $value
454
     * @param InputObjectType $type
455
     * @param NodeInterface   $blameNode
456
     * @param Path|null       $path
457
     * @return CoercedValue
458
     * @throws GraphQLException
459
     * @throws InvariantException
460
     */
461
    protected function coerceValueForInputObjectType(
462
        $value,
463
        InputObjectType $type,
464
        NodeInterface $blameNode,
465
        ?Path $path
466
    ): CoercedValue {
467
        $errors        = [];
468
        $coercedValues = [];
469
        $fields        = $type->getFields();
470
471
        // Ensure every defined field is valid.
472
        foreach ($fields as $field) {
473
            $fieldType = $field->getType();
474
475
            if (!isset($value[$field->getName()])) {
476
                if (!empty($field->getDefaultValue())) {
477
                    $coercedValue[$field->getName()] = $field->getDefaultValue();
478
                } elseif ($fieldType instanceof NonNullType) {
479
                    $errors[] = new GraphQLException(
480
                        \sprintf(
481
                            "Field %s of required type %s! was not provided.",
482
                            $this->printPath(new Path($path, $field->getName())),
483
                            (string)$fieldType->getOfType()
484
                        )
485
                    );
486
                }
487
            } else {
488
                $fieldValue   = $value[$field->getName()];
489
                $coercedValue = $this->coerceValue(
490
                    $fieldValue,
491
                    $fieldType,
492
                    $blameNode,
493
                    new Path($path, $field->getName())
494
                );
495
496
                if ($coercedValue->hasErrors()) {
497
                    $errors = \array_merge($errors, $coercedValue->getErrors());
498
                } elseif (empty($errors)) {
499
                    $coercedValues[$field->getName()] = $coercedValue->getValue();
500
                }
501
            }
502
        }
503
504
        // Ensure every provided field is defined.
505
        foreach ($value as $fieldName => $fieldValue) {
506
            if (!isset($fields[$fieldName])) {
507
                $suggestions = suggestionList($fieldName, \array_keys($fields));
508
                $didYouMean  = (!empty($suggestions))
509
                    ? 'did you mean' . \implode(',', $suggestions)
510
                    : null;
511
512
                $errors[] = $this->buildCoerceException(
513
                    \sprintf('Field "%s" is not defined by type %s', $fieldName, $type->getName()),
514
                    $blameNode,
515
                    $path,
516
                    $didYouMean
517
                );
518
            }
519
        }
520
521
        return new CoercedValue($coercedValues, $errors);
522
    }
523
524
    /**
525
     * @param mixed         $value
526
     * @param ListType      $type
527
     * @param NodeInterface $blameNode
528
     * @param Path|null     $path
529
     * @return CoercedValue
530
     * @throws GraphQLException
531
     * @throws InvariantException
532
     */
533
    protected function coerceValueForListType(
534
        $value,
535
        ListType $type,
536
        NodeInterface $blameNode,
537
        ?Path $path
538
    ): CoercedValue {
539
        $itemType = $type->getOfType();
540
541
        if (\is_array($value) || $value instanceof \Traversable) {
542
            $errors        = [];
543
            $coercedValues = [];
544
            foreach ($value as $index => $itemValue) {
545
                $coercedValue = $this->coerceValue($itemValue, $itemType, $blameNode, new Path($path, $index));
546
547
                if ($coercedValue->hasErrors()) {
548
                    $errors = \array_merge($errors, $coercedValue->getErrors());
549
                } else {
550
                    $coercedValues[] = $coercedValue->getValue();
551
                }
552
            }
553
554
            return new CoercedValue($coercedValues, $errors);
555
        }
556
557
        // Lists accept a non-list value as a list of one.
558
        $coercedValue = $this->coerceValue($value, $itemType, $blameNode);
559
560
        return new CoercedValue([$coercedValue->getValue()], $coercedValue->getErrors());
561
    }
562
563
    /**
564
     * @param string                $message
565
     * @param NodeInterface         $blameNode
566
     * @param Path|null             $path
567
     * @param null|string           $subMessage
568
     * @param GraphQLException|null $originalException
569
     * @return GraphQLException
570
     */
571
    protected function buildCoerceException(
572
        string $message,
573
        NodeInterface $blameNode,
574
        ?Path $path,
575
        ?string $subMessage = null,
576
        ?GraphQLException $originalException = null
577
    ) {
578
        $stringPath = $this->printPath($path);
579
580
        return new CoercingException(
581
            $message .
582
            (($stringPath !== '') ? ' at ' . $stringPath : $stringPath) .
583
            (($subMessage !== null) ? '; ' . $subMessage : '.'),
584
            [$blameNode],
585
            null,
586
            null,
587
            // TODO: Change this to null
588
            [],
589
            null,
590
            $originalException
591
        );
592
    }
593
594
    /**
595
     * @param Path|null $path
596
     * @return string
597
     */
598
    protected function printPath(?Path $path)
599
    {
600
        $stringPath  = '';
601
        $currentPath = $path;
602
603
        while ($currentPath !== null) {
604
            $stringPath = \is_string($currentPath->getKey())
605
                ? '.' . $currentPath->getKey() . $stringPath
606
                : '[' . (string)$currentPath->getKey() . ']' . $stringPath;
607
608
            $currentPath = $currentPath->getPrevious();
609
        }
610
611
        return !empty($stringPath) ? 'value' . $stringPath : '';
612
    }
613
614
    /**
615
     * @param VariableNode  $variableNode
616
     * @param TypeInterface $argumentType
617
     * @param string        $argumentName
618
     * @param array         $variableValues
619
     * @param mixed         $defaultValue
620
     * @return mixed
621
     * @throws ExecutionException
622
     */
623
    protected function coerceValueForVariableNode(
624
        VariableNode $variableNode,
625
        TypeInterface $argumentType,
626
        string $argumentName,
627
        array $variableValues,
628
        $defaultValue
629
    ) {
630
        $variableName = $variableNode->getNameValue();
631
632
        if (!empty($variableValues) && isset($variableValues[$variableName])) {
633
            // Note: this does not check that this variable value is correct.
634
            // This assumes that this query has been validated and the variable
635
            // usage here is of the correct type.
636
            return $variableValues[$variableName];
637
        }
638
639
        if (null !== $defaultValue) {
640
            return $defaultValue;
641
        }
642
643
        if ($argumentType instanceof NonNullType) {
644
            throw new ExecutionException(
645
                \sprintf(
646
                    'Argument "%s" of required type "%s" was provided the variable "$%s" which was not provided a runtime value.',
647
                    $argumentName,
648
                    $argumentType,
649
                    $variableName
650
                ),
651
                [$variableNode]
652
            );
653
        }
654
    }
655
}
656