Failed Conditions
Pull Request — master (#11)
by Adrien
15:48 queued 12:33
created

Server::handleError()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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