Passed
Push — master ( 73bfc2...a878a7 )
by Adrien
13:45 queued 10:48
created

Server   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 79
Duplicated Lines 0 %

Test Coverage

Coverage 50%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 32
c 5
b 0
f 0
dl 0
loc 79
ccs 15
cts 30
cp 0.5
rs 10
wmc 12

4 Methods

Rating   Name   Duplication   Size   Complexity  
A handleError() 0 17 6
A sendCli() 0 3 1
A __construct() 0 22 3
A execute() 0 16 2
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 4
    public function __construct(Schema $schema, bool $debug, array $rootValue = [])
28
    {
29 4
        GraphQL::setDefaultFieldResolver(new FilteredFieldResolver());
30
31 4
        $debugFlag = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
32
33 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...
34
            'schema' => $schema,
35
            'queryBatching' => true,
36 4
            'debugFlag' => $debug ? $debugFlag : DebugFlag::NONE,
37 4
            '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 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...
49
    }
50
51
    /**
52
     * @return ExecutionResult|ExecutionResult[]
53
     */
54 4
    public function execute(ServerRequestInterface $request): array|ExecutionResult
55
    {
56 4
        if (!$request->getParsedBody()) {
57
            /** @var array $parsedBody */
58 4
            $parsedBody = json_decode($request->getBody()->getContents(), true) ?? [];
59 4
            $request = $request->withParsedBody($parsedBody);
60
        }
61
62
        // Affect it to global request so it is available for log purpose in case of error
63 4
        $_REQUEST = $request->getParsedBody();
64
65
        // Set current session as the only context we will ever need
66 4
        $session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
67 4
        $this->config->setContext($session);
68
69 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...
70
    }
71
72
    /**
73
     * Send response to CLI.
74
     */
75
    public function sendCli(ExecutionResult $result): void
76
    {
77
        echo json_encode($result, JSON_PRETTY_PRINT) . PHP_EOL;
78
    }
79
80
    /**
81
     * Custom error handler to log in DB and show trigger messages to end-user.
82
     */
83
    private function handleError(Throwable $exception, callable $formatter): array
84
    {
85
        // Always log exception in DB (and by email)
86
        _log()->err($exception->__toString(), ['exception' => $exception]);
87
88
        // If we are absolutely certain that the error comes from one of our trigger with a custom message for end-user,
89
        // then wrap the exception to make it showable to the end-user
90
        $previous = $exception->getPrevious();
91
        if ($previous instanceof DriverException && $previous->getSQLState() === '45000' && $previous->getPrevious() && $previous->getPrevious()->getPrevious()) {
92
            $message = $previous->getPrevious()->getPrevious()->getMessage();
93
            $userMessage = (string) preg_replace('~SQLSTATE\[45000\]: <<Unknown error>>: \d+ ~', '', $message, -1, $count);
94
            if ($count === 1) {
95
                $exception = new Exception($userMessage, 0, $exception);
96
            }
97
        }
98
99
        return $formatter($exception);
100
    }
101
}
102