Completed
Push — 2.1 ( 28b26f...4d9204 )
by Alexander
10:53
created

SimpleCache::getMultiple()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7.0178

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 13
cts 14
cp 0.9286
rs 7.551
c 0
b 0
f 0
cc 7
eloc 15
nc 10
nop 2
crap 7.0178
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\caching;
9
10
use Psr\SimpleCache\CacheInterface;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, yii\caching\CacheInterface.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
11
use yii\base\Component;
12
use yii\helpers\StringHelper;
13
14
/**
15
 * SimpleCache is the base class for cache classes implementing pure PSR-16 [[CacheInterface]].
16
 * This class handles cache key normalization, default TTL specification normalization, data serialization.
17
 *
18
 * Derived classes should implement the following methods which do the actual cache storage operations:
19
 *
20
 * - [[getValue()]]: retrieve the value with a key (if any) from cache
21
 * - [[setValue()]]: store the value with a key into cache
22
 * - [[deleteValue()]]: delete the value with the specified key from cache
23
 * - [[clear()]]: delete all values from cache
24
 *
25
 * For more details and usage information on Cache, see the [guide article on caching](guide:caching-overview)
26
 * and [PSR-16 specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md).
27
 *
28
 * @see CacheInterface
29
 *
30
 * @author Paul Klimov <[email protected]>
31
 * @since 2.1.0
32
 */
