Passed
Push — master ( 5a87d9...5af04f )
by Mihail
03:04
created

CacheClientFactory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Caching\Client;
14
15
use Error;
16
use Exception;
17
use Koded\Caching\{Cache, CacheException};
18
use Koded\Caching\Configuration\{MemcachedConfiguration, PredisConfiguration, RedisConfiguration};
19
use Koded\Stdlib\Interfaces\{Configuration, ConfigurationFactory, Serializer};
20
use Koded\Stdlib\Serializer\SerializerFactory;
21
use Psr\Log\{LoggerInterface, NullLogger};
22
23
24
final class CacheClientFactory
25
{
26
    const CACHE_CLIENT = 'CACHE_CLIENT';
27
28
    private $conf;
29
30 1569
    public function __construct(ConfigurationFactory $conf)
31
    {
32 1569
        $this->conf = $conf;
33 1569
    }
34
35
    /**
36
     * Create an instance of specific cache client.
37
     *
38
     * @param string $client The required cache client
39
     *
40
     * @return Cache An instance of the cache client
41
     * @throws CacheException
42
     * @throws Exception
43
     */
44 1569
    public function new(string $client = ''): Cache
45
    {
46 1569
        $client = strtolower($client ?: getenv(self::CACHE_CLIENT) ?: 'memory');
47 1569
        $config = $this->conf->build($client);
48
49 1569
        switch ($client) {
50 1569
            case 'memcached':
51
                /** @var MemcachedConfiguration $config */
52 197
                return $this->createMemcachedClient($config);
53
54 1372
            case 'redis':
55
                /** @var RedisConfiguration $config */
56 407
                return $this->createRedisClient($config);
57
58 965
            case 'predis':
59
                /** @var PredisConfiguration $config */
60 387
                return $this->createPredisClient($config);
61
62 578
            case 'shmop':
63 187
                return new ShmopClient((string)$config->get('dir'), $config->get('ttl'));
64
65 391
            case 'file':
66 189
                return new FileClient($this->getLogger($config), (string)$config->get('dir'), $config->get('ttl'));
67
68 202
            case 'memory':
69 201
                return new MemoryClient($config->get('ttl'));
70
        }
71
72 1
        throw CacheException::forUnsupportedClient($client);
73
    }
74
75
76 407
    private function createRedisClient(RedisConfiguration $conf): Cache
77
    {
78 407
        $serializer = $conf->get('serializer');
79 407
        $binary = $conf->get('binary');
80
81 407
        if (Serializer::JSON === $serializer && $binary) {
82 197
            return new RedisJsonClient(
83 197
                $this->newRedis($conf),
84 196
                SerializerFactory::new((string)$binary, $conf->get('options')),
85 196
                (int)$conf->get('options'),
86 196
                $conf->get('ttl')
87
            );
88
        }
89
90 210
        return new RedisClient(
91 210
            $this->newRedis($conf),
92 209
            SerializerFactory::new($serializer, $conf->get('options')),
93 208
            $conf->get('ttl')
94
        );
95
    }
96
97
98 387
    private function createPredisClient(PredisConfiguration $conf): Cache
99
    {
100 387
        $serializer = $conf->get('serializer');
101 387
        $binary = $conf->get('binary');
102
103 387
        if (Serializer::JSON === $serializer && $binary) {
104 197
            return new PredisJsonClient(
105 197
                $this->newPredis($conf),
106 197
                SerializerFactory::new((string)$binary, $conf->get('options')),
107 197
                (int)$conf->get('options'),
108 197
                $conf->get('ttl')
109
            );
110
        }
111
112 190
        return new PredisClient(
113 190
            $this->newPredis($conf),
114 188
            SerializerFactory::new($conf->get('serializer'), $conf->get('options')),
0 ignored issues
show
Bug introduced by
It seems like $conf->get('serializer') can also be of type null; however, parameter $name of Koded\Stdlib\Serializer\SerializerFactory::new() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

114
            SerializerFactory::new(/** @scrutinizer ignore-type */ $conf->get('serializer'), $conf->get('options')),
Loading history...
115 188
            $conf->get('ttl')
116
        );
117
    }
118
119
120 407
    private function newRedis(RedisConfiguration $conf): \Redis
121
    {
122 407
        $client = new \Redis;
123
124
        try {
125 407
            @$client->connect(...$conf->getConnectionParams());
0 ignored issues
show
Bug introduced by
$conf->getConnectionParams() is expanded, but the parameter $host of Redis::connect() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

125
            @$client->connect(/** @scrutinizer ignore-type */ ...$conf->getConnectionParams());
Loading history...
Security Best Practice 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 ignore-unhandled  annotation

125
            /** @scrutinizer ignore-unhandled */ @$client->connect(...$conf->getConnectionParams());

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.');
}
Loading history...
126
127 406
            $client->setOption(\Redis::OPT_SERIALIZER, $conf->get('type'));
128 406
            $client->setOption(\Redis::OPT_PREFIX, $conf->get('prefix'));
129 405
            $client->select((int)$conf->get('db'));
130
131 405
            if ($auth = $conf->get('auth')) {
132 405
                $client->auth($auth);
133
            }
134
135
        } /** @noinspection PhpRedundantCatchClauseInspection */
136 2
        catch (\RedisException $e) {
137 1
            if (!strpos($e->getMessage(), ' AUTH ')) {
138 1
                error_log(sprintf(PHP_EOL . '[Redis] %s: %s', get_class($e), $e->getMessage()));
139 1
                error_log('[Redis] with conf: ' . $conf->toJSON());
140 1
                throw CacheException::withConnectionErrorFor('Redis');
141
            }
142 1
        } catch (Exception | Error $e) {
143 1
            throw CacheException::from($e);
144
        }
145
146 405
        return $client;
147
    }
