Completed
Push — fix-unique-exists-expressions ( b0719b...8467f4 )
by Alexander
07:52
created

DbCache::deleteValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 0
cts 6
cp 0
rs 9.4285
cc 1
eloc 6
nc 1
nop 1
crap 2
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 1
    public function init()
85
    {
86 1
        parent::init();
87 1
        $this->db = Instance::ensure($this->db, Connection::className());
88 1
    }
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
    public function exists($key)
101
    {
102
        $key = $this->buildKey($key);
103
104
        $query = new Query();
105
        $query->select(['COUNT(*)'])
106
            ->from($this->cacheTable)
107
            ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]);
108
        if ($this->db->enableQueryCache) {
109
            // temporarily disable and re-enable query caching
110
            $this->db->enableQueryCache = false;
111
            $result = $query->createCommand($this->db)->queryScalar();
112
            $this->db->enableQueryCache = true;
113
        } else {
114
            $result = $query->createCommand($this->db)->queryScalar();
115
        }
116
117
        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
    protected function getValue($key)
127
    {
128
        $query = new Query();
129
        $query->select(['data'])
130
            ->from($this->cacheTable)
131
            ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]);
132
        if ($this->db->enableQueryCache) {
133
            // temporarily disable and re-enable query caching
134
            $this->db->enableQueryCache = false;
135
            $result = $query->createCommand($this->db)->queryScalar();
136
            $this->db->enableQueryCache = true;
137
138
            return $result;
139
        }
140
        return $query->createCommand($this->db)->queryScalar();
141
    }
142
143
    /**
144
     * Retrieves multiple values from cache with the specified keys.
145
     * @param array $keys a list of keys identifying the cached values
146
     * @return array a list of cached values indexed by the keys
147
     */
148
    protected function getValues($keys)
149
    {
150
        if (empty($keys)) {
151
            return [];
152
        }
153
        $query = new Query();
154
        $query->select(['id', 'data'])
155
            ->from($this->cacheTable)
156
            ->where(['id' => $keys])
157
            ->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')');
158
159
        if ($this->db->enableQueryCache) {
160
            $this->db->enableQueryCache = false;
161
            $rows = $query->createCommand($this->db)->queryAll();
162
            $this->db->enableQueryCache = true;
163
        } else {
164
            $rows = $query->createCommand($this->db)->queryAll();
165
        }
166
167
        $results = [];
168
        foreach ($keys as $key) {
169
            $results[$key] = false;
170
        }
171
        foreach ($rows as $row) {
172
            $results[$row['id']] = $row['data'];
173
        }
174
175
        return $results;
176
    }
177
178
    /**
179
     * Stores a value identified by a key in cache.
180
     * This is the implementation of the method declared in the parent class.
181
     *
182
     * @param string $key the key identifying the value to be cached
183
     * @param string $value the value to be cached. Other types (if you have disabled [[serializer]]) cannot be saved.
184
     * @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
185
     * @return bool true if the value is successfully stored into cache, false otherwise
186
     */
187
    protected function setValue($key, $value, $duration)
188
    {
189
        $result = $this->db->noCache(function (Connection $db) use ($key, $value, $duration) {
190
            $command = $db->createCommand()
191
                ->update($this->cacheTable, [
192
                    'expire' => $duration > 0 ? $duration + time() : 0,
193
                    'data' => [$value, \PDO::PARAM_LOB],
194
                ], ['id' => $key]);
195
            return $command->execute();
196
        });
197
198
        if ($result) {
199
            $this->gc();
200
201
            return true;
202
        }
203
        return $this->addValue($key, $value, $duration);
204
    }
205
206
    /**
207
     * Stores a value identified by a key into cache if the cache does not contain this key.
208
     * This is the implementation of the method declared in the parent class.
209
     *
210
     * @param string $key the key identifying the value to be cached
211
     * @param string $value the value to be cached. Other types (if you have disabled [[serializer]]) cannot be saved.
212
     * @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
213
     * @return bool true if the value is successfully stored into cache, false otherwise
214
     */
215
    protected function addValue($key, $value, $duration)
216
    {
217
        $this->gc();
218
219
        try {
220
            $this->db->noCache(function (Connection $db) use ($key, $value, $duration) {
221
                $db->createCommand()
222
                    ->insert($this->cacheTable, [
223
                        'id' => $key,
224
                        'expire' => $duration > 0 ? $duration + time() : 0,
225
                        'data' => [$value, \PDO::PARAM_LOB],
226
                    ])->execute();
227
            });
228
229
            return true;
230
        } catch (\Exception $e) {
231
            return false;
232
        }
233
    }
234
235
    /**
236
     * Deletes a value with the specified key from cache
237
     * This is the implementation of the method declared in the parent class.
238
     * @param string $key the key of the value to be deleted
239
     * @return bool if no error happens during deletion
240
     */
241
    protected function deleteValue($key)
242
    {
243
        $this->db->noCache(function (Connection $db) use ($key) {
244
            $db->createCommand()
245
                ->delete($this->cacheTable, ['id' => $key])
246
                ->execute();
247
        });
248
249
        return true;
250
    }
251
252
    /**
253
     * Removes the expired data values.
254
     * @param bool $force whether to enforce the garbage collection regardless of [[gcProbability]].
255
     * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
256
     */
257
    public function gc($force = false)
258
    {
259
        if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
260
            $this->db->createCommand()
261
                ->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time())
262
                ->execute();
263
        }
264
    }
265
266
    /**
267
     * Deletes all values from cache.
268
     * This is the implementation of the method declared in the parent class.
269
     * @return bool whether the flush operation was successful.
270
     */
271
    protected function flushValues()
272
    {
273
        $this->db->createCommand()
274
            ->delete($this->cacheTable)
275
            ->execute();
276
277
        return true;
278
    }
279
}
280