Completed
Push — master ( 6d9275...e17f57 )
by Vladimir
26:45 queued 23:06
created

InputObjectCircularRefs   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 86
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 7
eloc 34
dl 0
loc 86
ccs 32
cts 32
cp 1
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
B validate() 0 51 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Type\Validation;
6
7
use GraphQL\Language\AST\InputValueDefinitionNode;
8
use GraphQL\Type\Definition\InputObjectField;
9
use GraphQL\Type\Definition\InputObjectType;
10
use GraphQL\Type\Definition\NonNull;
11
use GraphQL\Type\SchemaValidationContext;
12
use function array_map;
13
use function array_pop;
14
use function array_slice;
15
use function count;
16
use function implode;
17
18
class InputObjectCircularRefs
19
{
20
    /** @var SchemaValidationContext */
21
    private $schemaValidationContext;
22
23
    /**
24
     * Tracks already visited types to maintain O(N) and to ensure that cycles
25
     * are not redundantly reported.
26
     *
27
     * @var InputObjectType[]
28
     */
29
    private $visitedTypes = [];
30
31
    /** @var InputObjectField[] */
32
    private $fieldPath = [];
33
34
    /**
35
     * Position in the type path.
36
     *
37
     * [string $typeName => int $index]
38
     *
39
     * @var int[]
40
     */
41
    private $fieldPathIndexByTypeName = [];
42
43 87
    public function __construct(SchemaValidationContext $schemaValidationContext)
44
    {
45 87
        $this->schemaValidationContext = $schemaValidationContext;
46 87
    }
47
48
    /**
49
     * This does a straight-forward DFS to find cycles.
50
     * It does not terminate when a cycle was found but continues to explore
51
     * the graph to find all possible cycles.
52
     */
53 24
    public function validate(InputObjectType $inputObj) : void
54
    {
55 24
        if (isset($this->visitedTypes[$inputObj->name])) {
56 3
            return;
57
        }
58
59 24
        $this->visitedTypes[$inputObj->name]             = true;
60 24
        $this->fieldPathIndexByTypeName[$inputObj->name] = count($this->fieldPath);
61
62 24
        $fieldMap = $inputObj->getFields();
63 24
        foreach ($fieldMap as $fieldName => $field) {
64 23
            $type = $field->getType();
65
66 23
            if ($type instanceof NonNull) {
67 6
                $fieldType = $type->getWrappedType();
68
69
                // If the type of the field is anything else then a non-nullable input object,
70
                // there is no chance of an unbreakable cycle
71 6
                if ($fieldType instanceof InputObjectType) {
72 4
                    $this->fieldPath[] = $field;
73
74 4
                    if (! isset($this->fieldPathIndexByTypeName[$fieldType->name])) {
75 3
                        $this->validate($fieldType);
76
                    } else {
77 3
                        $cycleIndex = $this->fieldPathIndexByTypeName[$fieldType->name];
78 3
                        $cyclePath  = array_slice($this->fieldPath, $cycleIndex);
79 3
                        $fieldNames = array_map(
80
                            static function (InputObjectField $field) : string {
81 3
                                return $field->name;
82 3
                            },
83 3
                            $cyclePath
84
                        );
85
86 3
                        $this->schemaValidationContext->reportError(
87 3
                            'Cannot reference Input Object "' . $fieldType->name . '" within itself '
88 3
                            . 'through a series of non-null fields: "' . implode('.', $fieldNames) . '".',
89 3
                            array_map(
90
                                static function (InputObjectField $field) : ?InputValueDefinitionNode {
91 3
                                    return $field->astNode;
92 3
                                },
93 3
                                $cyclePath
94
                            )
95
                        );
96
                    }
97
                }
98
            }
99
100 23
            array_pop($this->fieldPath);
101
        }
102
103 24
        unset($this->fieldPathIndexByTypeName[$inputObj->name]);
104 24
    }
105
}
106