Completed
Pull Request — master (#45)
by Christoffer
02:06
created

NoFragmentCyclesRule::detectFragmentCycle()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 25
nc 6
nop 1
dl 0
loc 45
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Validation\Rule;
4
5
use Digia\GraphQL\Error\ValidationException;
6
use Digia\GraphQL\Language\AST\Node\DocumentNode;
7
use Digia\GraphQL\Language\AST\Node\FragmentDefinitionNode;
8
use Digia\GraphQL\Language\AST\Node\FragmentSpreadNode;
9
use Digia\GraphQL\Language\AST\Node\NodeInterface;
10
use Digia\GraphQL\Language\AST\Node\OperationDefinitionNode;
11
12
class NoFragmentCyclesRule extends AbstractRule
13
{
14
    /**
15
     * @var array
16
     */
17
    protected $visitedFragments = [];
18
19
    /**
20
     * @var array
21
     */
22
    protected $spreadPath = [];
23
24
    /**
25
     * @var array
26
     */
27
    protected $spreadPathIndexByName = [];
28
29
    /**
30
     * @inheritdoc
31
     */
32
    public function enterNode(NodeInterface $node): ?NodeInterface
33
    {
34
        if ($node instanceof OperationDefinitionNode) {
35
            return null;
36
        }
37
38
        if ($node instanceof FragmentDefinitionNode) {
39
            if (!isset($this->visitedFragments[$node->getNameValue()])) {
40
                $this->detectFragmentCycle($node);
41
            }
42
43
            return null;
44
        }
45
46
        return $node;
47
    }
48
49
    /**
50
     * @param FragmentDefinitionNode $fragment
51
     */
52
    protected function detectFragmentCycle(FragmentDefinitionNode $fragment): void
53
    {
54
        $fragmentName = $fragment->getNameValue();
55
56
        $this->visitedFragments[$fragmentName] = true;
57
58
        $spreadNodes = $this->context->getFragmentSpreads($fragment->getSelectionSet());
59
60
        if (empty($spreadNodes)) {
61
            return;
62
        }
63
64
        $this->spreadPathIndexByName[$fragmentName] = \count($this->spreadPath);
65
66
        foreach ($spreadNodes as $spreadNode) {
67
            $spreadName = $spreadNode->getNameValue();
68
            $cycleIndex = $this->spreadPathIndexByName[$spreadName] ?? null;
69
70
            if (null === $cycleIndex) {
71
                $this->spreadPath[] = $spreadNode;
72
73
                if (!isset($this->visitedFragments[$spreadName])) {
74
                    $spreadFragment = $this->context->getFragment($spreadName);
75
76
                    if (null !== $spreadFragment) {
77
                        $this->detectFragmentCycle($spreadFragment);
78
                    }
79
                }
80
81
                array_pop($this->spreadPath);
82
            } else {
83
                $cyclePath = \array_slice($this->spreadPath, $cycleIndex);
84
85
                $this->context->reportError(
86
                    new ValidationException(
87
                        fragmentCycleMessage($spreadName, array_map(function (FragmentSpreadNode $spread) {
88
                            return $spread->getNameValue();
89
                        }, $cyclePath)),
90
                        array_merge($cyclePath, [$spreadNode])
91
                    )
92
                );
93
            }
94
        }
95
96
        $this->spreadPathIndexByName[$fragmentName] = null;
97
    }
98
}
99