148
149
150 387
    private function newPredis(PredisConfiguration $conf): \Predis\Client
151
    {
152 387
        $client = new \Predis\Client($conf->getConnectionParams(), $conf->getOptions());
153
154
        try {
155 387
            $client->connect();
156 386
            $client->select((int)$conf->get('db'));
157
158 386
            if ($auth = $conf->get('auth')) {
159 386
                $client->auth($auth);
160
            }
161
162
        } /** @noinspection PhpRedundantCatchClauseInspection */
163 2
        catch (\Predis\Connection\ConnectionException $e) {
164 1
            if (!strpos($e->getMessage(), ' AUTH ')) {
165 1
                error_log(sprintf(PHP_EOL . '[Predis] %s: %s', get_class($e), $e->getMessage()));
166 1
                error_log('[Predis] with conf: ' . $conf->toJSON());
167 1
                throw CacheException::withConnectionErrorFor('Predis');
168
            }
169 1
        } catch (Exception $e) {
170 1
            throw CacheException::from($e);
171
        }
172
173 385
        return $client;
174
    }
175
176
177 197
    private function createMemcachedClient(MemcachedConfiguration $conf): Cache
178
    {
179 197
        $client = new \Memcached($conf->get('id'));
180 197
        $client->setOptions($conf->getOptions());
181
182 197
        if (empty($client->getServerList())) {
183 197
            $client->addServers($conf->getServers());
184
        }
185
186 197
        return new MemcachedClient($client, $conf->getTtl());
187
    }
188
189
    /**
190
     * @param Configuration $conf
191
     *
192
     * @return \Psr\Log\LoggerInterface
193
     */
194 189
    private function getLogger($conf): LoggerInterface
195
    {
196 189
        $logger = $conf->logger ?? new NullLogger;
0 ignored issues
show
Bug introduced by
Accessing logger on the interface Koded\Stdlib\Interfaces\Configuration suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
197
198 189
        if ($logger instanceof LoggerInterface) {
199 188
            return $logger;
200
        }
201
202 1
        throw CacheException::forUnsupportedLogger(LoggerInterface::class, get_class($logger));
203
    }
204
}
205