Failed Conditions
Push — master ( 009a85...8a3516 )
by Adrien
04:43
created

Server::__construct()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 3
eloc 13
c 3
b 0
f 0
nc 1
nop 3
dl 0
loc 22
ccs 0
cts 17
cp 0
crap 12
rs 9.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Api;
6
7
use Doctrine\DBAL\Exception\DriverException;
8
use GraphQL\Error\DebugFlag;
9
use GraphQL\Executor\ExecutionResult;
10
use GraphQL\GraphQL;
11
use GraphQL\Server\ServerConfig;
12
use GraphQL\Server\StandardServer;
13
use GraphQL\Type\Schema;
14
use Mezzio\Session\SessionMiddleware;
15
use Psr\Http\Message\ServerRequestInterface;
16
use Throwable;
17
18
/**
19
 * A thin wrapper to serve GraphQL via HTTP or CLI
20
 */
21
class Server
22
{
23
    /**
24
     * @var StandardServer
25
     */
26
    private $server;
27
28
    /**
29
     * @var ServerConfig
30
     */
31
    private $config;
32
33
    public function __construct(Schema $schema, bool $debug, array $rootValue = [])
34
    {
35
        GraphQL::setDefaultFieldResolver(new FilteredFieldResolver());
36
37
        $debugFlag = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
38
39
        $this->config = ServerConfig::create([
40
            'schema' => $schema,
41
            'queryBatching' => true,
42
            'debugFlag' => $debug ? $debugFlag : DebugFlag::NONE,
43
            'errorsHandler' => function (array $errors, callable $formatter) {
44
                $result = [];
45
                foreach ($errors as $e) {
46
                    $result[] = $this->handleError($e, $formatter);
47
                }
48
49
                return $result;
50
            },
51
            'rootValue' => $rootValue,
52
        ]);
53
54
        $this->server = new StandardServer($this->config);
55
    }
56
57
    /**
58
     * @return ExecutionResult|ExecutionResult[]
59
     */
60
    public function execute(ServerRequestInterface $request)
61
    {
62
        if (!$request->getParsedBody()) {
63
            $request = $request->withParsedBody(json_decode($request->getBody()->getContents(), true));
64
        }
65
66
        // Affect it to global request so it is available for log purpose in case of error
67
        $_REQUEST = $request->getParsedBody();
68
69
        // Set current session as the only context we will ever need
70
        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
71
        $this->config->setContext($session);
72
73
        return $this->server->executePsrRequest($request);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->server->executePsrRequest($request) also could return the type GraphQL\Executor\Promise\Promise which is incompatible with the documented return type GraphQL\Executor\Executi...xecutor\ExecutionResult.
Loading history...
74
    }
75
76
    /**
77
     * Send response using standard PHP `header()` and `echo`.
78
     *
79
     * Most of the time you should not use this and instead return the
80
     * response directly to the middleware.
81
     *
82
     * @param ExecutionResult|ExecutionResult[] $result
83
     */
84
    public function sendHttp($result): void
85
    {
86
        $this->server->getHelper()->sendResponse($result);
87
    }
88
89
    /**
90
     * Send response to CLI
91
     */
92
    public function sendCli(ExecutionResult $result): void
93
    {
94
        echo json_encode($result, JSON_PRETTY_PRINT) . PHP_EOL;
95
    }
96
97
    /**
98
     * Custom error handler to log in DB and show trigger messages to end-user
99
     */
100
    private function handleError(Throwable $exception, callable $formatter): array
101
    {
102
        // Always log exception in DB (and by email)
103
        _log()->err($exception->__toString());
104
105
        // If we are absolutely certain that the error comes from one of our trigger with a custom message for end-user,
106
        // then wrap the exception to make it showable to the end-user
107
        $previous = $exception->getPrevious();
108
        if ($previous instanceof DriverException && $previous->getSQLState() === '45000' && $previous->getPrevious() && $previous->getPrevious()->getPrevious()) {
109
            $message = $previous->getPrevious()->getPrevious()->getMessage();
110
            $userMessage = (string) preg_replace('~SQLSTATE\[45000\]: <<Unknown error>>: \d+ ~', '', $message, -1, $count);
111
            if ($count === 1) {
112
                $exception = new Exception($userMessage, 0, $exception);
113
            }
114
        }
115
116
        return $formatter($exception);
117
    }
118
}
119