Failed Conditions
Push — master ( 7aeedc...f7e5fc )
by Adrien
05:38 queued 02:24
created

Server   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 82
Duplicated Lines 0 %

Test Coverage

Coverage 57.14%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 12
eloc 32
c 7
b 0
f 0
dl 0
loc 82
ccs 20
cts 35
cp 0.5714
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A handleError() 0 17 6
A sendCli() 0 3 1
A execute() 0 16 2
A __construct() 0 22 3
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;
24
25
    private readonly ServerConfig $config;
26
27
    /**
28
     * @param bool $debug if true, dumps stacktrace in case of error
29
     */
30 4
    public function __construct(Schema $schema, bool $debug, array $rootValue = [])
31
    {
32 4
        GraphQL::setDefaultFieldResolver(new FilteredFieldResolver());
33
34 4
        $debugFlag = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
35
36 4
        $this->config = ServerConfig::create([
0 ignored issues
show
Bug introduced by
The property config is declared read-only in Ecodev\Felix\Api\Server.
Loading history...
37 4
            'schema' => $schema,
38 4
            'queryBatching' => true,
39 4
            'debugFlag' => $debug ? $debugFlag : DebugFlag::NONE,
40 4
            'errorsHandler' => function (array $errors, callable $formatter) {
41
                $result = [];
42
                foreach ($errors as $e) {
43
                    $result[] = $this->handleError($e, $formatter);
44
                }
45
46
                return $result;
47 4
            },
48 4
            'rootValue' => $rootValue,
49 4
        ]);
50
51 4
        $this->server = new StandardServer($this->config);
0 ignored issues
show
Bug introduced by
The property server is declared read-only in Ecodev\Felix\Api\Server.
Loading history...
52
    }
53
54
    /**
55
     * @return ExecutionResult|ExecutionResult[]
56
     */
57 4
    public function execute(ServerRequestInterface $request): array|ExecutionResult
58
    {
59 4
        if (!$request->getParsedBody()) {
60
            /** @var array $parsedBody */
61 4
            $parsedBody = json_decode($request->getBody()->getContents(), true) ?? [];
62 4
            $request = $request->withParsedBody($parsedBody);
63
        }
64
65
        // Affect it to global request, so it is available for log purpose in case of error
66 4
        $_REQUEST = $request->getParsedBody();
67
68
        // Set current session as the only context we will ever need
69 4
        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
70 4
        $this->config->setContext($session);
71
72 4
        return $this->server->executePsrRequest($request);
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->server->executePsrRequest($request) could return the type GraphQL\Executor\Promise\Promise which is incompatible with the type-hinted return GraphQL\Executor\ExecutionResult|array. Consider adding an additional type-check to rule them out.
Loading history...
73
    }
74
75
    /**
76
     * Send response to CLI.
77
     */
78
    public function sendCli(ExecutionResult $result): void
79
    {
80
        echo json_encode($result, JSON_PRETTY_PRINT) . PHP_EOL;
81
    }
82
83
    /**
84
     * Custom error handler to log in DB and show trigger messages to end-user.
85
     */
86
    private function handleError(Throwable $exception, callable $formatter): array
87
    {
88
        // Always log exception in DB (and by email)
89
        _log()->err($exception->__toString(), ['exception' => $exception]);
90
91
        // If we are absolutely certain that the error comes from one of our trigger with a custom message for end-user,
92
        // then wrap the exception to make it showable to the end-user
93
        $previous = $exception->getPrevious();
94
        if ($previous instanceof DriverException && $previous->getSQLState() === '45000' && $previous->getPrevious() && $previous->getPrevious()->getPrevious()) {
95
            $message = $previous->getPrevious()->getPrevious()->getMessage();
96
            $userMessage = (string) preg_replace('~SQLSTATE\[45000]: <<Unknown error>>: \d+ ~', '', $message, -1, $count);
97
            if ($count === 1) {
98
                $exception = new Exception($userMessage, 0, $exception);
99
            }
100
        }
101
102
        return $formatter($exception);
103
    }
104
}
105