Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Pull Request — master (#21)
by Jérémiah
08:59
created

MaxQueryDepth::countSelectionDepth()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 20
ccs 14
cts 14
cp 1
rs 8.2222
cc 7
eloc 12
nc 2
nop 4
crap 7
1
<?php
2
3
/*
4
 * This file is part of the OverblogGraphQLBundle package.
5
 *
6
 * (c) Overblog <http://github.com/overblog/>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Overblog\GraphQLBundle\Request\Validator\Rule;
13
14
use GraphQL\Error;
15
use GraphQL\Language\AST\Field;
16
use GraphQL\Language\AST\FragmentDefinition;
17
use GraphQL\Language\AST\FragmentSpread;
18
use GraphQL\Language\AST\InlineFragment;
19
use GraphQL\Language\AST\Node;
20
use GraphQL\Language\AST\SelectionSet;
21
use GraphQL\Type\Definition\WrappingType;
22
use GraphQL\Validator\ValidationContext;
23
24
class MaxQueryDepth
25
{
26
    const DEFAULT_QUERY_MAX_DEPTH = 100;
27
    const DEFAULT_MAX_COUNT_AFTER_DEPTH_LIMIT = 50;
28
29
    private static $maxQueryDepth;
30
31
    private $fragments = [];
32
33 58
    public function __construct($maxQueryDepth = self::DEFAULT_QUERY_MAX_DEPTH)
34
    {
35 58
        $this->setMaxQueryDepth($maxQueryDepth);
36 58
    }
37
38 58
    public static function setMaxQueryDepth($maxQueryDepth)
39
    {
40 58
        self::$maxQueryDepth = (int) $maxQueryDepth;
41 58
    }
42
43 6
    public static function maxQueryDepthErrorMessage($max, $count)
44
    {
45 6
        return sprintf('Max query depth should be %d but is greater or equal to %d.', $max, $count);
46
    }
47
48 58
    public function __invoke(ValidationContext $context)
49
    {
50
        // Gather all the fragment definition.
51
        // Importantly this does not include inline fragments.
52 58
        $definitions = $context->getDocument()->definitions;
53 58
        foreach ($definitions as $node) {
54 58
            if ($node instanceof FragmentDefinition) {
55 12
                $this->fragments[$node->name->value] = $node;
56 12
            }
57 58
        }
58 58
        $schema = $context->getSchema();
59 58
        $rootTypes = [$schema->getQueryType(), $schema->getMutationType(), $schema->getSubscriptionType()];
60
61
        return [
62 58
            Node::FIELD => $this->getFieldClosure($context, $rootTypes),
63 58
        ];
64
    }
65
66
    private function getFieldClosure(ValidationContext $context, array $rootTypes)
67
    {
68 58
        return function (Field $node) use ($context, $rootTypes) {
69 58
            $parentType = $context->getParentType();
70 58
            $type = $this->retrieveCurrentTypeFromValidationContext($context);
71 58
            $isIntrospectionType = $type && $type->name === '__Schema';
72 58
            $isParentRootType = $parentType && in_array($parentType, $rootTypes);
73
74
            // check depth only on first rootTypes children and ignore check on introspection query
75 58
            if ($isParentRootType && !$isIntrospectionType) {
76 53
                $depth = $node->selectionSet ?
77 53
                    $this->countSelectionDepth(
78 53
                        $node->selectionSet,
79 53
                        self::$maxQueryDepth + static::DEFAULT_MAX_COUNT_AFTER_DEPTH_LIMIT,
80 53
                        0,
81
                        true
82 53
                    ) :
83
                    0
84 53
                ;
85
86 53
                if ($depth > self::$maxQueryDepth) {
87 6
                    return new Error(static::maxQueryDepthErrorMessage(self::$maxQueryDepth, $depth), [$node]);
88
                }
89 47
            }
90 58
        };
91
    }
92
93 58
    private function retrieveCurrentTypeFromValidationContext(ValidationContext $context)
94
    {
95 58
        $type = $context->getType();
96
97 58
        if ($type instanceof WrappingType) {
98 53
            $type = $type->getWrappedType(true);
99 53
        }
100
101 58
        return $type;
102
    }
103
104 53
    private function countSelectionDepth(SelectionSet $selectionSet, $stopCountingAt, $depth = 0, $resetDepthForEachSelection = false)
105
    {
106 53
        foreach ($selectionSet->selections as $selectionAST) {
107 53
            if ($depth >= $stopCountingAt) {
108 3
                break;
109
            }
110
111 53
            $depth = $resetDepthForEachSelection ? 0 : $depth;
112
113 53
            if ($selectionAST instanceof Field) {
114 53
                $depth = $this->countFieldDepth($selectionAST->selectionSet, $stopCountingAt, $depth);
115 53
            } elseif ($selectionAST instanceof FragmentSpread) {
116 10
                $depth = $this->countFragmentDepth($selectionAST, $stopCountingAt, $depth);
117 23
            } elseif ($selectionAST instanceof InlineFragment) {
118 13
                $depth = $this->countInlineFragmentDepth($selectionAST->selectionSet, $stopCountingAt, $depth);
119 13
            }
120 53
        }
121
122 53
        return $depth;
123
    }
124
125 53
    private function countFieldDepth(SelectionSet $selectionSet = null, $stopCountingAt, $depth)
126
    {
127 53
        return null === $selectionSet ? $depth : $this->countSelectionDepth($selectionSet, $stopCountingAt, ++$depth);
128
    }
129
130 13
    private function countInlineFragmentDepth(SelectionSet $selectionSet = null, $stopCountingAt, $depth)
131
    {
132 13
        return null === $selectionSet ? $depth : $this->countSelectionDepth($selectionSet, $stopCountingAt, $depth);
133
    }
134
135 10
    private function countFragmentDepth(FragmentSpread $selectionAST, $stopCountingAt, $depth)
136
    {
137 10
        $spreadName = $selectionAST->name->value;
138 10
        if (isset($this->fragments[$spreadName])) {
139
            /** @var FragmentDefinition $fragment */
140 10
            $fragment = $this->fragments[$spreadName];
141 10
            $depth = $this->countSelectionDepth($fragment->selectionSet, $stopCountingAt, $depth);
142 10
        }
143
144 10
        return $depth;
145
    }
146
}
147