Completed
Push — master ( eed00d...bda091 )
by Dmitry
10:44
created

DbCache::exists()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2.0023

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 11
cts 12
cp 0.9167
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 13
nc 2
nop 1
crap 2.0023
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 Yii;
11
use yii\base\InvalidConfigException;
12
use yii\db\Connection;
13
use yii\db\Query;
14
use yii\di\Instance;
15
16
/**
17
 * DbCache implements a cache application component by storing cached data in a database.
18
 *
19
 * By default, DbCache stores session data in a DB table named 'cache'. This table
20
 * must be pre-created. The table name can be changed by setting [[cacheTable]].
21
 *
22
 * Please refer to [[Cache]] for common cache operations that are supported by DbCache.
23
 *
24
 * The following example shows how you can configure the application to use DbCache:
25
 *
26
 * ```php
27
 * 'cache' => [
28
 *     'class' => 'yii\caching\DbCache',
29
 *     // 'db' => 'mydb',
30
 *     // 'cacheTable' => 'my_cache',
31
 * ]
32
 * ```
33
 *
34
 * For more details and usage information on Cache, see the [guide article on caching](guide:caching-overview).
35
 *
36
 * @author Qiang Xue <[email protected]>
37
 * @since 2.0
38
 */
39
class DbCache extends Cache
40
{
41
    /**
42
     * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
43
     * After the DbCache object is created, if you want to change this property, you should only assign it
44
     * with a DB connection object.
45
     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
46
     */
47
    public $db = 'db';
48
    /**
49
     * @var string name of the DB table to store cache content.
50
     * The table should be pre-created as follows:
51
     *
52
     * ```php
53
     * CREATE TABLE cache (
54
     *     id char(128) NOT NULL PRIMARY KEY,
55
     *     expire int(11),
56
     *     data BLOB
57
     * );
58
     * ```
59
     *
60
     * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
61
     * that can be used for some popular DBMS:
62
     *
63
     * - MySQL: LONGBLOB
64
     * - PostgreSQL: BYTEA
65
     * - MSSQL: BLOB
66
     *
67
     * When using DbCache in a production server, we recommend you create a DB index for the 'expire'
68
     * column in the cache table to improve the performance.
69
     */
70
    public $cacheTable = '{{%cache}}';
71
    /**
72
     * @var int the probability (parts per million) that garbage collection (GC) should be performed
73
     * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
74
     * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
75
     */
76
    public $gcProbability = 100;
77
78
79
    /**
80
     * Initializes the DbCache component.
81
     * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
82
     * @throws InvalidConfigException if [[db]] is invalid.
83
     */
84 19
    public function init()
85
    {
86 19
        parent::init();
87 19
        $this->db = Instance::ensure($this->db, Connection::className());
88 19
    }
89
90
    /**
91
     * Checks whether a specified key exists in the cache.
92
     * This can be faster than getting the value from the cache if the data is big.
93
     * Note that this method does not check whether the dependency associated
94
     * with the cached data, if there is any, has changed. So a call to [[get]]
95
     * may return false while exists returns true.
96
     * @param mixed $key a key identifying the cached value. This can be a simple string or
97
     * a complex data structure consisting of factors representing the key.
98
     * @return bool true if a value exists in cache, false if the value is not in the cache or expired.
99
     */
100 1
    public function exists($key)
101
    {
102 1
        $key = $this->buildKey($key);
103
104 1
        $query = new Query;
105 1
        $query->select(['COUNT(*)'])
106 1
            ->from($this->cacheTable)
107 1
            ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]);
108 1
        if ($this->db->enableQueryCache) {
109
            // temporarily disable and re-enable query caching
110 1
            $this->db->enableQueryCache = false;
111 1
            $result = $query->createCommand($this->db)->queryScalar();
112 1
            $this->db->enableQueryCache = true;
113
        } else {
114
            $result = $query->createCommand($this->db)->queryScalar();
115
        }
116
117 1
        return $result > 0;
118
    }
119
120
    /**
121
     * Retrieves a value from cache with a specified key.
122
     * This is the implementation of the method declared in the parent class.
123
     * @param string $key a unique key identifying the cached value
124
     * @return string|false the value stored in cache, false if the value is not in the cache or expired.
125
     */
126 15
    protected function getValue($key)
127
    {
128 15
        $query = new Query;
129 15
        $query->select(['data'])
130 15
            ->from($this->cacheTable)
131 15
            ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]);
