1 | <?php |
||||
2 | /* |
||||
3 | * This file is part of the Koded package. |
||||
4 | * |
||||
5 | * (c) Mihail Binev <[email protected]> |
||||
6 | * |
||||
7 | * Please view the LICENSE distributed with this source code |
||||
8 | * for the full copyright and license information. |
||||
9 | */ |
||||
10 | |||||
11 | namespace Koded\Caching\Client; |
||||
12 | |||||
13 | use Exception; |
||||
14 | use Koded\Caching\{Cache, CacheException}; |
||||
15 | use Memcached; |
||||
16 | use Koded\Caching\Configuration\{MemcachedConfiguration, PredisConfiguration, RedisConfiguration}; |
||||
17 | use Koded\Stdlib\{Configuration, Serializer}; |
||||
18 | use Koded\Stdlib\Serializer\SerializerFactory; |
||||
19 | use Psr\Log\{LoggerInterface, NullLogger}; |
||||
20 | use Redis; |
||||
21 | use Throwable; |
||||
22 | use function error_log; |
||||
23 | use function getenv; |
||||
24 | use function sprintf; |
||||
25 | use function strtolower; |
||||
26 | |||||
27 | final class ClientFactory |
||||
28 | { |
||||
29 | public const CACHE_CLIENT = 'CACHE_CLIENT'; |
||||
30 | |||||
31 | public function __construct(private Configuration $factory) {} |
||||
32 | |||||
33 | /** |
||||
34 | * Create an instance of specific cache client. |
||||
35 | * |
||||
36 | * @param string $client The required cache client |
||||
37 | * (memcached, redis, predis, shmop, file, memory) |
||||
38 | * |
||||
39 | * @return Cache An instance of the cache client |
||||
40 | * @throws CacheException |
||||
41 | * @throws Exception |
||||
42 | */ |
||||
43 | 560 | public function new(string $client = ''): Cache |
|||
44 | { |
||||
45 | 560 | $client = strtolower($client ?: getenv(self::CACHE_CLIENT) ?: 'memory'); |
|||
46 | 560 | $config = $this->factory->build($client); |
|||
47 | |||||
48 | 560 | return match ($client) { |
|||
49 | 560 | 'memory' => new MemoryClient($config->get('ttl')), |
|||
50 | 560 | 'memcached' => $this->createMemcachedClient($config), |
|||
51 | 560 | 'redis' => $this->createRedisClient($config), |
|||
52 | 560 | 'predis' => $this->createPredisClient($config), |
|||
53 | 560 | 'shmop' => new ShmopClient((string)$config->get('dir'), $config->get('ttl')), |
|||
54 | 560 | 'file' => new FileClient($this->getLogger($config), (string)$config->get('dir'), $config->get('ttl')), |
|||
55 | 560 | default => throw CacheException::forUnsupportedClient($client) |
|||
56 | 560 | }; |
|||
57 | } |
||||
58 | |||||
59 | 70 | private function createMemcachedClient(MemcachedConfiguration|Configuration $conf): Cache |
|||
60 | { |
||||
61 | 70 | $client = new Memcached($conf->get('id')); |
|||
62 | 70 | $client->setOptions($conf->getOptions()); |
|||
63 | 70 | if (empty($client->getServerList())) { |
|||
64 | 70 | $client->addServers($conf->getServers()); |
|||
65 | } |
||||
66 | 70 | return new MemcachedClient($client, $conf->getTtl()); |
|||
67 | } |
||||
68 | |||||
69 | 155 | private function createRedisClient(RedisConfiguration|Configuration $conf): Cache |
|||
70 | { |
||||
71 | 155 | $serializer = $conf->get('serializer'); |
|||
72 | 155 | $binary = $conf->get('binary'); |
|||
73 | 155 | if (Serializer::JSON === $serializer && $binary) { |
|||
74 | 71 | return new RedisJsonClient( |
|||
75 | 71 | $this->newRedisClient($conf), |
|||
76 | 71 | SerializerFactory::new((string)$binary, ...$conf->get('options', [0])), |
|||
77 | 71 | (int)$conf->get('options'), |
|||
78 | 71 | $conf->get('ttl') |
|||
79 | 71 | ); |
|||
80 | } |
||||
81 | 84 | return new RedisClient( |
|||
82 | 84 | $this->newRedisClient($conf), |
|||
83 | 84 | SerializerFactory::new($serializer, ...$conf->get('options', [0])), |
|||
84 | 84 | $conf->get('ttl') |
|||
85 | 84 | ); |
|||
86 | } |
||||
87 | |||||
88 | 135 | private function createPredisClient(PredisConfiguration|Configuration $conf): Cache |
|||
89 | { |
||||
90 | 135 | $binary = $conf->get('binary'); |
|||
91 | 135 | if (Serializer::JSON === $conf->get('serializer') && $binary) { |
|||
92 | 71 | return new PredisJsonClient( |
|||
93 | 71 | $this->newPredisClient($conf), |
|||
94 | 71 | SerializerFactory::new((string)$binary, ...$conf->get('options', [0])), |
|||
95 | 71 | (int)$conf->get('options'), |
|||
96 | 71 | $conf->get('ttl') |
|||
97 | 71 | ); |
|||
98 | } |
||||
99 | 64 | return new PredisClient( |
|||
100 | 64 | $this->newPredisClient($conf), |
|||
101 | 64 | SerializerFactory::new($conf->get('serializer'), ...$conf->get('options', [0])), |
|||
102 | 64 | $conf->get('ttl') |
|||
103 | 64 | ); |
|||
104 | } |
||||
105 | |||||
106 | 155 | private function newRedisClient(RedisConfiguration $conf): Redis |
|||
107 | { |
||||
108 | 155 | $client = new Redis; |
|||
109 | try { |
||||
110 | 155 | @$client->connect(...$conf->getConnectionParams()); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() It seems like you do not handle an error condition for
connect() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||
111 | 154 | $client->setOption(Redis::OPT_SERIALIZER, $conf->get('type')); |
|||
112 | 154 | $client->setOption(Redis::OPT_PREFIX, $conf->get('prefix')); |
|||
113 | 153 | $client->select((int)$conf->get('db')); |
|||
114 | 153 | if ($auth = $conf->get('auth')) { |
|||
115 | 1 | $client->auth($auth); |
|||
116 | } |
||||
117 | 152 | if ($message = $client->getLastError()) { |
|||
118 | // [NOTE] Redis module complains if auth is set, |
||||
119 | // but <Redis v5 or less> does not have auth |
||||
120 | throw new Exception($message); |
||||
121 | } |
||||
122 | 152 | return $client; |
|||
123 | |||||
124 | } /** @noinspection PhpRedundantCatchClauseInspection */ |
||||
125 | 3 | catch (Throwable $e) { |
|||
126 | 3 | error_log(sprintf(PHP_EOL . '[Redis] %s: %s', $e::class, $e->getMessage())); |
|||
127 | 3 | error_log('[Redis] with conf: ' . $conf->delete('auth')->toJSON()); |
|||
128 | 3 | throw CacheException::withConnectionErrorFor('Redis'); |
|||
129 | } |
||||
130 | } |
||||
131 | |||||
132 | 135 | private function newPredisClient(PredisConfiguration $conf): \Predis\Client |
|||
133 | { |
||||
134 | 135 | $client = new \Predis\Client($conf->getConnectionParams(), $conf->getOptions()); |
|||
135 | |||||
136 | try { |
||||
137 | 135 | $client->connect(); |
|||
138 | 134 | $client->select((int)$conf->get('db')); |
|||
139 | 134 | if ($auth = $conf->get('auth')) { |
|||
140 | 1 | $client->auth($auth); |
|||
141 | } |
||||
142 | 133 | return $client; |
|||
143 | |||||
144 | } /** @noinspection PhpRedundantCatchClauseInspection */ |
||||
145 | 2 | catch (Throwable $e) { |
|||
146 | 2 | error_log(sprintf(PHP_EOL . '[Predis] %s: %s', $e::class, $e->getMessage())); |
|||
147 | 2 | error_log('[Predis] with conf: ' . $conf->delete('auth')->toJSON()); |
|||
148 | 2 | throw CacheException::withConnectionErrorFor('Predis'); |
|||
149 | } |
||||
150 | } |
||||
151 | |||||
152 | 63 | private function getLogger(Configuration $conf): LoggerInterface |
|||
153 | { |
||||
154 | 63 | $logger = $conf->logger ?? new NullLogger; |
|||
0 ignored issues
–
show
|
|||||
155 | 63 | if ($logger instanceof LoggerInterface) { |
|||
156 | 62 | return $logger; |
|||
157 | } |
||||
158 | 1 | throw CacheException::forUnsupportedLogger(LoggerInterface::class, $logger::class); |
|||
159 | } |
||||
160 | } |
||||
161 |