Skip to content

Commit 97f0285

Browse files
committed
WiP on GraphQL Boilerplate, SchemaLoader, CacheManager (redis)
1 parent 173bc1c commit 97f0285

29 files changed

+3832
-96
lines changed

Diff for: .php_cs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
// @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/2.17/doc/config.rst
4+
5+
$finder = PhpCsFixer\Finder::create()
6+
//->exclude('somedir')
7+
//->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php')
8+
->in(__DIR__.'/app')
9+
;
10+
11+
$config = new PhpCsFixer\Config();
12+
return $config->setRules([
13+
'@PSR2' => false,
14+
'@PSR12' => false, // TODO @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/pull/4943#ref-issue-725230599
15+
'@Symfony' => true,
16+
])
17+
->setFinder($finder)
18+
;

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ docker exec -ti gql_slim_api sh
2828

2929
### Endpoints
3030

31-
* `/graphql` The main api endpoint.
32-
* `/graphql/refs` Another api endpoint with another schema
31+
* `/graphql/blog` The 'blog' api endpoint and schema.
32+
* `/graphql/refs` Another 'refs' api endpoint with another schema

Diff for: app/public/index.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@
7474
$nowUTC = new \DateTime('now', new \DateTimeZone('UTC'));
7575
$nowFR = clone($nowUTC);
7676
$nowFR->setTimezone(new \DateTimeZone('Europe/Paris'));
77-
$response->getBody()->write("Hello World !! current DateTime in France(ISO8601): ".$nowFR->format(\DateTimeInterface::ISO8601));
77+
$response->getBody()->write("Hello World !! current DateTime in Europe/Paris(ISO8601): ".$nowFR->format(\DateTimeInterface::ISO8601));
7878
return $response;
7979
});
8080

8181
// Demo graphql endpoints
82-
$app->map(['GET', 'POST'], '/graphql', \App\Controller\GraphqlController::class.':mainEndpoint');
83-
$app->map(['GET', 'POST'], '/graphql/refs', \App\Controller\GraphqlController::class.':refsEndpoint');
82+
$app->map(['GET', 'POST'], '/graphql/blog', \App\Controller\GraphqlController::class.':blogEndpoint');
83+
$app->any('/graphql/refs', \App\Controller\GraphqlController::class.':refsEndpoint');
8484

8585
$app->run();

Diff for: app/src/Controller/GraphqlController.php

+18-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
use Psr\Http\Message\ResponseInterface;
77
use Psr\Http\Message\ServerRequestInterface;
88

