GraphQLEndpointController::__invoke()   F
last analyzed

Complexity

Conditions 17
Paths 1985

Size

Total Lines 94
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 306

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 56
c 7
b 0
f 0
dl 0
loc 94
ccs 0
cts 73
cp 0
rs 1.0499
cc 17
nc 1985
nop 1
crap 306

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
/*******************************************************************************
4
 *  This file is part of the GraphQL Bundle package.
5
 *
6
 *  (c) YnloUltratech <[email protected]>
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 Ynlo\GraphQLBundle\Controller;
13
14
use GraphQL\Error\ClientAware;
15
use GraphQL\Error\Debug;
16
use GraphQL\Error\Error;
17
use GraphQL\GraphQL;
18
use GraphQL\Validator\DocumentValidator;
19
use GraphQL\Validator\Rules;
20
use Psr\Log\LoggerInterface;
21
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22
use Symfony\Component\HttpFoundation\JsonResponse;
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpFoundation\Response;
25
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
26
use Symfony\Component\HttpKernel\Exception\HttpException;
27
use Symfony\Component\Mercure\PublisherInterface;
28
use Symfony\Component\Mercure\Update;
29
use Ynlo\GraphQLBundle\Error\ErrorFormatterInterface;
30
use Ynlo\GraphQLBundle\Error\ErrorHandlerInterface;
31
use Ynlo\GraphQLBundle\Error\ErrorQueue;
32
use Ynlo\GraphQLBundle\Events\GraphQLEvents;
33
use Ynlo\GraphQLBundle\Events\GraphQLOperationEvent;
34
use Ynlo\GraphQLBundle\Request\ExecuteQuery;
35
use Ynlo\GraphQLBundle\Request\RequestMiddlewareInterface;
36
use Ynlo\GraphQLBundle\Resolver\ResolverContext;
37
use Ynlo\GraphQLBundle\Schema\SchemaCompiler;
38
use Ynlo\GraphQLBundle\Security\EndpointResolver;
39
use Ynlo\GraphQLBundle\Subscription\SubscriptionRequest;
40
41
class GraphQLEndpointController
42
{
43
    /**
44
     * @var EndpointResolver
45
     */
46
    protected $resolver;
47
48
    /**
49
     * @var SchemaCompiler
50
     */
51
    protected $compiler;
52
53
    /**
54
     * @var EventDispatcherInterface
55
     */
56
    protected $dispatcher;
57
58
    /**
59
     * App Config
60
     *
61
     * @var array
62
     */
63
    protected $config = [];
64
65
    /**
66
     * @var ErrorFormatterInterface
67
     */
68
    protected $errorFormatter;
69
70
    /**
71
     * @var ErrorHandlerInterface
72
     */
73
    protected $errorHandler;
74
75
    /**
76
     * @var bool
77
     */
78
    protected $debug = false;
79
80
    /**
81
     * @var LoggerInterface
82
     */
83
    protected $logger;
84
85
    /**
86
     * @var iterable
87
     */
88
    protected $middlewares = [];
89
90
    /**
91
     * @var PublisherInterface
92
     */
93
    protected $publisher;
94
95
    /**
96
     * GraphQLEndpointController constructor.
97
     *
98
     * @param EndpointResolver $endpointResolver
99
     * @param SchemaCompiler   $compiler
100
     */
101
    public function __construct(EndpointResolver $endpointResolver, SchemaCompiler $compiler)
102
    {
103
        $this->resolver = $endpointResolver;
104
        $this->compiler = $compiler;
105
    }
106
107
    /**
108
     * @param ErrorFormatterInterface $errorFormatter
109
     */
110
    public function setErrorFormatter(ErrorFormatterInterface $errorFormatter): void
111
    {
112
        $this->errorFormatter = $errorFormatter;
113
    }
114
115
    /**
116
     * @param EventDispatcherInterface $dispatcher
117
     */
118
    public function setDispatcher(EventDispatcherInterface $dispatcher): void
119
    {
120
        $this->dispatcher = $dispatcher;
121
    }
122
123
    /**
124
     * @param ErrorHandlerInterface $errorHandler
125
     */
126
    public function setErrorHandler(ErrorHandlerInterface $errorHandler): void
127
    {
128
        $this->errorHandler = $errorHandler;
129
    }
130
131
    /**
132
     * @param bool $debug
133
     */
134
    public function setDebug(bool $debug): void
135
    {
136
        $this->debug = $debug;
137
    }
138
139
    /**
140
     * @param LoggerInterface|null $logger
141
     */
142
    public function setLogger(?LoggerInterface $logger): void
143
    {
144
        $this->logger = $logger;
145
    }
146
147
    /**
148
     * @param array $config
149
     */
150
    public function setConfig(array $config): void
151
    {
152
        $this->config = $config;
153
    }
154
155
    /**
156
     * @param iterable $middlewares
157
     */
158
    public function setMiddlewares(iterable $middlewares): void
159
    {
160
        $this->middlewares = $middlewares;
161
    }
162
163
    /**
164
     * @param PublisherInterface $publisher
165
     */
166
    public function setPublisher(PublisherInterface $publisher): void
167
    {
168
        $this->publisher = $publisher;
169
    }
170
171
    /**
172
     * @param Request $request
173
     *
174
     * @return JsonResponse
175
     */
176
    public function __invoke(Request $request): JsonResponse
