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:56
created

MaxQueryDepth::__invoke()   D

Complexity

Conditions 9
Paths 3

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 9

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 39
ccs 27
cts 27
cp 1
rs 4.909
cc 9
eloc 23
nc 3
nop 1
crap 9
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 => function (Field $node) use ($context, $rootTypes) {
63 58
                $parentType = $context->getParentType();
64 58
                $type = $this->retrieveCurrentTypeFromValidationContext($context);
65 58
                $isIntrospectionType = $type && $type->name === '__Schema';
66 58
                $isParentRootType = $parentType && in_array($parentType, $rootTypes);
67
68
                // check depth only on first rootTypes children and ignore check on introspection query
69 58
                if ($isParentRootType && !$isIntrospectionType) {
70 53
                    $depth = $node->selectionSet ?
71 53
                        $this->countSelectionDepth(
72 53
                            $node->selectionSet,
73 53
                            self::$maxQueryDepth + static::DEFAULT_MAX_COUNT_AFTER_DEPTH_LIMIT,
74 53
                            0,
75
                            true
76 53
                        ) :
77
                        0
78 53
                    ;
79
80 53
                    if ($depth > self::$maxQueryDepth) {
81 6
                        return new Error(static::maxQueryDepthErrorMessage(self::$maxQueryDepth, $depth), [$node]);
82
                    }
83 47
                }
84 58
            },
85 58
        ];
86
    }
87
88 58
    private function retrieveCurrentTypeFromValidationContext(ValidationContext $context)
89
    {
90 58
        $type = $context->getType();
91
92 58
        if ($type instanceof WrappingType) {
93 53
            $type = $type->getWrappedType(true);
94 53
        }
95
96 58
        return $type;
97
    }
98
99 53
    private function countSelectionDepth(SelectionSet $selectionSet, $stopCountingAt, $depth = 0, $resetDepthForEachSelection = false)
100
    {
101 53
        foreach ($selectionSet->selections as $selectionAST) {
102 53
            if ($depth >= $stopCountingAt) {
103 3
                break;
104
            }
105
106 53
            $depth = $resetDepthForEachSelection ? 0 : $depth;
107
108 53
            if ($selectionAST instanceof Field) {
109 53
                $depth = $this->countFieldDepth($selectionAST->selectionSet, $stopCountingAt, $depth);
110 53
            } elseif ($selectionAST instanceof FragmentSpread) {
111 10
                $depth = $this->countFragmentDepth($selectionAST, $stopCountingAt, $depth);
112 23
            } elseif ($selectionAST instanceof InlineFragment) {
113 13
                $depth = $this->countInlineFragmentDepth($selectionAST->selectionSet, $stopCountingAt, $depth);
114 13
            }
115 53
        }
116
117 53
        return $depth;
118
    }
119
120 53
    private function countFieldDepth(SelectionSet $selectionSet = null, $stopCountingAt, $depth)
121
    {
122 53
        return null === $selectionSet ? $depth : $this->countSelectionDepth($selectionSet, $stopCountingAt, ++$depth);
123
    }
124
125 13
    private function countInlineFragmentDepth(SelectionSet $selectionSet = null, $stopCountingAt, $depth)
126
    {
127 13
        return null === $selectionSet ? $depth : $this->countSelectionDepth($selectionSet, $stopCountingAt, $depth);
128
    }
129
130 10
    private function countFragmentDepth(FragmentSpread $selectionAST, $stopCountingAt, $depth)
131
    {
132 10
        $spreadName = $selectionAST->name->value;
133 10
        if (isset($this->fragments[$spreadName])) {
134
            /** @var FragmentDefinition $fragment */
135 10
            $fragment = $this->fragments[$spreadName];
136 10
            $depth = $this->countSelectionDepth($fragment->selectionSet, $stopCountingAt, $depth);
137 10
        }
138
139 10
        return $depth;
140
    }
141
}
142