Passed
Push — master ( 319fcb...7a107a )
by Rafael
04:41
created

GraphQLEndpointController   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 155
Duplicated Lines 0 %

Test Coverage

Coverage 72%

Importance

Changes 0
Metric Value
wmc 26
dl 0
loc 155
c 0
b 0
f 0
ccs 54
cts 75
cp 0.72
rs 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A addGlobalValidationRules() 0 13 4
F __invoke() 0 98 21
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\GraphQL;
17
use GraphQL\Validator\DocumentValidator;
18
use GraphQL\Validator\Rules;
19
use Psr\Log\LoggerInterface;
20
use Symfony\Component\HttpFoundation\JsonResponse;
21
use Symfony\Component\HttpFoundation\Request;
22
use Symfony\Component\HttpFoundation\Response;
23
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
24
use Symfony\Component\HttpKernel\Exception\HttpException;
25
use Ynlo\GraphQLBundle\Request\ExecuteQuery;
26
use Ynlo\GraphQLBundle\Request\RequestMiddlewareInterface;
27
use Ynlo\GraphQLBundle\Schema\SchemaCompiler;
28
use Ynlo\GraphQLBundle\Security\EndpointResolver;
29
30
class GraphQLEndpointController
31
{
32
    /**
33
     * @var EndpointResolver
34
     */
35
    protected $resolver;
36
37
    /**
38
     * @var SchemaCompiler
39
     */
40
    protected $compiler;
41
42
    /**
43
     * @var bool
44
     */
45
    protected $debug;
46
47
    /**
48
     * @var LoggerInterface
49
     */
50
    protected $logger;
51
52
    /**
53
     * @var iterable
54
     */
55
    protected $middlewares = [];
56
57 22
    public function __construct(
58
        EndpointResolver $endpointResolver,
59
        SchemaCompiler $compiler,
60
        iterable $middlewares = [],
61
        bool $debug = false,
62
        LoggerInterface $logger = null
63
    )
64
    {
65 22
        $this->resolver = $endpointResolver;
66 22
        $this->middlewares = $middlewares;
67 22
        $this->compiler = $compiler;
68 22
        $this->debug = $debug;
69 22
        $this->logger = $logger;
70 22
    }
71
72 22
    public function __invoke(Request $request): JsonResponse
73
    {
74 22
        if (!$this->debug && $request->getMethod() !== Request::METHOD_POST) {
75
            throw new HttpException(Response::HTTP_BAD_REQUEST, 'The method should be POST to talk with GraphQL API');
76
        }
77
78 22
        $query = new ExecuteQuery();
79 22
        foreach ($this->middlewares as $middleware) {
80 22
            if ($middleware instanceof RequestMiddlewareInterface) {
81 22
                $middleware->processRequest($request, $query);
82
            }
83
        }
84
85 22
        $context = null;
86 22
        $validationRules = null;
87
88
        try {
89 22
            $endpoint = $this->resolver->resolveEndpoint($request);
90 22
            if (!$endpoint) {
91
                throw new AccessDeniedHttpException();
92
            }
93
94 22
            $schema = $this->compiler->compile($endpoint);
95 22
            $schema->assertValid();
96
97 22
            $result = GraphQL::executeQuery(
98 22
                $schema,
99 22
                $query->getRequestString(),
100 22
                null,
101 22
                $context,
102 22
                $query->getVariables(),
103 22
                $query->getOperationName(),
104 22
                null,
105 22
                $validationRules
106
            );
107
108 22
            if (!$this->debug) {
109
                // in case of debug = false
110
                // If API_DEBUG is passed, output of error formatter is enriched which debugging information.
111
                // Helpful for tests to get full error logs without the need of enable full app debug flag
112 22
                if (isset($_ENV['API_DEBUG'])) {
113
                    $this->debug = $_ENV['API_DEBUG'];
114 22
                } elseif (isset($_SERVER['API_DEBUG'])) {
115
                    $this->debug = $_SERVER['API_DEBUG'];
116
                }
117
            }
118
119 22
            $debugFlags = false;
120 22
            if ($this->debug) {
121
                $debugFlags = Debug::INCLUDE_DEBUG_MESSAGE | Debug::INCLUDE_TRACE;
122
            }
123
124 22
            foreach ($result->errors as $error) {
125 1
                if (null !== $this->logger) {
126 1
                    $originError = $error->getTrace()[0]['args'][0] ?? null;
127 1
                    $context = [];
128 1
                    if ($originError instanceof \Exception) {
129
                        $context = [
130 1
                            'file' => $originError->getFile(),
131 1
                            'line' => $originError->getLine(),
132 1
                            'error' => get_class($originError),
133
                        ];
134
                    }
135 1
                    if ($error->isClientSafe()) {
136 1
                        $this->logger->notice($error->getMessage(), $context);
137
                    } else {
138 1
                        $this->logger->error($error->getMessage(), $context);
139
                    }
140
                }
141
            }
142
143 22
            $output = $result->toArray($debugFlags);
144 22
            $statusCode = Response::HTTP_OK;
145
        } catch (\Exception $e) {
146
            if (null !== $this->logger) {
147
                $this->logger->error($e->getMessage(), $e->getTrace());
148
            }
149
150
            if ($e instanceof HttpException) {
151
                $message = Response::$statusTexts[$e->getStatusCode()];
152
                $statusCode = $e->getStatusCode();
153
            } else {
154
                $statusCode = Response::HTTP_INTERNAL_SERVER_ERROR;
155
                $message = 'Internal Server Error';
156
                if ($this->debug || ($e instanceof ClientAware && $e->isClientSafe())) {
157
                    $message = $e->getMessage();
158
                }
159
            }
160
161
            $output['errors']['message'] = $message;
162
            $output['errors']['category'] = 'internal';
163
164
            if ($this->debug) {
165
                $output['errors']['trace'] = $e->getTraceAsString();
166
            }
167
        }
168
169 22
        return JsonResponse::create($output, $statusCode);
170
    }
171
172 22
    public function addGlobalValidationRules(array $validationRules): void
173
    {
174 22
        $rules = [];
175 22
        if (!empty($validationRules['query_complexity'])) {
176 22
            $rules[] = new Rules\QueryComplexity($validationRules['query_complexity']);
177
        }
178 22
        if (!empty($validationRules['query_depth'])) {
179
            $rules[] = new Rules\QueryDepth($validationRules['query_depth']);
180
        }
181 22
        if (!empty($validationRules['disable_introspection'])) {
182
            $rules[] = new Rules\DisableIntrospection();
183
        }
184 22
        array_map([DocumentValidator::class, 'addRule'], $rules);
185 22
    }
186
}
187