Failed Conditions
Push — master ( e31947...cc39b3 )
by Šimon
11s
created

NoFragmentCycles::detectCycleRecursive()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 53
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 7.0014

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 53
ccs 31
cts 32
cp 0.9688
rs 8.4586
c 0
b 0
f 0
cc 7
nc 7
nop 2
crap 7.0014

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Validator\Rules;
6
7
use GraphQL\Error\Error;
8
use GraphQL\Language\AST\FragmentDefinitionNode;
9
use GraphQL\Language\AST\FragmentSpreadNode;
10
use GraphQL\Language\AST\NodeKind;
11
use GraphQL\Language\Visitor;
12
use GraphQL\Utils\Utils;
13
use GraphQL\Validator\ValidationContext;
14
use function array_merge;
15
use function array_pop;
16
use function array_slice;
17
use function count;
18
use function implode;
19
use function is_array;
20
use function sprintf;
21
22
class NoFragmentCycles extends ValidationRule
23
{
24
    /** @var bool[] */
25
    public $visitedFrags;
26
27
    /** @var FragmentSpreadNode[] */
28
    public $spreadPath;
29
30
    /** @var (int|null)[] */
31
    public $spreadPathIndexByName;
32
33 117
    public function getVisitor(ValidationContext $context)
34
    {
35
        // Tracks already visited fragments to maintain O(N) and to ensure that cycles
36
        // are not redundantly reported.
37 117
        $this->visitedFrags = [];
38
39
        // Array of AST nodes used to produce meaningful errors
40 117
        $this->spreadPath = [];
41
42
        // Position in the spread path
43 117
        $this->spreadPathIndexByName = [];
44
45
        return [
46
            NodeKind::OPERATION_DEFINITION => function () {
47 102
                return Visitor::skipNode();
48 117
            },
49
            NodeKind::FRAGMENT_DEFINITION  => function (FragmentDefinitionNode $node) use ($context) {
50 23
                if (! isset($this->visitedFrags[$node->name->value])) {
51 23
                    $this->detectCycleRecursive($node, $context);
52
                }
53
54 23
                return Visitor::skipNode();
55 117
            },
56
        ];
57
    }
58
59 23
    private function detectCycleRecursive(FragmentDefinitionNode $fragment, ValidationContext $context)
60
    {
61 23
        $fragmentName                      = $fragment->name->value;
62 23
        $this->visitedFrags[$fragmentName] = true;
63
64 23
        $spreadNodes = $context->getFragmentSpreads($fragment);
65
66 23
        if (empty($spreadNodes)) {
67 12
            return;
68
        }
69
70 17
        $this->spreadPathIndexByName[$fragmentName] = count($this->spreadPath);
71
72 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...
73 17
            $spreadNode = $spreadNodes[$i];
74 17
            $spreadName = $spreadNode->name->value;
75 17
            $cycleIndex = $this->spreadPathIndexByName[$spreadName] ?? null;
76
77 17
            if ($cycleIndex === null) {
78 14
                $this->spreadPath[] = $spreadNode;
79 14
                if (empty($this->visitedFrags[$spreadName])) {
80 13
                    $spreadFragment = $context->getFragment($spreadName);
81 13
                    if ($spreadFragment) {
82 12
                        $this->detectCycleRecursive($spreadFragment, $context);
83
                    }
84
                }
85 14
                array_pop($this->spreadPath);
86
            } else {
87 10
                $cyclePath = array_slice($this->spreadPath, $cycleIndex);
88 10
                $nodes     = $cyclePath;
89
90 10
                if (is_array($spreadNode)) {
91
                    $nodes = array_merge($nodes, $spreadNode);
92
                } else {
93 10
                    $nodes[] = $spreadNode;
94
                }
95
96 10
                $context->reportError(new Error(
97 10
                    self::cycleErrorMessage(
98 10
                        $spreadName,
99 10
                        Utils::map(
100 10
                            $cyclePath,
101
                            function ($s) {
102 7
                                return $s->name->value;
103 10
                            }
104
                        )
105
                    ),
106 10
                    $nodes
107
                ));
108
            }
109
        }
110
111 17
        $this->spreadPathIndexByName[$fragmentName] = null;
112 17
    }
113
114
    /**
115
     * @param string[] $spreadNames
116
     */
117 10
    public static function cycleErrorMessage($fragName, array $spreadNames = [])
118
    {
119 10
        return sprintf(
120 10
            'Cannot spread fragment "%s" within itself%s.',
121 10
            $fragName,
122 10
            ! empty($spreadNames) ? ' via ' . implode(', ', $spreadNames) : ''
123
        );
124
    }
125
}
126