Completed
Push — master ( 911cdf...67ba27 )
by Alexandr
02:54
created

Reducer::walkQuery()   C

Complexity

Conditions 15
Paths 4

Size

Total Lines 53
Code Lines 33

Duplication

Lines 18
Ratio 33.96 %

Code Coverage

Tests 42
CRAP Score 15.0211

Importance

Changes 0
Metric Value
dl 18
loc 53
ccs 42
cts 44
cp 0.9545
rs 6.2939
c 0
b 0
f 0
cc 15
eloc 33
nc 4
nop 2
crap 15.0211

How to fix   Long Method    Complexity   

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
 * Date: 07.11.16
4
 *
5
 * @author Portey Vasil <[email protected]>
6
 */
7
8
namespace Youshido\GraphQL\Execution;
9
10
11
use Youshido\GraphQL\Execution\Context\ExecutionContextInterface;
12
use Youshido\GraphQL\Execution\Visitor\AbstractQueryVisitor;
13
use Youshido\GraphQL\Field\Field;
14
use Youshido\GraphQL\Field\FieldInterface;
15
use Youshido\GraphQL\Parser\Ast\Field as FieldAst;
16
use Youshido\GraphQL\Parser\Ast\FragmentReference;
17
use Youshido\GraphQL\Parser\Ast\Interfaces\FragmentInterface;
18
use Youshido\GraphQL\Parser\Ast\Mutation;
19
use Youshido\GraphQL\Parser\Ast\Query;
20
use Youshido\GraphQL\Type\AbstractType;
21
use Youshido\GraphQL\Type\Object\AbstractObjectType;
22
use Youshido\GraphQL\Type\Union\AbstractUnionType;
23
24
class Reducer
25
{
26
27
    /** @var  ExecutionContextInterface */
28
    private $executionContext;
29
30
    /**
31
     * Apply all of $reducers to this query.  Example reducer operations: checking for maximum query complexity,
32
     * performing look-ahead query planning, etc.
33
     *
34
     * @param ExecutionContextInterface $executionContext
35
     * @param AbstractQueryVisitor[]    $reducers
36
     */
37 2
    public function reduceQuery(ExecutionContextInterface $executionContext, array $reducers)
38
    {
39 2
        $this->executionContext = $executionContext;
40 2
        $schema                 = $executionContext->getSchema();
41
42 2
        foreach ($reducers as $reducer) {
43 2
            foreach ($executionContext->getRequest()->getAllOperations() as $operation) {
44 2
                $this->doVisit($operation, $operation instanceof Mutation ? $schema->getMutationType() : $schema->getQueryType(), $reducer);
45 2
            }
46 2
        }
47 2
    }
48
49
    /**
50
     * Entry point for the `walkQuery` routine.  Execution bounces between here, where the reducer's ->visit() method
51
     * is invoked, and `walkQuery` where we send in the scores from the `visit` call.
52
     *
53
     * @param Query                $query
54
     * @param AbstractType         $currentLevelSchema
55
     * @param AbstractQueryVisitor $reducer
56
     */
57 2
    protected function doVisit(Query $query, $currentLevelSchema, $reducer)
58
    {
59 2
        if (!($currentLevelSchema instanceof AbstractObjectType) || !$currentLevelSchema->hasField($query->getName())) {
60
            return;
61
        }
62
63 2
        if ($operationField = $currentLevelSchema->getField($query->getName())) {
64
65 2
            $coroutine = $this->walkQuery($query, $operationField);
66
67 2
            if ($results = $coroutine->current()) {
68 2
                $queryCost = 0;
69 2
                while ($results) {
70
                    // initial values come from advancing the generator via ->current, subsequent values come from ->send()
71 2
                    list($queryField, $astField, $childCost) = $results;
72
73
                    /**
74
                     * @var Query|FieldAst $queryField
75
                     * @var Field          $astField
76
                     */
77 2
                    $cost = $reducer->visit($queryField->getKeyValueArguments(), $astField->getConfig(), $childCost);
78 2
                    $queryCost += $cost;
79 2
                    $results = $coroutine->send($cost);
80 2
                }
81 2
            }
82 2
        }
83 2
    }
84
85
    /**
86
     * Coroutine to walk the query and schema in DFS manner (see AbstractQueryVisitor docs for more info) and yield a
87
     * tuple of (queryNode, schemaNode, childScore)
88
     *
89
     * childScore costs are accumulated via values sent into the coroutine.
90
     *
91
     * Most of the branching in this function is just to handle the different types in a query: Queries, Unions,
92
     * Fragments (anonymous and named), and Fields.  The core of the function is simple: recurse until we hit the base
93
     * case of a Field and yield that back up to the visitor up in `doVisit`.
94
     *
95
     * @param Query|Field|\Youshido\GraphQL\Parser\Ast\Interfaces\FragmentInterface $queryNode
96
     * @param FieldInterface                                                        $currentLevelAST
97
     *
98
     * @return \Generator
99
     */
100 2
    protected function walkQuery($queryNode, FieldInterface $currentLevelAST)
101
    {
102 2
        $childrenScore = 0;
103 2
        if (!($queryNode instanceof FieldAst)) {
104 2
            foreach ($queryNode->getFields() as $queryField) {
0 ignored issues
show
Bug introduced by
The method getFields does only exist in Youshido\GraphQL\Field\F...raphQL\Parser\Ast\Query, but not in Youshido\GraphQL\Parser\...faces\FragmentInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
105 2
                if ($queryField instanceof FragmentInterface) {
106 1
                    if ($queryField instanceof FragmentReference) {
107
                        $queryField = $this->executionContext->getRequest()->getFragment($queryField->getName());
108
                    }
109
                    // the next 7 lines are essentially equivalent to `yield from $this->walkQuery(...)` in PHP7.
110
                    // for backwards compatibility this is equivalent.
111
                    // This pattern is repeated multiple times in this function, and unfortunately cannot be extracted or
112
                    // made less verbose.
113 1
                    $gen  = $this->walkQuery($queryField, $currentLevelAST);
0 ignored issues
show
Bug introduced by
It seems like $queryField defined by $this->executionContext-...$queryField->getName()) on line 107 can also be of type null or object<Youshido\GraphQL\Parser\Ast\Fragment>; however, Youshido\GraphQL\Execution\Reducer::walkQuery() does only seem to accept object<Youshido\GraphQL\...aces\FragmentInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
114 1
                    $next = $gen->current();
115 1
                    while ($next) {
116 1
                        $received = (yield $next);
117 1
                        $childrenScore += (int)$received;
118 1
                        $next = $gen->send($received);
119 1
                    }
120 1
                } else {
121 2
                    $fieldType = $currentLevelAST->getType()->getNamedType();
122 2
                    if ($fieldType instanceof AbstractUnionType) {
123 1
                        foreach ($fieldType->getTypes() as $unionFieldType) {
124 1 View Code Duplication
                            if ($fieldAst = $unionFieldType->getField($queryField->getName())) {
0 ignored issues
show
Bug introduced by
The method getField does only exist in Youshido\GraphQL\Type\Object\AbstractObjectType, but not in Youshido\GraphQL\Type\Scalar\AbstractScalarType.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125 1
                                $gen  = $this->walkQuery($queryField, $fieldAst);
126 1
                                $next = $gen->current();
127 1
                                while ($next) {
128 1
                                    $received = (yield $next);
129 1
                                    $childrenScore += (int)$received;
130 1
                                    $next = $gen->send($received);
131 1
                                }
132 1
                            }
133 1
                        }
134 2 View Code Duplication
                    } elseif ($fieldType instanceof AbstractObjectType && $fieldAst = $fieldType->getField($queryField->getName())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
135 1
                        $gen  = $this->walkQuery($queryField, $fieldAst);
136 1
                        $next = $gen->current();
137 1
                        while ($next) {
138 1
                            $received = (yield $next);
139 1
                            $childrenScore += (int)$received;
140 1
                            $next = $gen->send($received);
141 1
                        }
142 1
                    }
143
                }
144 2
            }
145 2
        }
146
        // sanity check.  don't yield fragments; they don't contribute to cost
147 2
        if ($queryNode instanceof Query || $queryNode instanceof FieldAst) {
148
            // BASE CASE.  If we're here we're done recursing -
149
            // this node is either a field, or a query that we've finished recursing into.
150 2
            yield [$queryNode, $currentLevelAST, $childrenScore];
151 2
        }
152 2
    }
153
154
}