Failed Conditions
Push — master ( 48b44f...392b56 )
by Vladimir
04:42
created

NoFragmentCycles::detectCycleRecursive()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 50
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 8.0023

Importance

Changes 0
Metric Value
eloc 31
dl 0
loc 50
ccs 29
cts 30
cp 0.9667
rs 8.1795
c 0
b 0
f 0
cc 8
nc 12
nop 2
crap 8.0023
1
<?php
2
namespace GraphQL\Validator\Rules;
3
4
use GraphQL\Error\Error;
5
use GraphQL\Language\AST\FragmentDefinitionNode;
6
use GraphQL\Language\AST\NodeKind;
7
use GraphQL\Language\Visitor;
8
use GraphQL\Utils\Utils;
9
use GraphQL\Validator\ValidationContext;
10
11
class NoFragmentCycles extends AbstractValidationRule
12
{
13 10
    static function cycleErrorMessage($fragName, array $spreadNames = [])
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...
14
    {
15 10
        $via = !empty($spreadNames) ? ' via ' . implode(', ', $spreadNames) : '';
16 10
        return "Cannot spread fragment \"$fragName\" within itself$via.";
17
    }
18
19
    public $visitedFrags;
20
21
    public $spreadPath;
22
23
    public $spreadPathIndexByName;
24
25 116
    public function getVisitor(ValidationContext $context)
26
    {
27
        // Tracks already visited fragments to maintain O(N) and to ensure that cycles
28
        // are not redundantly reported.
29 116
        $this->visitedFrags = [];
30
31
        // Array of AST nodes used to produce meaningful errors
32 116
        $this->spreadPath = [];
33
34
        // Position in the spread path
35 116
        $this->spreadPathIndexByName = [];
36
37
        return [
38
            NodeKind::OPERATION_DEFINITION => function () {
39 101
                return Visitor::skipNode();
40 116
            },
41
            NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) use ($context) {
42 23
                if (!isset($this->visitedFrags[$node->name->value])) {
43 23
                    $this->detectCycleRecursive($node, $context);
44
                }
45 23
                return Visitor::skipNode();
46 116
            }
47
        ];
48
    }
49
50 23
    private function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context)
51
    {
52 23
        $fragmentName = $fragment->name->value;
53 23
        $this->visitedFrags[$fragmentName] = true;
54
55 23
        $spreadNodes = $context->getFragmentSpreads($fragment);
56
57 23
        if (empty($spreadNodes)) {
58 12
            return;
59
        }
60
61 17
        $this->spreadPathIndexByName[$fragmentName] = count($this->spreadPath);
62
63 17
        for ($i = 0; $i < count($spreadNodes); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
64 17
            $spreadNode = $spreadNodes[$i];
65 17
            $spreadName = $spreadNode->name->value;
66 17
            $cycleIndex = isset($this->spreadPathIndexByName[$spreadName]) ? $this->spreadPathIndexByName[$spreadName] : null;
67
68 17
            if ($cycleIndex === null) {
69 14
                $this->spreadPath[] = $spreadNode;
70 14
                if (empty($this->visitedFrags[$spreadName])) {
71 13
                    $spreadFragment = $context->getFragment($spreadName);
72 13
                    if ($spreadFragment) {
73 12
                        $this->detectCycleRecursive($spreadFragment, $context);
74
                    }
75
                }
76 14
                array_pop($this->spreadPath);
77
            } else {
78 10
                $cyclePath = array_slice($this->spreadPath, $cycleIndex);
79 10
                $nodes = $cyclePath;
80
81 10
                if (is_array($spreadNode)) {
82
                    $nodes = array_merge($nodes, $spreadNode);
83
                } else {
84 10
                    $nodes[] = $spreadNode;
85
                }
86
87 10
                $context->reportError(new Error(
88 10
                    self::cycleErrorMessage(
89 10
                        $spreadName,
90
                        Utils::map($cyclePath, function ($s) {
91 7
                            return $s->name->value;
92 10
                        })
93
                    ),
94 10
                    $nodes
95
                ));
96
            }
97
        }
98
99 17
        $this->spreadPathIndexByName[$fragmentName] = null;
100 17
    }
101
}
102