Passed
Push — master ( c82647...c877fa )
by Georges
11:01
created

Driver   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 68
dl 0
loc 198
rs 10
c 3
b 0
f 0
wmc 22

11 Methods

Rating   Name   Duplication   Size   Complexity  
A driverCheck() 0 7 2
A getHelp() 0 3 1
A driverWrite() 0 14 2
A driverDelete() 0 3 1
B driverConnect() 0 45 6
A driverReadAllKeys() 0 7 2
A driverReadMultiple() 0 7 2
A getStats() 0 17 2
A driverRead() 0 9 2
A driverDeleteMultiple() 0 3 1
A driverClear() 0 3 1
1
<?php
2
3
/**
4
 *
5
 * This file is part of Phpfastcache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files.
10
 *
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Contributors  https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Drivers\Predis;
18
19
use DateTime;
20
use Phpfastcache\Cluster\AggregatablePoolInterface;
21
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
22
use Phpfastcache\Core\Pool\TaggableCacheItemPoolTrait;
23
use Phpfastcache\Core\Item\ExtendedCacheItemInterface;
24
use Phpfastcache\Entities\DriverStatistic;
25
use Phpfastcache\Exceptions\PhpfastcacheDriverException;
26
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException;
27
use Phpfastcache\Exceptions\PhpfastcacheLogicException;
28
use Predis\Client as PredisClient;
29
use Predis\Collection\Iterator as PredisIterator;
30
use Predis\Connection\ConnectionException as PredisConnectionException;
31
32
/**
33
 * @property PredisClient $instance Instance of driver service
34
 * @method Config getConfig()
35
 */