177
    {
178
        $operationEvent = null;
179
180
        if (!$this->debug && $request->getMethod() !== Request::METHOD_POST) {
181
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The method should be POST to talk with GraphQL API');
182
        }
183
184
        try {
185
            $query = new ExecuteQuery();
186
            foreach ($this->middlewares as $middleware) {
187
                if ($middleware instanceof RequestMiddlewareInterface) {
188
                    $middleware->processRequest($request, $query);
189
                }
190
            }
191
192
            $endpoint = $this->resolver->resolveEndpoint($request);
193
            if (!$endpoint) {
194
                throw new AccessDeniedHttpException();
195
            }
196
197
            if ($this->dispatcher) {
198
                $operationEvent = new GraphQLOperationEvent($query, $endpoint);
199
                $this->dispatcher->dispatch(GraphQLEvents::OPERATION_START, $operationEvent);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with $operationEvent. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

199
                $this->dispatcher->/** @scrutinizer ignore-call */ 
200
                                   dispatch(GraphQLEvents::OPERATION_START, $operationEvent);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
Ynlo\GraphQLBundle\Event...Events::OPERATION_START of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
                $this->dispatcher->dispatch(/** @scrutinizer ignore-type */ GraphQLEvents::OPERATION_START, $operationEvent);
Loading history...
200
            }
201
202
            $context = new ResolverContext($endpoint);
203
            $validationRules = null;
204
205
            $schema = $this->compiler->compile($endpoint);
206
            $schema->assertValid();
207
208
            if ($subscriptionRequest = $query->getSubscriptionRequest()) {
209
                $context->setMeta('subscriptionRequest', $subscriptionRequest);
210
            }
211
212
            $result = GraphQL::executeQuery(
213
                $schema,
214
                $query->getRequestString(),
215
                null,
216
                $context,
217
                $query->getVariables(),
218
                $query->getOperationName(),
219
                null,
220
                $validationRules
221
            );
222
223
            //https://webonyx.github.io/graphql-php/error-handling/
224
            $formatter = $this->errorFormatter;
225
            $handler = $this->errorHandler;
226
227
            //get queued errors
228
            $exceptions = ErrorQueue::all();
229
            foreach ($exceptions as $exception) {
230
                $result->errors[] = Error::createLocatedError($exception);
231
            }
232
233
            $result->setErrorFormatter([$formatter, 'format']);
234
            $result->setErrorsHandler(
235
                function ($errors) use ($handler, $formatter) {
236
                    return $handler->handle($errors, $formatter, $this->getDebugMode());
237
                }
238
            );
239
240
            $output = $result->toArray($this->getDebugMode());
241
            $statusCode = Response::HTTP_OK;
242
        } catch (\Exception $e) {
243
            $error = Error::createLocatedError($e);
244
            $errors = $this->errorHandler->handle([$error], $this->errorFormatter, $this->debug);
245
            if ($e instanceof HttpException) {
246
                $statusCode = $e->getStatusCode();
247
            } elseif ($e instanceof ClientAware) {
248
                // usually client's exceptions do not arrive until here
249
                // but sometimes this exception happen during compilation time, like: Ynlo\GraphQLBundle\Type\Registry\InvalidTypeException
250
                // due to invalid user request
251
                $statusCode = Response::HTTP_BAD_REQUEST;
252
            } else {
253
                $statusCode = Response::HTTP_INTERNAL_SERVER_ERROR;
254
            }
255
256
            $output = [
257
                'errors' => $errors,
258
            ];
259
        }
260
261
        if ($this->dispatcher && $operationEvent) {
262
            $this->dispatcher->dispatch(GraphQLEvents::OPERATION_END, $operationEvent);
263
        }
264
265
        if ($this->publisher && isset($subscriptionRequest) && $subscriptionRequest instanceof SubscriptionRequest) {
266
            ($this->publisher)(new Update($subscriptionRequest->getId(), json_encode($output)));
267
        }
268
269
        return JsonResponse::create($output, $statusCode);
270
    }
271
272
    public function addGlobalValidationRules(array $validationRules): void
273
    {
274
        $rules = [];
275
        if (!empty($validationRules['query_complexity'])) {
276
            $rules[] = new Rules\QueryComplexity($validationRules['query_complexity']);
277
        }
278
        if (!empty($validationRules['query_depth'])) {
279
            $rules[] = new Rules\QueryDepth($validationRules['query_depth']);
280
        }
281
        if (!empty($validationRules['disable_introspection'])) {
282
            $rules[] = new Rules\DisableIntrospection();
283
        }
284
        array_map([DocumentValidator::class, 'addRule'], $rules);
285
    }
286
287
    /**
288
     * @return bool|int
289
     */
290
    private function getDebugMode()
291
    {
292
        if (!$this->debug) {
293
            // in case of debug = false
294
            // If API_DEBUG is passed, output of error formatter is enriched which debugging information.
295
            // Helpful for tests to get full error logs without the need of enable full app debug flag
296
            if (isset($_ENV['API_DEBUG'])) {
297
                $this->debug = $_ENV['API_DEBUG'];
298
            } elseif (isset($_SERVER['API_DEBUG'])) {
299
                $this->debug = $_SERVER['API_DEBUG'];
300
            }
301
        }
302
303
        $debugFlags = false;
304
        if ($this->debug) {
305
            if ($this->config['error_handling']['show_trace'] ?? true) {
306
                $debugFlags = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE;
307
            } else {
308
                $debugFlags = Debug::INCLUDE_DEBUG_MESSAGE;
309
            }
310
        }
311
312
        return $debugFlags;
313
    }
314
}
315