Ecodev /
felix
| 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
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 |