33
abstract class SimpleCache extends Component implements CacheInterface
34
{
35
    /**
36
     * @var int default TTL for a cache entry. Default value is 0, meaning infinity.
37
     * This value is used by [[set()]] and [[setMultiple()]], if the duration is not explicitly given.
38
     */
39
    public $defaultTtl = 0;
40
    /**
41
     * @var string a string prefixed to every cache key so that it is unique globally in the whole cache storage.
42
     * It is recommended that you set a unique cache key prefix for each application if the same cache
43
     * storage is being used by different applications.
44
     *
45
     * To ensure interoperability, only alphanumeric characters should be used.
46
     */
47
    public $keyPrefix = '';
48
    /**
49
     * @var null|array|false the functions used to serialize and unserialize cached data. Defaults to null, meaning
50
     * using the default PHP `serialize()` and `unserialize()` functions. If you want to use some more efficient
51
     * serializer (e.g. [igbinary](http://pecl.php.net/package/igbinary)), you may configure this property with
52
     * a two-element array. The first element specifies the serialization function, and the second the deserialization
53
     * function. If this property is set false, data will be directly sent to and retrieved from the underlying
54
     * cache component without any serialization or deserialization. You should not turn off serialization if
55
     * you are using [[Dependency|cache dependency]], because it relies on data serialization. Also, some
56
     * implementations of the cache can not correctly save and retrieve data different from a string type.
57
     */
58
    public $serializer;
59
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 109
    public function get($key, $default = null)
65
    {
66 109
        $key = $this->normalizeKey($key);
67 109
        $value = $this->getValue($key);
68 109
        if ($value === false || $this->serializer === false) {
69 72
            return $default;
70 88
        } elseif ($this->serializer === null) {
71 88
            return unserialize($value);
72
        }
73
74
        return call_user_func($this->serializer[1], $value);
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80 16
    public function getMultiple($keys, $default = null)
81
    {
82 16
        $keyMap = [];
83 16
        foreach ($keys as $key) {
84 16
            $keyMap[$key] = $this->normalizeKey($key);
85
        }
86 16
        $values = $this->getValues(array_values($keyMap));
87 16
        $results = [];
88 16
        foreach ($keyMap as $key => $newKey) {
89 16
            $results[$key] = $default;
90 16
            if (isset($values[$newKey]) && $values[$newKey] !== false) {
91 16
                if ($this->serializer === false) {
92
                    $results[$key] = $values[$newKey];
93
                } else {
94 16
                    $results[$key] = $this->serializer === null ? unserialize($values[$newKey])
95 16
                        : call_user_func($this->serializer[1], $values[$newKey]);
96
                }
97
            }
98
        }
99 16
        return $results;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $results; (array) is incompatible with the return type declared by the interface Psr\SimpleCache\CacheInterface::getMultiple of type Psr\SimpleCache\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105 2
    public function has($key)
106
    {
107 2
        $key = $this->normalizeKey($key);
108 2
        $value = $this->getValue($key);
109 2
        return $value !== false;
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 103
    public function set($key, $value, $ttl = null)
116
    {
117 103
        if ($this->serializer === null) {
118 103
            $value = serialize($value);
119
        } elseif ($this->serializer !== false) {
120
            $value = call_user_func($this->serializer[0], $value);
121
        }
122 103
        $key = $this->normalizeKey($key);
123 103
        $ttl = $this->normalizeTtl($ttl);
124 103
        return $this->setValue($key, $value, $ttl);
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130 21
    public function setMultiple($values, $ttl = null)
131
    {
132 21
        $data = [];
133 21
        foreach ($values as $key => $value) {
134 21
            if ($this->serializer === null) {
135 21
                $value = serialize($value);
136
            } elseif ($this->serializer !== false) {
137
                $value = call_user_func($this->serializer[0], $value);
138
            }
139 21
            $key = $this->normalizeKey($key);
140 21
            $data[$key] = $value;
141
        }
142 21
        return $this->setValues($data, $this->normalizeTtl($ttl));
0 ignored issues
show
Bug introduced by
It seems like $ttl defined by parameter $ttl on line 130 can also be of type null; however, yii\caching\SimpleCache::normalizeTtl() does only seem to accept integer|object<DateInterval>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 93
    public function delete($key)
149
    {
150 93
        $key = $this->normalizeKey($key);
151 93
        return $this->deleteValue($key);
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function deleteMultiple($keys)
158
    {
159
        $result = true;
160
        foreach ($keys as $key) {
161
            if (!$this->delete($key)) {
162
                $result = false;
163
            }
164
        }
165
        return $result;
166
    }
167
168
    /**
169
     * Builds a normalized cache key from a given key.
170
     *
171
     * The given key will be type-casted to string.
172
     * If the result string does not contain alphanumeric characters only or has more than 32 characters,
173
     * then the hash of the key will be used.
174
     * The result key will be returned back prefixed with [[keyPrefix]].
175
     *
176
     * @param mixed $key the key to be normalized
177
     * @return string the generated cache key
178
     */
179 202
    protected function normalizeKey($key)
180
    {
181 202
        $key = (string)$key;
182 202
        $key = ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key);
183 202
        return $this->keyPrefix . $key;
184
    }
185
186
    /**
187
     * Normalizes cache TTL handling `null` value and [[\DateInterval]] objects.
188
     * @param int|\DateInterval $ttl raw TTL.
189
     * @return int TTL value as UNIX timestamp.
190
     */
191 119
    protected function normalizeTtl($ttl)
192
    {
193 119
        if ($ttl === null) {
194 69
            return $this->defaultTtl;
195
        }
196 60
        if ($ttl instanceof \DateInterval) {
197 2
            return (new \DateTime('@0'))->add($ttl)->getTimestamp();
198
        }
199 58
        return (int)$ttl;
200
    }
201
202
    /**
203
     * Retrieves a value from cache with a specified key.
204
     * This method should be implemented by child classes to retrieve the data
205
     * from specific cache storage.
206
     * @param string $key a unique key identifying the cached value
207
     * @return mixed|false the value stored in cache, `false` if the value is not in the cache or expired. Most often
208
     * value is a string. If you have disabled [[serializer]], it could be something else.
209
     */
210
    abstract protected function getValue($key);
211
212
    /**
213
     * Stores a value identified by a key in cache.
214
     * This method should be implemented by child classes to store the data
215
     * in specific cache storage.
216
     * @param string $key the key identifying the value to be cached
217
     * @param mixed $value the value to be cached. Most often it's a string. If you have disabled [[serializer]],
218
     * it could be something else.
219
     * @param int $ttl the number of seconds in which the cached value will expire.
220
     * @return bool true if the value is successfully stored into cache, false otherwise
221
     */
222
    abstract protected function setValue($key, $value, $ttl);
223
224
    /**
225
     * Deletes a value with the specified key from cache
226
     * This method should be implemented by child classes to delete the data from actual cache storage.
227
     * @param string $key the key of the value to be deleted
228
     * @return bool if no error happens during deletion
229
     */
230
    abstract protected function deleteValue($key);
231
232
    /**
233
     * Retrieves multiple values from cache with the specified keys.
234
     * The default implementation calls [[getValue()]] multiple times to retrieve
235
     * the cached values one by one. If the underlying cache storage supports multiget,
236
     * this method should be overridden to exploit that feature.
237
     * @param array $keys a list of keys identifying the cached values
238
     * @return array a list of cached values indexed by the keys
239
     */
240 7
    protected function getValues($keys)
241
    {
242 7
        $results = [];
243 7
        foreach ($keys as $key) {
244 7
            $value = $this->getValue($key);
245 7
            if ($value !== false) {
246 7
                $results[$key] = $value;
247
            }
248
        }
249 7
        return $results;
250
    }
251
252
    /**
253
     * Stores multiple key-value pairs in cache.
254
     * The default implementation calls [[setValue()]] multiple times store values one by one. If the underlying cache
255
     * storage supports multi-set, this method should be overridden to exploit that feature.
256
     * @param array $values array where key corresponds to cache key while value is the value stored
257
     * @param int $ttl the number of seconds in which the cached values will expire.
258
     * @return bool `true` on success and `false` on failure.
259
     */
260 13
    protected function setValues($values, $ttl)
261
    {
262 13
        $result = true;
263 13
        foreach ($values as $key => $value) {
264 13
            if ($this->setValue($key, $value, $ttl) === false) {
265 13
                $result = false;
266
            }
267
        }
268 13
        return $result;
269
    }
270
}