Skip to content

Commit 6c808ed

Browse files
committed
APQ first implementation
1 parent c4c7ffa commit 6c808ed

11 files changed

+348
-61
lines changed

app/public/index.php

+31-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
use Slim\Factory\AppFactory;
1010
use Slim\Psr7\Response;
1111

12+
use App\GraphQL\Boilerplate\Endpoint;
13+
use App\GraphQL\Exception\GenericGraphQlException;
14+
1215
// Create Container using PHP-DI
1316
$container = new Container();
1417

@@ -50,6 +53,30 @@
5053
return $handler->handle($request);
5154
});
5255

56+
57+
// Define Custom JSON Error Handler
58+
$graphQlErrorHandler = function (
59+
Request $request,
60+
Throwable $exception,
61+
bool $displayErrorDetails,
62+
bool $logErrors,
63+
bool $logErrorDetails,
64+
?LoggerInterface $logger = null
65+
) use ($app) {
66+
if (isset($logger)) {
67+
$logger->error($exception->getMessage());
68+
}
69+
70+
$response = $app->getResponseFactory()->createResponse();
71+
$output = Endpoint::generateOutputError([$exception]);
72+
$httpCode = 500;
73+
if ($exception instanceof GenericGraphQlException) {
74+
$httpCode = $exception->isHttpCode ? $exception->getCode() : $httpCode;
75+
}
76+
$response = Endpoint::setupResponse($response, $output, $httpCode);
77+
return $response;
78+
};
79+
5380
/**
5481
* The routing middleware should be added earlier than the ErrorMiddleware
5582
* Otherwise exceptions thrown from it will not be handled by the middleware
@@ -64,7 +91,10 @@
6491
* Note: This middleware should be added last. It will not handle any exceptions/errors
6592
* for middleware added after it.
6693
*/
67-
$errorMiddleware = $app->addErrorMiddleware(true, true, true, $logger);
94+
$errorMiddleware = $app->addErrorMiddleware(true, true, true); // TODO gql format detection
95+
$errorMiddleware->setDefaultErrorHandler($graphQlErrorHandler);
96+
//$errorHandler = $errorMiddleware->getDefaultErrorHandler();
97+
//$errorHandler->forceContentType('application/json');
6898

6999
// Demo index route
70100
$app->redirect('/', '/hello', 301);

app/src/Controller/GraphqlController.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,22 @@ public function blogEndpoint(ServerRequestInterface $request, ResponseInterface
2626
{
2727
// $this->container->get('');
2828
return (new Endpoint($response, DebugFlag::INCLUDE_TRACE))
29-
->executeSchema('blog')
29+
->executeSchema([
30+
'lookupDirectories' => [
31+
__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'GraphQL'.DIRECTORY_SEPARATOR.'Schema'.DIRECTORY_SEPARATOR.'blog',
32+
],
33+
'lookupExtensions'=> ['php'],
34+
'isLookupRecursive'=> true,
35+
'lookupExcludePaths' => [
36+
__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'GraphQL'.DIRECTORY_SEPARATOR.'Schema'.DIRECTORY_SEPARATOR.'blog'.DIRECTORY_SEPARATOR.'Data',
37+
],
38+
])
3039
;
3140
}
3241

3342
public function refsEndpoint(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
3443
{
44+
throw new \Exception('Not Implemented Yet');
3545
// your code to access items in the container... $this->container->get('');
3646
return (new Endpoint($response, DebugFlag::INCLUDE_TRACE))
3747
->executeSchema('refs')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?
2+
3+
namespace App\GraphQL\Boilerplate;
4+
5+
use App\GraphQL\Boilerplate\CacheManager;
6+
7+
use App\GraphQL\Exception\PersistedQueryNotSupportedException;
8+
use App\GraphQL\Exception\PersistedQueryNotFoundException;
9+
10+
/**
11+
* Improve network performance by sending smaller requests
12+
*
13+
* With Automatic Persisted Queries, the ID is a deterministic hash
14+
* of the input query, so we don't need a complex build step to share the ID between clients and servers.
15+
* If a server doesn't know about a given hash, the client can expand the query for it;
16+
* The server caches that mapping.
17+
*
18+
* @see https://www.apollographql.com/docs/apollo-server/performance/apq/
19+
*/
20+
class AutomaticPersistedQueries
21+
{
22+
23+
protected static $extensionName = 'persistedQuery';
24+
protected static $cacheKeyPrefix = 'graphQL_APQ';
25+
public $isEnabled;
26+
27+
public function __construct($cacheTTL = 300)
28+
{
29+
$this->isEnabled = true; // @TODO: optionize
30+
}
31+
32+
33+
/**
34+
* @return bool|string Boolean indicates if is cached, string is the gql query
35+
*/
36+
public function onRequestRecieved ($query = null, $extensions = null, $variables = null)
37+
{
38+
if (!$this->hasCurrentExtension($extensions)) {
39+
return $query;
40+
}
41+
elseif (!$this->isEnabled) {
42+
throw new PersistedQueryNotSupportedException();
43+
}
44+
45+
$queryHash = $this->getQueryHash($extensions);
46+
if ($queryHash === null) {
47+
return $query;
48+
}
49+
50+
if ($query) {
51+
// A GQL Query is available
52+
return $this->setQueryCacheByHash($queryHash, $query);
53+
}
54+
55+
$persistedQuery = $this->lookupQueryCacheByHash($queryHash);
56+
if ($persistedQuery === null) {
57+
throw new PersistedQueryNotFoundException($extensions, $queryHash);
58+
}
59+
return $persistedQuery;
60+
}
61+
62+
private function hasCurrentExtension (array $extensions)
63+
{
64+
return in_array(self::$extensionName, $extensions);
65+
}
66+
67+
private function hasQueryHash (array $extensions)
68+
{
69+
return ($this->hasCurrentExtension($extensions) && isset($extensions[self::$extensionName]['sha256Hash']));
70+
}
71+
72+
private function getQueryHash (array $extensions)
73+
{
74+
if ($this->hasQueryHash($extensions)) {
75+
return $extensions[self::$extensionName]['sha256Hash'];
76+
}
77+
return null;
78+
}
79+
80+
private function generateCacheKey (string $queryHash)
81+
{
82+
return '${self::$cacheKeyPrefix}_${queryHash}';
83+
}
84+
85+
/**
86+
* @param String queryHash
87+
* @param String query (gql)
88+
* @param Boolean force when true, set the cache even if it already exist.
89+
* @return Boolean acknowledgement
90+
*/
91+
private function setQueryCacheByHash (string $queryHash, string $query, bool $force = true)
92+
{
93+
try {
94+
$lookupKey = $this->generateCacheKey($queryHash);
95+
}
96+
catch (\Exception $ex) {
97+
// @TODO: Improve logs on failure
98+
return false;
99+
}
100+
return true;
101+
}
102+
103+
private function lookupQueryCacheByHash (string $queryHash)
104+
{
105+
// @TODO: Improve logs on failure
106+
$lookupKey = $this->generateCacheKey($queryHash);
107+
$cache = CacheManager::getInstance();
108+
$cacheItem = $cache->getItem($lookupKey);
109+
if ($cacheItem->isHit()) {
110+
// lookupKey item exists in the cache
111+
return $cacheItem->get();
112+
}
113+
return null;
114+
}
115+
}

0 commit comments

Comments
 (0)