9+
use GraphQL\GraphQL;
10+
use GraphQL\Type\Schema;
11+
use GraphQL\Error\DebugFlag;
12+
use GraphQL\Error\FormattedError;
13+
14+
use App\GraphQL\Boilerplate\Endpoint;
15+
916
class GraphqlController
1017
{
1118
private $container;
@@ -14,20 +21,20 @@ public function __construct(ContainerInterface $container)
1421
{
1522
$this->container = $container;
1623
}
17-
18-
public function mainEndpoint(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
24+
25+
public function blogEndpoint(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
1926
{
20-
// your code to access items in the container... $this->container->get('');
21-
22-
23-
return $response;
27+
// $this->container->get('');
28+
return (new Endpoint($response, DebugFlag::INCLUDE_TRACE))
29+
->executeSchema('blog')
30+
;
2431
}
2532

2633
public function refsEndpoint(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
2734
{
28-
// your code to access items in the container... $this->container->get('');
29-
30-
31-
return $response;
35+
// your code to access items in the container... $this->container->get('');
36+
return (new Endpoint($response, DebugFlag::INCLUDE_TRACE))
37+
->executeSchema('refs')
38+
;
3239
}
33-
}
40+
}

Diff for: app/src/GraphQL/Boilerplate/CacheManager.php

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?
2+
3+
namespace App\GraphQL\Boilerplate;
4+
5+
// use Symfony\Component\Cache\Adapter\FilesystemAdapter; // @see https://symfony.com/doc/current/components/cache/adapters/filesystem_adapter.html
6+
use Symfony\Component\Cache\Adapter\AbstractAdapter;
7+
use Symfony\Component\Cache\Adapter\RedisAdapter; // @see https://symfony.com/doc/current/components/cache/adapters/redis_adapter.html
8+
9+
/**
10+
* PSR-6 Compatible Caching Utility
11+
* @see https://www.php-fig.org/psr/psr-6/
12+
* @see https://symfony.com/doc/current/components/cache.html#cache-component-psr6-caching
13+
*
14+
* @usage
15+
*
16+
// With Symfony Contracts...
17+
use App\GraphQL\Boilerplate\CacheManager;
18+
$memoization = CacheManager::getInstance();
19+
$value = $memoization->get('FOO', function (\Symfony\Contracts\Cache\ItemInterface $item) {
20+
$item->expiresAfter(10); //seconds
21+
return 'BAR';
22+
});
23+
24+
* @usage
25+
*
26+
// Without Contracts (pure PSR-6)...
27+
$memoization = CacheManager::getInstance();
28+
$foo = $memoization->getItem('FOO');
29+
if (!$foo->isHit()) {
30+
// foo item does not exist in the cache
31+
$foo->set('BAR')->expiresAfter(10);
32+
$memoization->save($foo);
33+
}
34+
// retrieve the value stored by the item
35+
$value = $foo->get();
36+
*
37+
*/
38+
class CacheManager
39+
{
40+
41+
// protected $instanceId;
42+
protected static $instance;
43+
44+
protected $redisClient = null;
45+
protected $redisAdapter = null;
46+
47+
protected $cacheKeyPrefix = '';
48+
49+
public static function defaultOptions () {
50+
return [
51+
// Enables or disables compression of items. This requires phpredis v4 or higher with LZF support enabled.
52+
'compression' => true,
53+
54+
// Enables or disables lazy connections to the backend. It’s false by default when using this as a stand-alone component and true by default when using it inside a Symfony application.
55+
'lazy' => true,
56+
57+
// Enables or disables use of persistent connections. A value of 0 disables persistent connections, and a value of 1 enables them.
58+
'persistent' => 1,
59+
60+
// Specifies the persistent id string to use for a persistent connection.
61+
'persistent_id' => str_replace('\\', '_', __CLASS__),
62+
63+
// Specifies the TCP-keepalive timeout (in seconds) of the connection. This requires phpredis v4 or higher and a TCP-keepalive enabled server. This option is useful in order to detect dead peers (clients that cannot be reached even if they look connected).
64+
'tcp_keepalive' => 40,
65+
66+
// Specifies the time (in seconds) used to connect to a Redis server before the connection attempt times out.
67+
'timeout' => 20,
68+
69+
// Specifies the time (in seconds) used when performing read operations on the underlying network resource before the operation times out.
70+
'read_timeout' => 5,
71+
72+
// Specifies the delay (in milliseconds) between reconnection attempts in case the client loses connection with the server.
73+
'retry_interval' => 600,
74+
];
75+
}
76+
77+
public function __construct($cacheKeyPrefix = null)
78+
{
79+
// $this->instanceId = uuid_create(UUID_TYPE_RANDOM); // @see https://symfony.com/blog/introducing-the-new-symfony-uuid-polyfill
80+
$this->setCacheKeyPrefix(str_replace('\\', '.', __CLASS__), false);
81+
82+
if ($cacheKeyPrefix) {
83+
$this->setCacheKeyPrefix($cacheKeyPrefix, true);
84+
}
85+
}
86+
87+
public static function getInstance () {
88+
if (!self::$instance) {
89+
self::$instance = new self();
90+
}
91+
return self::$instance;
92+
}
93+
94+
public function adapter () : AbstractAdapter
95+
{
96+
return $this->getRedisAdapter();
97+
}
98+
99+
public function __invoke($invoked) : AbstractAdapter
100+
{
101+
return $this->adapter();
102+
}
103+
104+
public function __call($name, $arguments = [])
105+
{
106+
if (!isset($arguments) || !is_array($arguments)) {
107+
$arguments = [];
108+
}
109+
return call_user_func_array([$this->adapter(), $name], $arguments);
110+
}
111+
112+
public function setCacheKeyPrefix (string $prefix, bool $isAppendMode = true)
113+
{
114+
if ($isAppendMode) {
115+
$this->cacheKeyPrefix .= '--'.$prefix;
116+
}
117+
else {
118+
$this->cacheKeyPrefix = $prefix;
119+
}
120+
return $this;
121+
}
122+
123+
public function setRedisAdapter (RedisAdapter $adapter)
124+
{
125+
if ($this->redisAdapter !== null) {
126+
throw new \Exception('Cannot set redis adapter when already initialized.');
127+
}
128+
$this->redisAdapter = $adapter;
129+
return $this;
130+
}
131+
132+
public function setRedisClient (\Symfony\Component\Cache\Traits\RedisProxy $client)
133+
{
134+
if ($this->redisClient !== null || $this->redisAdapter !== null) {
135+
throw new \Exception('Cannot set redis client when already initialized.');
136+
}
137+
$this->redisClient = $client;
138+
return $this;
139+
}
140+
141+
protected function getRedisClient ($dsn = null, $options = null) : \Symfony\Component\Cache\Traits\RedisProxy
142+
{
143+
// pass a single DSN string to register a single server with the client
144+
if (!isset($dsn)) {
145+
$dsn = 'redis://gql_slim_redis:6379';
146+
}
147+
if ($this->redisClient === null) {
148+
$this->setRedisClient(
149+
RedisAdapter::createConnection(
150+
$dsn,
151+
is_array($options) ? array_merge(self::defaultOptions(), $options) : self::defaultOptions()
152+
)
153+
);
154+
}
155+
return $this->redisClient;
156+
}
157+
158+
protected function getRedisAdapter () : RedisAdapter
159+
{
160+
// pass a single DSN string to register a single server with the client
161+
if ($this->redisAdapter === null) {
162+
$this->redisAdapter = new RedisAdapter(
163+
$this->getRedisClient(),
164+
// Namespace: the string prefixed to the keys of the items stored in this cache
165+
$this->cacheKeyPrefix,
166+
// TTL: the default lifetime (in seconds) for cache items that do not define their
167+
// own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
168+
// until RedisAdapter::clear() is invoked or the server(s) are purged)
169+
3 * 60 * 60 // 3h
170+
);
171+
}
172+
return $this->redisAdapter;
173+
}
174+
175+
}

0 commit comments

Comments
 (0)