Predis::set()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.0187

Importance

Changes 0
Metric Value
cc 5
eloc 10
nc 4
nop 3
dl 0
loc 18
ccs 10
cts 11
cp 0.9091
crap 5.0187
rs 9.6111
c 0
b 0
f 0
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) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
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
introduced by
Method Desarrolla2\Cache\Predis::getMultiple() should return iterable but returns false.
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $values of array_combine() does only seem to accept array, 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

149
        $packedItems = array_combine(array_values($idKeyPairs), /** @scrutinizer ignore-type */ $response);
Loading history...
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