36
class Driver implements AggregatablePoolInterface
37
{
38
    use TaggableCacheItemPoolTrait;
39
40
    /**
41
     * @return bool
42
     */
43
    public function driverCheck(): bool
44
    {
45
        if (extension_loaded('Redis')) {
46
            trigger_error('The native Redis extension is installed, you should use Redis instead of Predis to increase performances', E_USER_NOTICE);
47
        }
48
49
        return class_exists(\Predis\Client::class);
50
    }
51
52
    /**
53
     * @return string
54
     */
55
    public function getHelp(): string
56
    {
57
        return <<<HELP
58
<p>
59
To install the Predis library via Composer:
60
<code>composer require "predis/predis" "~1.1.0"</code>
61
</p>
62
HELP;
63
    }
64
65
    /**
66
     * @return DriverStatistic
67
     */
68
    public function getStats(): DriverStatistic
69
    {
70
        $info = $this->instance->info();
71
        $size = ($info['Memory']['used_memory'] ?? 0);
72
        $version = ($info['Server']['redis_version'] ?? 0);
73
        $date = (isset($info['Server']['uptime_in_seconds']) ? (new DateTime())->setTimestamp(time() - $info['Server']['uptime_in_seconds']) : 'unknown date');
74
75
        return (new DriverStatistic())
76
            ->setData(implode(', ', array_keys($this->itemInstances)))
77
            ->setRawData($info)
78
            ->setSize((int)$size)
79
            ->setInfo(
80
                sprintf(
81
                    "The Redis daemon v%s (with Predis v%s) is up since %s.\n For more information see RawData. \n Driver size includes the memory allocation size.",
82
                    $version,
83
                    PredisClient::VERSION,
84
                    $date->format(DATE_RFC2822)
85
                )
86
            );
87
    }
88
89
    /**
90
     * @return bool
91
     * @throws PhpfastcacheDriverException
92
     * @throws PhpfastcacheLogicException
93
     */
94
    protected function driverConnect(): bool
95
    {
96
        /**
97
         * In case of a user-provided
98
         * Predis client just return here
99
         */
100
        if ($this->getConfig()->getPredisClient() instanceof PredisClient) {
101
            $this->instance = $this->getConfig()->getPredisClient();
102
            if (!$this->instance->isConnected()) {
103
                $this->instance->connect();
104
            }
105
            return true;
106
        }
107
108
        $options = [];
109
110
        if ($this->getConfig()->getOptPrefix()) {
111
            $options['prefix'] = $this->getConfig()->getOptPrefix();
112
        }
113
114
        if (!empty($this->getConfig()->getPath())) {
115
            $this->instance = new PredisClient(
116
                [
117
                    'scheme' => $this->getConfig()->getScheme(),
118
                    'persistent' => $this->getConfig()->isPersistent(),
119
                    'timeout' => $this->getConfig()->getTimeout(),
120
                    'path' => $this->getConfig()->getPath(),
121
                ],
122
                $options
123
            );
124
        } else {
125
            $this->instance = new PredisClient($this->getConfig()->getPredisConfigArray(), $options);
126
        }
127
128
        try {
129
            $this->instance->connect();
130
        } catch (PredisConnectionException $e) {
131
            throw new PhpfastcacheDriverException(
132
                'Failed to connect to predis server. Check the Predis documentation: https://github.com/nrk/predis/tree/v1.1#how-to-install-and-use-predis',
133
                0,
134
                $e
135
            );
136
        }
137
138
        return true;
139
    }
140
141
    /**
142
     * @param ExtendedCacheItemInterface $item
143
     * @return ?array<string, mixed>
144
     */
145
    protected function driverRead(ExtendedCacheItemInterface $item): ?array
146
    {
147
        $val = $this->instance->get($item->getKey());
148
149
        if ($val === null) {
150
            return null;
151
        }
152
153
        return $this->decode($val);
154
    }
155
156
157
    /**
158
     * @param ExtendedCacheItemInterface ...$items
159
     * @return array<array<string, mixed>>
160
     * @throws \Phpfastcache\Exceptions\PhpfastcacheDriverException
161
     * @throws \RedisException
162
     */
163
    protected function driverReadMultiple(ExtendedCacheItemInterface ...$items): array
164
    {
165
        $keys = $this->getKeys($items);
166
167
        return array_combine($keys, array_map(
168
            fn($val) => $val ? $this->decode($val) : null,
169
            $this->instance->mget($keys)
170
        ));
171
    }
172
173
    /**
174
     * @return array<int, string>
175
     * @throws \RedisException
176
     */
177
    protected function driverReadAllKeys(string $pattern = '*'): iterable
178
    {
179
        $keys = [];
180
        foreach (new PredisIterator\Keyspace($this->instance, $pattern, ExtendedCacheItemPoolInterface::MAX_ALL_KEYS_COUNT) as $key) {
181
            $keys[] = $key;
182
        }
183
        return $keys;
184
    }
185
186
187
    /**
188
     * @param ExtendedCacheItemInterface $item
189
     * @return mixed
190
     * @throws PhpfastcacheInvalidArgumentException
191
     * @throws PhpfastcacheLogicException
192
     */
193
    protected function driverWrite(ExtendedCacheItemInterface $item): bool
194
    {
195
196
        $ttl = $item->getExpirationDate()->getTimestamp() - time();
197
198
        /**
199
         * @see https://redis.io/commands/setex
200
         * @see https://redis.io/commands/expire
201
         */
202
        if ($ttl <= 0) {
203
            return (bool)$this->instance->expire($item->getKey(), 0);
204
        }
205
206
        return $this->instance->setex($item->getKey(), $ttl, $this->encode($this->driverPreWrap($item)))->getPayload() === 'OK';
207
    }
208
209
    /**
210
     * @param string $key
211
     * @param string $encodedKey
212
     * @return bool
213
     */
214
    protected function driverDelete(string $key, string $encodedKey): bool
215
    {
216
        return (bool)$this->instance->del([$key]);
217
    }
218
219
    /**
220
     * @param string[] $keys
221
     * @return bool
222
     */
223
    protected function driverDeleteMultiple(array $keys): bool
224
    {
225
        return (bool) $this->instance->del(...$keys);
226
    }
227
228
    /**
229
     * @return bool
230
     */
231
    protected function driverClear(): bool
232
    {
233
        return $this->instance->flushdb()->getPayload() === 'OK';
234
    }
235
}
236