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 (#264)
by Jérémiah
22:49
created

Executor::checkExecutionResult()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 1
crap 3
1
<?php
2
3
namespace Overblog\GraphQLBundle\Request;
4
5
use GraphQL\Executor\ExecutionResult;
6
use GraphQL\Executor\Promise\Promise;
7
use GraphQL\Executor\Promise\PromiseAdapter;
8
use GraphQL\Type\Schema;
9
use GraphQL\Validator\DocumentValidator;
10
use GraphQL\Validator\Rules\QueryComplexity;
11
use GraphQL\Validator\Rules\QueryDepth;
12
use Overblog\GraphQLBundle\Event\Events;
13
use Overblog\GraphQLBundle\Event\ExecutorContextEvent;
14
use Overblog\GraphQLBundle\Event\ExecutorEvent;
15
use Overblog\GraphQLBundle\Event\ExecutorResultEvent;
16
use Overblog\GraphQLBundle\Executor\ExecutorInterface;
17
use Overblog\GraphQLBundle\Executor\Promise\PromiseAdapterInterface;
18
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
19
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
20
21
class Executor
22
{
23
    const PROMISE_ADAPTER_SERVICE_ID = 'overblog_graphql.promise_adapter';
24
25
    /** @var Schema[] */
26
    private $schemas;
27
28
    /** @var EventDispatcherInterface|null */
29
    private $dispatcher;
30
31
    /** @var ExecutorInterface */
32
    private $executor;
33
34
    /** @var PromiseAdapter */
35
    private $promiseAdapter;
36
37
    /** @var callable|null */
38
    private $defaultFieldResolver;
39
40
    public function __construct(
41
        ExecutorInterface $executor,
42
        EventDispatcherInterface $dispatcher,
43
        PromiseAdapter $promiseAdapter = null,
44
        callable $defaultFieldResolver = null
45
    ) {
46
        $this->executor = $executor;
47 77
        $this->dispatcher = $dispatcher;
48
        $this->promiseAdapter = $promiseAdapter;
49
        $this->defaultFieldResolver = $defaultFieldResolver;
50
    }
51
52
    public function setExecutor(ExecutorInterface $executor)
53
    {
54
        $this->executor = $executor;
55
56 77
        return $this;
57 77
    }
58 77
59 77
    public function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
60 77
    {
61 77
        $this->promiseAdapter = $promiseAdapter;
62 77
63 77
        return $this;
64
    }
65 2
66
    /**
67 2
     * @param string $name
68
     * @param Schema $schema
69 2
     *
70
     * @return $this
71
     */
72 1
    public function addSchema($name, Schema $schema)
73
    {
74 1
        $this->schemas[$name] = $schema;
75
76 1
        return $this;
77
    }
78
79
    /**
80
     * @param string|null $name
81
     *
82 69
     * @return Schema
83
     */
84 69
    public function getSchema($name = null)
85
    {
86 69
        if (empty($this->schemas)) {
87
            throw new \RuntimeException('At least one schema should be declare.');
88
        }
89 1
90
        if (null === $name) {
91 1
            $schema = array_values($this->schemas)[0];
92
        } else {
93 1
            if (!isset($this->schemas[$name])) {
94
                throw new NotFoundHttpException(sprintf('Could not found "%s" schema.', $name));
95
            }
96 77
            $schema = $this->schemas[$name];
97
        }
98 77
99
        return $schema;
100 77
    }
101
102
    public function setMaxQueryDepth($maxQueryDepth)
103 59
    {
104
        /** @var QueryDepth $queryDepth */
105 59
        $queryDepth = DocumentValidator::getRule('QueryDepth');
106
        $queryDepth->setMaxQueryDepth($maxQueryDepth);
107
    }
108 71
109
    public function setMaxQueryComplexity($maxQueryComplexity)
110
    {
111 71
        /** @var QueryComplexity $queryComplexity */
112 71
        $queryComplexity = DocumentValidator::getRule('QueryComplexity');
113 71
        $queryComplexity->setMaxQueryComplexity($maxQueryComplexity);
114
    }
115 71
116
    /**
117
     * @param null|string                    $schemaName
118 71
     * @param array                          $request
119 71
     * @param null|array|\ArrayObject|object $rootValue
120 71
     * @param null|array|\ArrayObject|object $contextValue
121
     *
122
     * @return ExecutionResult
123
     */
124
    public function execute($schemaName, array $request, $rootValue = null, $contextValue = null)
125
    {
126
        $executorEvent = $this->preExecute(
127 28
            $this->getSchema($schemaName),
128
            isset($request[ParserInterface::PARAM_QUERY]) ? $request[ParserInterface::PARAM_QUERY] : null,
129 28
            self::createArrayObject($rootValue),
130
            self::createArrayObject($contextValue),
131 28
            $request[ParserInterface::PARAM_VARIABLES],
132
            isset($request[ParserInterface::PARAM_OPERATION_NAME]) ? $request[ParserInterface::PARAM_OPERATION_NAME] : null
133
        );
134 63
135
        $result = $this->executor->execute(
136 63
            $executorEvent->getSchema(),
137 58
            $executorEvent->getRequestString(),
138 58
            $executorEvent->getRootValue(),
0 ignored issues
show
Documentation introduced by
$executorEvent->getRootValue() is of type object<ArrayObject>, but the function expects a null|array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
139 58
            $executorEvent->getContextValue(),
0 ignored issues
show
Documentation introduced by
$executorEvent->getContextValue() is of type object<ArrayObject>, but the function expects a null|array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
140
            $executorEvent->getVariableValue(),
141
            $executorEvent->getOperationName()
142 63
        );
143 59
144 1
        $result = $this->postExecute($result);
145 1
146 1
        return $result;
147 1
    }
148 1
149
    /**
150
     * @param Schema       $schema
151
     * @param string       $requestString
152
     * @param \ArrayObject $rootValue
153
     * @param \ArrayObject $contextValue
154 62
     * @param array|null   $variableValue
155
     * @param string|null  $operationName
156 61
     *
157 61
     * @return ExecutorEvent
158
     */
159 61
    private function preExecute(
160
        Schema $schema, $requestString,
161 61
        \ArrayObject $rootValue,
162 57
        \ArrayObject $contextValue,
163
        array $variableValue = null,
164
        $operationName = null
165 61
    ) {
166 61
        $this->checkPromiseAdapter();
167 61
168 61
        $this->executor->setPromiseAdapter($this->promiseAdapter);
169 61
        // this is needed when not using only generated types
170 61
        if ($this->defaultFieldResolver) {
171 61
            $this->executor->setDefaultFieldResolver($this->defaultFieldResolver);
172
        }
173
        $this->dispatcher->dispatch(Events::EXECUTOR_CONTEXT, new ExecutorContextEvent($contextValue));
174 61
175 57
        return $this->dispatcher->dispatch(
176
            Events::PRE_EXECUTOR,
177
            new ExecutorEvent($schema, $requestString, $rootValue, $contextValue, $variableValue, $operationName)
178 61
        );
179 2
    }
180 2
181
    /**
182
     * @param ExecutionResult|Promise $result
183
     *
184 59
     * @return ExecutionResult
185
     */
186
    private function postExecute($result)
187
    {
188
        if ($this->promiseAdapter) {
189
            $result = $this->promiseAdapter->wait($result);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GraphQL\Executor\Promise\PromiseAdapter as the method wait() does only exist in the following implementations of said interface: GraphQL\Executor\Promise...pter\SyncPromiseAdapter, Overblog\GraphQLBundle\E...ter\ReactPromiseAdapter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
190
        }
191
192
        $this->checkExecutionResult($result);
193
194 59
        $event = $this->dispatcher->dispatch(
195
            Events::POST_EXECUTOR,
196 59
            new ExecutorResultEvent($result)
197 1
        );
198 1
199 1
        return $event->getResult();
200
    }
201
202
    private function checkPromiseAdapter()
203 59
    {
204 57
        if ($this->promiseAdapter && !$this->promiseAdapter instanceof PromiseAdapterInterface && !is_callable([$this->promiseAdapter, 'wait'])) {
205
            throw new \RuntimeException(
206
                sprintf(
207 59
                    'PromiseAdapter should be an object instantiating "%s" or "%s" with a "wait" method.',
208
                    PromiseAdapterInterface::class,
209
                    PromiseAdapter::class
210
                )
211
            );
212
        }
213
    }
214
215 64
    private function checkExecutionResult($result)
216
    {
217 64
        if (!is_object($result) || !$result instanceof ExecutionResult) {
218 1
            throw new \RuntimeException(
219
                sprintf('Execution result should be an object instantiating "%s".', ExecutionResult::class)
220
            );
221 63
        }
222 60
    }
223
224 3
    private static function createArrayObject($data)
225 1
    {
226
        if (is_array($data) || is_object($data)) {
227 2
            $object = new \ArrayObject($data);
228
        } else {
229
            $object = new \ArrayObject();
230 62
        }
231
232
        return $object;
233
    }
234
}
235