Passed
Push — master ( 46c3ee...445208 )
by Rafael
04:28
created

GraphQLEndpointController::setErrorHandler()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
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\Debug;
15
use GraphQL\Error\Error;
16
use GraphQL\GraphQL;
17
use GraphQL\Validator\DocumentValidator;
18
use GraphQL\Validator\Rules;
19
use Psr\Log\LoggerInterface;
20
use Symfony\Component\EventDispatcher\EventDispatcher;
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 Ynlo\GraphQLBundle\Error\ErrorFormatterInterface;
28
use Ynlo\GraphQLBundle\Error\ErrorHandlerInterface;
29
use Ynlo\GraphQLBundle\Error\ErrorQueue;
30
use Ynlo\GraphQLBundle\Events\GraphQLEvents;
31
use Ynlo\GraphQLBundle\Events\GraphQLOperationEvent;
32
use Ynlo\GraphQLBundle\Request\ExecuteQuery;
33
use Ynlo\GraphQLBundle\Request\RequestMiddlewareInterface;
34
use Ynlo\GraphQLBundle\Resolver\ResolverContext;
35
use Ynlo\GraphQLBundle\Schema\SchemaCompiler;
36
use Ynlo\GraphQLBundle\Security\EndpointResolver;
37
38
class GraphQLEndpointController
39
{
40
    /**
41
     * @var EndpointResolver
42
     */
43
    protected $resolver;
44
45
    /**
46
     * @var SchemaCompiler
47
     */
48
    protected $compiler;
49
50
    /**
51
     * @var EventDispatcherInterface
52
     */
53
    protected $dispatcher;
54
55
    /**
56
     * App Config
57
     *
58
     * @var array
59
     */
60
    protected $config = [];
61
62
    /**
63
     * @var ErrorFormatterInterface
64
     */
65
    protected $errorFormatter;
66
67
    /**
68
     * @var ErrorHandlerInterface
69
     */
70
    protected $errorHandler;
71
72
    /**
73
     * @var bool
74
     */
75
    protected $debug = false;
76
77
    /**
78
     * @var LoggerInterface
79
     */
80
    protected $logger;
81
82
    /**
83
     * @var iterable
84
     */
85
    protected $middlewares = [];
86
87
    /**
88
     * GraphQLEndpointController constructor.
89
     *
90
     * @param EndpointResolver $endpointResolver
91
     * @param SchemaCompiler   $compiler
92
     */
93
    public function __construct(EndpointResolver $endpointResolver, SchemaCompiler $compiler)
94
    {
95
        $this->resolver = $endpointResolver;
96
        $this->compiler = $compiler;
97
    }
98
99
    /**
100
     * @param ErrorFormatterInterface $errorFormatter
101
     */
102
    public function setErrorFormatter(ErrorFormatterInterface $errorFormatter): void
103
    {
104
        $this->errorFormatter = $errorFormatter;
105
    }
106
107
    /**
108
     * @param EventDispatcherInterface $dispatcher
109
     */
110
    public function setDispatcher(EventDispatcherInterface $dispatcher): void
111
    {
112
        $this->dispatcher = $dispatcher;
113
    }
114
115
    /**
116
     * @param ErrorHandlerInterface $errorHandler
117
     */
118
    public function setErrorHandler(ErrorHandlerInterface $errorHandler): void
119
    {
120
        $this->errorHandler = $errorHandler;
121
    }
122
123
    /**
124
     * @param bool $debug
125
     */
126
    public function setDebug(bool $debug): void
127
    {
128
        $this->debug = $debug;
129
    }
130
131
    /**
132
     * @param LoggerInterface|null $logger
133
     */
134
    public function setLogger(?LoggerInterface $logger): void
135
    {
136
        $this->logger = $logger;
137
    }
138
139
    /**
140
     * @param array $config
141
     */
142
    public function setConfig(array $config): void
143
    {
144
        $this->config = $config;
145
    }
146
147
    /**
148
     * @param iterable $middlewares
149
     */
150
    public function setMiddlewares(iterable $middlewares): void
151
    {
152
        $this->middlewares = $middlewares;
153
    }
154
155
    public function __invoke(Request $request): JsonResponse
156
    {
157
        $operationEvent = null;
158
159
        if (!$this->debug && $request->getMethod() !== Request::METHOD_POST) {
160
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The method should be POST to talk with GraphQL API');
161
        }
162
163
        try {
164
            $query = new ExecuteQuery();
165
            foreach ($this->middlewares as $middleware) {
166
                if ($middleware instanceof RequestMiddlewareInterface) {
167
                    $middleware->processRequest($request, $query);
168
                }
169
            }
170
171
            $endpoint = $this->resolver->resolveEndpoint($request);
172
            if (!$endpoint) {
173
                throw new AccessDeniedHttpException();
174
            }
175
176
            if ($this->dispatcher) {
177
                $operationEvent = new GraphQLOperationEvent($query, $endpoint);
178
                $this->dispatcher->dispatch(GraphQLEvents::OPERATION_START, $operationEvent);
179
            }
180
181
            $context = new ResolverContext($endpoint);
182
            $validationRules = null;
183
184
            $schema = $this->compiler->compile($endpoint);
185
            $schema->assertValid();
186
187
            $result = GraphQL::executeQuery(
188
                $schema,
189
                $query->getRequestString(),
190
                null,
191
                $context,
192
                $query->getVariables(),
193
                $query->getOperationName(),
194
                null,
195
                $validationRules
196
            );
197
198
            //https://webonyx.github.io/graphql-php/error-handling/
199
            $formatter = $this->errorFormatter;
200
            $handler = $this->errorHandler;
201
202
            //get queued errors
203
            $exceptions = ErrorQueue::all();
204
            foreach ($exceptions as $exception) {
205
                $result->errors[] = Error::createLocatedError($exception);
206
            }
207
208
            $result->setErrorFormatter([$formatter, 'format']);
209
            $result->setErrorsHandler(
210
                function ($errors) use ($handler, $formatter) {
211
                    return $handler->handle($errors, $formatter, $this->getDebugMode());
212
                }
213
            );
214
215
            $output = $result->toArray($this->getDebugMode());
216
            $statusCode = Response::HTTP_OK;
217
        } catch (\Exception $e) {
218
            $error = Error::createLocatedError($e);
219
            $errors = $this->errorHandler->handle([$error], $this->errorFormatter, $this->debug);
220
            if ($e instanceof HttpException) {
221
                $statusCode = $e->getStatusCode();
222
            } else {
223
                $statusCode = Response::HTTP_INTERNAL_SERVER_ERROR;
224
            }
225
226
            $output = [
227
                'errors' => $errors,
228
            ];
229
        }
230
231
        if ($this->dispatcher && $operationEvent) {
232
            $this->dispatcher->dispatch(GraphQLEvents::OPERATION_END, $operationEvent);
233
        }
234
235
        return JsonResponse::create($output, $statusCode);
236
    }
237
238
    public function addGlobalValidationRules(array $validationRules): void
239
    {
240
        $rules = [];
241
        if (!empty($validationRules['query_complexity'])) {
242
            $rules[] = new Rules\QueryComplexity($validationRules['query_complexity']);
243
        }
244
        if (!empty($validationRules['query_depth'])) {
245
            $rules[] = new Rules\QueryDepth($validationRules['query_depth']);
246
        }
247
        if (!empty($validationRules['disable_introspection'])) {
248
            $rules[] = new Rules\DisableIntrospection();
249
        }
250
        array_map([DocumentValidator::class, 'addRule'], $rules);
251
    }
252
253
    /**
254
     * @return bool|int
255
     */
256
    private function getDebugMode()
257
    {
258
        if (!$this->debug) {
259
            // in case of debug = false
260
            // If API_DEBUG is passed, output of error formatter is enriched which debugging information.
261
            // Helpful for tests to get full error logs without the need of enable full app debug flag
262
            if (isset($_ENV['API_DEBUG'])) {
263
                $this->debug = $_ENV['API_DEBUG'];
264
            } elseif (isset($_SERVER['API_DEBUG'])) {
265
                $this->debug = $_SERVER['API_DEBUG'];
266
            }
267
        }
268
269
        $debugFlags = false;
270
        if ($this->debug) {
271
            if ($this->config['error_handling']['show_trace'] ?? true) {
272
                $debugFlags = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE;
273
            } else {
274
                $debugFlags = Debug::INCLUDE_DEBUG_MESSAGE;
275
            }
276
        }
277
278
        return $debugFlags;
279
    }
280
}
281