132 15
        if ($this->db->enableQueryCache) {
133
            // temporarily disable and re-enable query caching
134 15
            $this->db->enableQueryCache = false;
135 15
            $result = $query->createCommand($this->db)->queryScalar();
136 15
            $this->db->enableQueryCache = true;
137
138 15
            return $result;
139
        } else {
140
            return $query->createCommand($this->db)->queryScalar();
141
        }
142
    }
143
144
    /**
145
     * Retrieves multiple values from cache with the specified keys.
146
     * @param array $keys a list of keys identifying the cached values
147
     * @return array a list of cached values indexed by the keys
148
     */
149 2
    protected function getValues($keys)
150
    {
151 2
        if (empty($keys)) {
152
            return [];
153
        }
154 2
        $query = new Query;
155 2
        $query->select(['id', 'data'])
156 2
            ->from($this->cacheTable)
157 2
            ->where(['id' => $keys])
158 2
            ->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')');
159
160 2
        if ($this->db->enableQueryCache) {
161 2
            $this->db->enableQueryCache = false;
162 2
            $rows = $query->createCommand($this->db)->queryAll();
163 2
            $this->db->enableQueryCache = true;
164
        } else {
165
            $rows = $query->createCommand($this->db)->queryAll();
166
        }
167
168 2
        $results = [];
169 2
        foreach ($keys as $key) {
170 2
            $results[$key] = false;
171
        }
172 2
        foreach ($rows as $row) {
173 2
            $results[$row['id']] = $row['data'];
174
        }
175
176 2
        return $results;
177
    }
178
179
    /**
180
     * Stores a value identified by a key in cache.
181
     * This is the implementation of the method declared in the parent class.
182
     *
183
     * @param string $key the key identifying the value to be cached
184
     * @param string $value the value to be cached. Other types (if you have disabled [[serializer]]) cannot be saved.
185
     * @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
186
     * @return bool true if the value is successfully stored into cache, false otherwise
187
     */
188 15
    protected function setValue($key, $value, $duration)
189
    {
190 15
        $command = $this->db->createCommand()
191 15
            ->update($this->cacheTable, [
192 15
                'expire' => $duration > 0 ? $duration + time() : 0,
193 15
                'data' => [$value, \PDO::PARAM_LOB],
194 15
            ], ['id' => $key]);
195
196 15
        if ($command->execute()) {
197 1
            $this->gc();
198
199 1
            return true;
200
        } else {
201 15
            return $this->addValue($key, $value, $duration);
202
        }
203
    }
204
205
    /**
206
     * Stores a value identified by a key into cache if the cache does not contain this key.
207
     * This is the implementation of the method declared in the parent class.
208
     *
209
     * @param string $key the key identifying the value to be cached
210
     * @param string $value the value to be cached. Other types (if you have disabled [[serializer]]) cannot be saved.
211
     * @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
212
     * @return bool true if the value is successfully stored into cache, false otherwise
213
     */
214 16
    protected function addValue($key, $value, $duration)
215
    {
216 16
        $this->gc();
217
218
        try {
219 16
            $this->db->createCommand()
220 16
                ->insert($this->cacheTable, [
221 16
                    'id' => $key,
222 16
                    'expire' => $duration > 0 ? $duration + time() : 0,
223 16
                    'data' => [$value, \PDO::PARAM_LOB],
224 16
                ])->execute();
225
226 15
            return true;
227 3
        } catch (\Exception $e) {
228 3
            return false;
229
        }
230
    }
231
232
    /**
233
     * Deletes a value with the specified key from cache
234
     * This is the implementation of the method declared in the parent class.
235
     * @param string $key the key of the value to be deleted
236
     * @return bool if no error happens during deletion
237
     */
238 1
    protected function deleteValue($key)
239
    {
240 1
        $this->db->createCommand()
241 1
            ->delete($this->cacheTable, ['id' => $key])
242 1
            ->execute();
243
244 1
        return true;
245
    }
246
247
    /**
248
     * Removes the expired data values.
249
     * @param bool $force whether to enforce the garbage collection regardless of [[gcProbability]].
250
     * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
251
     */
252 16
    public function gc($force = false)
253
    {
254 16
        if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
255
            $this->db->createCommand()
256
                ->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time())
257
                ->execute();
258
        }
259 16
    }
260
261
    /**
262
     * Deletes all values from cache.
263
     * This is the implementation of the method declared in the parent class.
264
     * @return bool whether the flush operation was successful.
265
     */
266 11
    protected function flushValues()
267
    {
268 11
        $this->db->createCommand()
269 11
            ->delete($this->cacheTable)
270 11
            ->execute();
271
272 11
        return true;
273
    }
274
}
275