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
Push — master ( f52af1...798802 )
by Jérémiah
01:26
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 62
    public function __construct($maxQueryDepth = self::DEFAULT_QUERY_MAX_DEPTH)
34
    {
35 62
        $this->setMaxQueryDepth($maxQueryDepth);
36 61
    }
37
38
    /**
39
     * Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
40
     *
41
     * @param $maxQueryDepth
42
     */
43 62
    public static function setMaxQueryDepth($maxQueryDepth)
44
    {
45 62
        if ($maxQueryDepth < 0) {
46 1
            throw new \InvalidArgumentException('$maxQueryDepth argument must be greater or equal to 0. ');
47
        }
48
49 61
        self::$maxQueryDepth = (int) $maxQueryDepth;
50 61
    }
51
52 6
    public static function maxQueryDepthErrorMessage($max, $count)
53
    {
54 6
        return sprintf('Max query depth should be %d but is greater or equal to %d.', $max, $count);
55
    }
56
57 61
    public function __invoke(ValidationContext $context)
58
    {
59
        // Gather all the fragment definition.
60
        // Importantly this does not include inline fragments.
61 61
        $definitions = $context->getDocument()->definitions;
62 61
        foreach ($definitions as $node) {
63 61
            if ($node instanceof FragmentDefinition) {
64 13
                $this->fragments[$node->name->value] = $node;
65 13
            }
66 61
        }
67 61
        $schema = $context->getSchema();
68 61
        $rootTypes = [$schema->getQueryType(), $schema->getMutationType(), $schema->getSubscriptionType()];
69
70 61
        return 0 !== self::$maxQueryDepth ? [Node::FIELD => $this->getFieldClosure($context, $rootTypes)] : [];
71
    }
72
73
    private function getFieldClosure(ValidationContext $context, array $rootTypes)
74
    {
75 31
        return function (Field $node) use ($context, $rootTypes) {
76 31
            $parentType = $context->getParentType();
77 31
            $type = $this->retrieveCurrentTypeFromValidationContext($context);
78 31
            $isIntrospectionType = $type && $type->name === '__Schema';
79 31
            $isParentRootType = $parentType && in_array($parentType, $rootTypes);
80
81
            // check depth only on first rootTypes children and ignore check on introspection query
82 31
            if ($isParentRootType && !$isIntrospectionType) {
83 30
                $depth = $node->selectionSet ?
84 30
                    $this->countSelectionDepth(
85 30
                        $node->selectionSet,
86 30
                        self::$maxQueryDepth + static::DEFAULT_MAX_COUNT_AFTER_DEPTH_LIMIT,
87 30
                        0,
88
                        true
89 30
                    ) :
90
                    0
91 30
                ;
92
93 30
                if ($depth > self::$maxQueryDepth) {
94 6
                    return new Error(static::maxQueryDepthErrorMessage(self::$maxQueryDepth, $depth), [$node]);
95
                }
96 24
            }
97 31
        };
98
    }
99
100 31
    private function retrieveCurrentTypeFromValidationContext(ValidationContext $context)
101
    {
102 31
        $type = $context->getType();
103
104 31
        if ($type instanceof WrappingType) {
105 27
            $type = $type->getWrappedType(true);
106 27
        }
107
108 31
        return $type;
109
    }
110
111 30
    private function countSelectionDepth(SelectionSet $selectionSet, $stopCountingAt, $depth = 0, $resetDepthForEachSelection = false)
112
    {
113 30
        foreach ($selectionSet->selections as $selectionAST) {
114 30
            if ($depth >= $stopCountingAt) {
115 3
                break;
116
            }
117
118 30
            $depth = $resetDepthForEachSelection ? 0 : $depth;
119
120 30
            if ($selectionAST instanceof Field) {
121 30
                $depth = $this->countFieldDepth($selectionAST->selectionSet, $stopCountingAt, $depth);
122 30
            } elseif ($selectionAST instanceof FragmentSpread) {
123 10
                $depth = $this->countFragmentDepth($selectionAST, $stopCountingAt, $depth);
124 20
            } elseif ($selectionAST instanceof InlineFragment) {
125 10
                $depth = $this->countInlineFragmentDepth($selectionAST->selectionSet, $stopCountingAt, $depth);
126 10
            }
127 30
        }
128
129 30
        return $depth;
130
    }
131
132 30
    private function countFieldDepth(SelectionSet $selectionSet = null, $stopCountingAt, $depth)
133
    {
134 30
        return null === $selectionSet ? $depth : $this->countSelectionDepth($selectionSet, $stopCountingAt, ++$depth);
135
    }
136
137 10
    private function countInlineFragmentDepth(SelectionSet $selectionSet = null, $stopCountingAt, $depth)
138
    {
139 10
        return null === $selectionSet ? $depth : $this->countSelectionDepth($selectionSet, $stopCountingAt, $depth);
140
    }
141
142 10
    private function countFragmentDepth(FragmentSpread $selectionAST, $stopCountingAt, $depth)
143
    {
144 10
        $spreadName = $selectionAST->name->value;
145 10
        if (isset($this->fragments[$spreadName])) {
146
            /** @var FragmentDefinition $fragment */
147 10
            $fragment = $this->fragments[$spreadName];
148 10
            $depth = $this->countSelectionDepth($fragment->selectionSet, $stopCountingAt, $depth);
149 10
        }
150
151 10
        return $depth;
152
    }
153
}
154