Issues (27)

src/Predis.php (1 issue)

1
<?php
2
3
/*
4
 * This file is part of the Cache package.
5
 *
6
 * Copyright (c) Daniel González
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @author Daniel González <[email protected]>
12
 * @author Arnold Daniels <[email protected]>
13
 */
14
15
declare(strict_types=1);
16
17
namespace Desarrolla2\Cache;
18
19
use Desarrolla2\Cache\AbstractCache;
20
use Desarrolla2\Cache\Exception\UnexpectedValueException;
21
use Desarrolla2\Cache\Packer\PackerInterface;
22
use Desarrolla2\Cache\Packer\SerializePacker;
23
use Predis\Client;
24
use Predis\Response\ServerException;
25
use Predis\Response\Status;
26
use Predis\Response\ErrorInterface;
27
28
/**
29
 * Predis cache adapter.
30
 *
31
 * Errors are silently ignored but ServerExceptions are **not** caught. To PSR-16 compliant disable the `exception`
32
 * option.
33
 */
34
class Predis extends AbstractCache
35
{
36
    /**
37
     * @var Client
38
     */
39
    protected $predis;
40
41
    /**
42
     * Class constructor
43
     * @see predis documentation about how know your configuration https://github.com/nrk/predis
44
     *
45
     * @param Client $client
46
     */
47 198
    public function __construct(Client $client)
48
    {
49 198
        $this->predis = $client;
50
    }
51
52
    /**
53
     * Create the default packer for this cache implementation.
54
     *
55
     * @return PackerInterface
56
     */
57 81
    protected static function createDefaultPacker(): PackerInterface
58
    {
59 81
        return new SerializePacker();
60
    }
61
62
63
    /**
64
     * Run a predis command.
65
     *
66
     * @param string $cmd
67
     * @param mixed ...$args
68
     * @return mixed|bool
69
     */
70 198
    protected function execCommand(string $cmd, ...$args)
71
    {
72 198
        $command = $this->predis->createCommand($cmd, $args);
73 198
        $response = $this->predis->executeCommand($command);
74
75 198
        if ($response instanceof ErrorInterface) {
76
            return false;
77
        }
78
79 198
        if ($response instanceof Status) {
80 198
            return $response->getPayload() === 'OK';
81
        }
82
83 45
        return $response;
84
    }
85
86
    /**
87
     * Set multiple (mset) with expire
88
     *
89
     * @param array    $dictionary
90
     * @param int|null $ttlSeconds
91
     * @return bool
92
     */
93 13
    protected function msetExpire(array $dictionary, ?int $ttlSeconds): bool
94
    {
95 13
        if (empty($dictionary)) {
96
            return true;
97
        }
98
99 13
        if (!isset($ttlSeconds)) {
100 12
            return $this->execCommand('MSET', $dictionary);
101
        }
102
103 1
        $transaction = $this->predis->transaction();
104
105 1
        foreach ($dictionary as $key => $value) {
106 1
            $transaction->set($key, $value, 'EX', $ttlSeconds);
107
        }
108
109
        try {
110 1
            $responses = $transaction->execute();
111
        } catch (ServerException $e) {
112
            return false;
113
        }
114
115 1
        $ok = array_reduce($responses, function($ok, $response) {
116 1
            return $ok && $response instanceof Status && $response->getPayload() === 'OK';
117
        }, true);
118
119 1
        return $ok;
120
    }
121
122
123
    /**
124
     * {@inheritdoc}
125
     */
126 52
    public function get($key, $default = null)
127
    {
128 52
        $id = $this->keyToId($key);
129 34
        $response = $this->execCommand('GET', $id);
130
131 34
        return !empty($response) ? $this->unpack($response) : $default;
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 30
    public function getMultiple($keys, $default = null)
138
    {
139 30
        $idKeyPairs = $this->mapKeysToIds($keys);
140 11
        $ids = array_keys($idKeyPairs);
141
142 11
        $response = $this->execCommand('MGET', $ids);
143
144 11
        if ($response === false) {
145
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by Psr\SimpleCache\CacheInterface::getMultiple() of iterable.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
146
        }
147
148 11
        $items = [];
149 11
        $packedItems = array_combine(array_values($idKeyPairs), $response);
150
151 11
        foreach ($packedItems as $key => $packed) {
152 11
            $items[$key] = isset($packed) ? $this->unpack($packed) : $default;
153
        }
154
155 11
        return $items;
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 22
    public function has($key)
162
    {
163 22
        return $this->execCommand('EXISTS', $this->keyToId($key));
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169 58
    public function set($key, $value, $ttl = null)
170
    {
171 58
        $id = $this->keyToId($key);
172 40
        $packed = $this->pack($value);
173
174 40
        if (!is_string($packed)) {
175
            throw new UnexpectedValueException("Packer must create a string for the data");
176
        }
177
178 40
        $ttlSeconds = $this->ttlToSeconds($ttl);
179
180 30
        if (isset($ttlSeconds) && $ttlSeconds <= 0) {
181 1
            return $this->execCommand('DEL', [$id]);
182
        }
183
184 30
        return !isset($ttlSeconds)
185 29
            ? $this->execCommand('SET', $id, $packed)
186 30
            : $this->execCommand('SETEX', $id, $ttlSeconds, $packed);
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192 42
    public function setMultiple($values, $ttl = null)
193
    {
194 42
        $this->assertIterable($values, 'values not iterable');
195
196 41
        $dictionary = [];
197
198 41
        foreach ($values as $key => $value) {
199 41
            $id = $this->keyToId(is_int($key) ? (string)$key : $key);
200 41
            $packed = $this->pack($value);
201
202 41
            if (!is_string($packed)) {
203
                throw new UnexpectedValueException("Packer must create a string for the data");
204
            }
205
206 41
            $dictionary[$id] = $packed;
207
        }
208
209 24
        $ttlSeconds = $this->ttlToSeconds($ttl);
210
211 14
        if (isset($ttlSeconds) && $ttlSeconds <= 0) {
212 1
            return $this->execCommand('DEL', array_keys($dictionary));
213
        }
214
215 13
        return $this->msetExpire($dictionary, $ttlSeconds);
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221 20
    public function delete($key)
222
    {
223 20
        $id = $this->keyToId($key);
224
225 2
        return $this->execCommand('DEL', [$id]) !== false;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231 21
    public function deleteMultiple($keys)
232
    {
233 21
        $ids = array_keys($this->mapKeysToIds($keys));
234
235 2
        return empty($ids) || $this->execCommand('DEL', $ids) !== false;
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241 198
    public function clear()
242
    {
243 198
        return $this->execCommand('FLUSHDB');
244
    }
245
}
246