Passed
Push — master ( 9da6fd...0354d3 )
by Rafael
05:30
created

GraphQLEndpointController   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Test Coverage

Coverage 74.73%

Importance

Changes 0
Metric Value
wmc 30
dl 0
loc 219
ccs 68
cts 91
cp 0.7473
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A setErrorHandler() 0 3 1
A __construct() 0 4 1
A setDebug() 0 3 1
A addGlobalValidationRules() 0 13 4
A setErrorFormatter() 0 3 1
A setLogger() 0 3 1
A setMiddlewares() 0 3 1
A setConfig() 0 3 1
F __invoke() 0 100 19
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\HttpFoundation\JsonResponse;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
25
use Symfony\Component\HttpKernel\Exception\HttpException;
26
use Ynlo\GraphQLBundle\Error\DefaultErrorFormatter;
27
use Ynlo\GraphQLBundle\Error\DefaultErrorHandler;
28
use Ynlo\GraphQLBundle\Error\ErrorFormatterInterface;
29
use Ynlo\GraphQLBundle\Error\ErrorHandlerInterface;
30
use Ynlo\GraphQLBundle\Error\ErrorQueue;
31
use Ynlo\GraphQLBundle\Request\ExecuteQuery;
32
use Ynlo\GraphQLBundle\Request\RequestMiddlewareInterface;
33
use Ynlo\GraphQLBundle\Resolver\QueryExecutionContext;
34
use Ynlo\GraphQLBundle\Schema\SchemaCompiler;
35
use Ynlo\GraphQLBundle\Security\EndpointResolver;
36
37
class GraphQLEndpointController
38
{
39
    /**
40
     * @var EndpointResolver
41
     */
42
    protected $resolver;
43
44
    /**
45
     * @var SchemaCompiler
46
     */
47
    protected $compiler;
48
49
    /**
50
     * App Config
51
     *
52
     * @var array
53
     */
54
    protected $config = [];
55
56
    /**
57
     * @var ErrorFormatterInterface
58
     */
59
    protected $errorFormatter;
60
61
    /**
62
     * @var ErrorHandlerInterface
63
     */
64
    protected $errorHandler;
65
66
    /**
67
     * @var bool
68
     */
69
    protected $debug = false;
70
71
    /**
72
     * @var LoggerInterface
73
     */
74
    protected $logger;
75
76
    /**
77
     * @var iterable
78
     */
79
    protected $middlewares = [];
80
81
    /**
82
     * GraphQLEndpointController constructor.
83
     *
84
     * @param EndpointResolver $endpointResolver
85
     * @param SchemaCompiler   $compiler
86
     */
87 22
    public function __construct(EndpointResolver $endpointResolver, SchemaCompiler $compiler)
88
    {
89 22
        $this->resolver = $endpointResolver;
90 22
        $this->compiler = $compiler;
91 22
    }
92
93
    /**
94
     * @param ErrorFormatterInterface $errorFormatter
95
     */
96 22
    public function setErrorFormatter(ErrorFormatterInterface $errorFormatter): void
97
    {
98 22
        $this->errorFormatter = $errorFormatter;
99 22
    }
100
101
    /**
102
     * @param ErrorHandlerInterface $errorHandler
103
     */
104 22
    public function setErrorHandler(ErrorHandlerInterface $errorHandler): void
105
    {
106 22
        $this->errorHandler = $errorHandler;
107 22
    }
108
109
    /**
110
     * @param bool $debug
111
     */
112 22
    public function setDebug(bool $debug): void
113
    {
114 22
        $this->debug = $debug;
115 22
    }
116
117
    /**
118
     * @param LoggerInterface|null $logger
119
     */
120 22
    public function setLogger(?LoggerInterface $logger): void
121
    {
122 22
        $this->logger = $logger;
123 22
    }
124
125
    /**
126
     * @param array $config
127
     */
128 22
    public function setConfig(array $config): void
129
    {
130 22
        $this->config = $config;
131 22
    }
132
133
    /**
134
     * @param iterable $middlewares
135
     */
136 22
    public function setMiddlewares(iterable $middlewares): void
137
    {
138 22
        $this->middlewares = $middlewares;
139 22
    }
140
141 22
    public function __invoke(Request $request): JsonResponse
142
    {
143 22
        if (!$this->debug && $request->getMethod() !== Request::METHOD_POST) {
144
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The method should be POST to talk with GraphQL API');
145
        }
146
147 22
        $query = new ExecuteQuery();
148 22
        foreach ($this->middlewares as $middleware) {
149 22
            if ($middleware instanceof RequestMiddlewareInterface) {
150 22
                $middleware->processRequest($request, $query);
151
            }
152
        }
153
154 22
        $context = new QueryExecutionContext();
155 22
        $validationRules = null;
156
157
        try {
158 22
            $endpoint = $this->resolver->resolveEndpoint($request);
159 22
            if (!$endpoint) {
160
                throw new AccessDeniedHttpException();
161
            }
162
163 22
            $schema = $this->compiler->compile($endpoint);
164 22
            $schema->assertValid();
165
166 22
            $result = GraphQL::executeQuery(
167 22
                $schema,
168 22
                $query->getRequestString(),
169 22
                null,
170 22
                $context,
171 22
                $query->getVariables(),
172 22
                $query->getOperationName(),
173 22
                null,
174 22
                $validationRules
175
            );
176
177 22
            if (!$this->debug) {
178
                // in case of debug = false
179
                // If API_DEBUG is passed, output of error formatter is enriched which debugging information.
180
                // Helpful for tests to get full error logs without the need of enable full app debug flag
181 22
                if (isset($_ENV['API_DEBUG'])) {
182
                    $this->debug = $_ENV['API_DEBUG'];
183 22
                } elseif (isset($_SERVER['API_DEBUG'])) {
184
                    $this->debug = $_SERVER['API_DEBUG'];
185
                }
186
            }
187
188 22
            $debugFlags = false;
189 22
            if ($this->debug) {
190
                if ($this->config['error_handling']['show_trace'] ?? true) {
191
                    $debugFlags = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE;
192
                } else {
193
                    $debugFlags = Debug::INCLUDE_DEBUG_MESSAGE;
194
                }
195
            }
196
197
            //https://webonyx.github.io/graphql-php/error-handling/
198 22
            $formatter = $this->errorFormatter ?? new DefaultErrorFormatter();
0 ignored issues
show
Bug introduced by
The call to Ynlo\GraphQLBundle\Error...ormatter::__construct() has too few arguments starting with errorManager. ( Ignorable by Annotation )

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

198
            $formatter = $this->errorFormatter ?? /** @scrutinizer ignore-call */ new DefaultErrorFormatter();

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
199 22
            $handler = $this->errorHandler ?? new DefaultErrorHandler($this->logger);
200
201
            //get queued errors
202 22
            $exceptions = ErrorQueue::all();
203 22
            foreach ($exceptions as $exception) {
204 1
                $result->errors[] = Error::createLocatedError($exception);
205
            }
206
207 22
            $result->setErrorFormatter([$formatter, 'format']);
208 22
            $result->setErrorsHandler(
209 22
                function ($errors) use ($handler, $formatter, $debugFlags) {
210 2
                    return $handler->handle($errors, $formatter, $debugFlags);
211 22
                }
212
            );
213
214 22
            $output = $result->toArray($debugFlags);
215 22
            $statusCode = Response::HTTP_OK;
216
        } catch (\Exception $e) {
217
            if (null !== $this->logger) {
218
                $this->logger->error($e->getMessage(), $e->getTrace());
219
            }
220
221
            if ($e instanceof HttpException) {
222
                $message = Response::$statusTexts[$e->getStatusCode()];
223
                $statusCode = $e->getStatusCode();
224
            } else {
225
                $statusCode = Response::HTTP_INTERNAL_SERVER_ERROR;
226
                $message = 'Internal Server Error';
227
                if ($this->debug || ($e instanceof ClientAware && $e->isClientSafe())) {
228
                    $message = $e->getMessage();
229
                }
230
            }
231
232
            $output['errors']['message'] = $message;
233
            $output['errors']['category'] = 'internal';
234
235
            if ($this->debug) {
236
                $output['errors']['trace'] = $e->getTraceAsString();
237
            }
238
        }
239
240 22
        return JsonResponse::create($output, $statusCode);
241
    }
242
243 22
    public function addGlobalValidationRules(array $validationRules): void
244
    {
245 22
        $rules = [];
246 22
        if (!empty($validationRules['query_complexity'])) {
247 22
            $rules[] = new Rules\QueryComplexity($validationRules['query_complexity']);
248
        }
249 22
        if (!empty($validationRules['query_depth'])) {
250
            $rules[] = new Rules\QueryDepth($validationRules['query_depth']);
251
        }
252 22
        if (!empty($validationRules['disable_introspection'])) {
253
            $rules[] = new Rules\DisableIntrospection();
254
        }
255 22
        array_map([DocumentValidator::class, 'addRule'], $rules);
256 22
    }
257
}
258