Completed
Push — master ( 625d55...2d12e1 )
by Carsten
12:37
created

MemCache   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 300
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 52.81%

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 3
dl 0
loc 300
ccs 47
cts 89
cp 0.5281
rs 8.3157
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 5 1
A getServers() 0 4 1
A getValue() 0 4 1
A getValues() 0 4 2
A setValue() 0 9 3
A setServers() 0 6 2
A addValue() 0 9 3
A deleteValue() 0 4 1
A flushValues() 0 4 1
B addServers() 0 20 5
B addMemcachedServers() 0 14 6
C getMemcache() 0 24 9
A setValues() 0 15 4
B addMemcacheServers() 0 33 4

How to fix   Complexity   

Complex Class

Complex classes like MemCache often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MemCache, and based on these observations, apply Extract Interface, too.

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
13
/**
14
 * MemCache implements a cache application component based on [memcache](http://pecl.php.net/package/memcache)
15
 * and [memcached](http://pecl.php.net/package/memcached).
16
 *
17
 * MemCache supports both [memcache](http://pecl.php.net/package/memcache) and
18
 * [memcached](http://pecl.php.net/package/memcached). By setting [[useMemcached]] to be true or false,
19
 * one can let MemCache to use either memcached or memcache, respectively.
20
 *
21
 * MemCache can be configured with a list of memcache servers by settings its [[servers]] property.
22
 * By default, MemCache assumes there is a memcache server running on localhost at port 11211.
23
 *
24
 * See [[Cache]] for common cache operations that MemCache supports.
25
 *
26
 * Note, there is no security measure to protected data in memcache.
27
 * All data in memcache can be accessed by any process running in the system.
28
 *
29
 * To use MemCache as the cache application component, configure the application as follows,
30
 *
31
 * ```php
32
 * [
33
 *     'components' => [
34
 *         'cache' => [
35
 *             'class' => 'yii\caching\MemCache',
36
 *             'servers' => [
37
 *                 [
38
 *                     'host' => 'server1',
39
 *                     'port' => 11211,
40
 *                     'weight' => 60,
41
 *                 ],
42
 *                 [
43
 *                     'host' => 'server2',
44
 *                     'port' => 11211,
45
 *                     'weight' => 40,
46
 *                 ],
47
 *             ],
48
 *         ],
49
 *     ],
50
 * ]
51
 * ```
52
 *
53
 * In the above, two memcache servers are used: server1 and server2. You can configure more properties of
54
 * each server, such as `persistent`, `weight`, `timeout`. Please see [[MemCacheServer]] for available options.
55
 *
56
 * For more details and usage information on Cache, see the [guide article on caching](guide:caching-overview).
57
 *
58
 * @property \Memcache|\Memcached $memcache The memcache (or memcached) object used by this cache component.
59
 * This property is read-only.
60
 * @property MemCacheServer[] $servers List of memcache server configurations. Note that the type of this
61
 * property differs in getter and setter. See [[getServers()]] and [[setServers()]] for details.
62
 *
63
 * @author Qiang Xue <[email protected]>
64
 * @since 2.0
65
 */
66
class MemCache extends Cache
67
{
68
    /**
69
     * @var bool whether to use memcached or memcache as the underlying caching extension.
70
     * If true, [memcached](http://pecl.php.net/package/memcached) will be used.
71
     * If false, [memcache](http://pecl.php.net/package/memcache) will be used.
72
     * Defaults to false.
73
     */
74
    public $useMemcached = false;
75
    /**
76
     * @var string an ID that identifies a Memcached instance. This property is used only when [[useMemcached]] is true.
77
     * By default the Memcached instances are destroyed at the end of the request. To create an instance that
78
     * persists between requests, you may specify a unique ID for the instance. All instances created with the
79
     * same ID will share the same connection.
80
     * @see http://ca2.php.net/manual/en/memcached.construct.php
81
     */
82
    public $persistentId;
83
    /**
84
     * @var array options for Memcached. This property is used only when [[useMemcached]] is true.
85
     * @see http://ca2.php.net/manual/en/memcached.setoptions.php
86
     */
87
    public $options;
88
    /**
89
     * @var string memcached sasl username. This property is used only when [[useMemcached]] is true.
90
     * @see http://php.net/manual/en/memcached.setsaslauthdata.php
91
     */
92
    public $username;
93
    /**
94
     * @var string memcached sasl password. This property is used only when [[useMemcached]] is true.
95
     * @see http://php.net/manual/en/memcached.setsaslauthdata.php
96
     */
97
    public $password;
98
99
    /**
100
     * @var \Memcache|\Memcached the Memcache instance
101
     */
102
    private $_cache;
103
    /**
104
     * @var array list of memcache server configurations
105
     */
106
    private $_servers = [];
107
108
109
    /**
110
     * Initializes this application component.
111
     * It creates the memcache instance and adds memcache servers.
112
     */
113 16
    public function init()
114
    {
115 16
        parent::init();
116 16
        $this->addServers($this->getMemcache(), $this->getServers());
117 16
    }
118
119
    /**
120
     * Add servers to the server pool of the cache specified
121
     *
122
     * @param \Memcache|\Memcached $cache
123
     * @param MemCacheServer[] $servers
124
     * @throws InvalidConfigException
125
     */
126 16
    protected function addServers($cache, $servers)
127
    {
128 16
        if (empty($servers)) {
129 16
            $servers = [new MemCacheServer([
130 16
                'host' => '127.0.0.1',
131
                'port' => 11211,
132
            ])];
133
        } else {
134
            foreach ($servers as $server) {
135
                if ($server->host === null) {
136
                    throw new InvalidConfigException("The 'host' property must be specified for every memcache server.");
137
                }
138
            }
139
        }
140 16
        if ($this->useMemcached) {
141 16
            $this->addMemcachedServers($cache, $servers);
0 ignored issues
show
Bug introduced by
It seems like $cache defined by parameter $cache on line 126 can also be of type object<Memcache>; however, yii\caching\MemCache::addMemcachedServers() does only seem to accept object<Memcached>, 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...
142
        } else {
143
            $this->addMemcacheServers($cache, $servers);
0 ignored issues
show
Bug introduced by
It seems like $cache defined by parameter $cache on line 126 can also be of type object<Memcached>; however, yii\caching\MemCache::addMemcacheServers() does only seem to accept object<Memcache>, 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...
144
        }
145 16
    }
146
147
    /**
148
     * Add servers to the server pool of the cache specified
149
     * Used for memcached PECL extension.
150
     *
151
     * @param \Memcached $cache
152
     * @param MemCacheServer[] $servers
153
     */
154 16
    protected function addMemcachedServers($cache, $servers)
155
    {
156 16
        $existingServers = [];
157 16
        if ($this->persistentId !== null) {
158
            foreach ($cache->getServerList() as $s) {
159
                $existingServers[$s['host'] . ':' . $s['port']] = true;
160
            }
161
        }
162 16
        foreach ($servers as $server) {
163 16
            if (empty($existingServers) || !isset($existingServers[$server->host . ':' . $server->port])) {
164 16
                $cache->addServer($server->host, $server->port, $server->weight);
165
            }
166
        }
167 16
    }
168
169
    /**
170
     * Add servers to the server pool of the cache specified
171
     * Used for memcache PECL extension.
172
     *
173
     * @param \Memcache $cache
174
     * @param MemCacheServer[] $servers
175
     */
176
    protected function addMemcacheServers($cache, $servers)
177
    {
178
        $class = new \ReflectionClass($cache);
179
        $paramCount = $class->getMethod('addServer')->getNumberOfParameters();
180
        foreach ($servers as $server) {
181
            // $timeout is used for memcache versions that do not have $timeoutms parameter
182
            $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0);
183
            if ($paramCount === 9) {
184
                $cache->addserver(
185
                    $server->host,
186
                    $server->port,
187
                    $server->persistent,
188
                    $server->weight,
189
                    $timeout,
190
                    $server->retryInterval,
191
                    $server->status,
192
                    $server->failureCallback,
193
                    $server->timeout
194
                );
195
            } else {
196
                $cache->addserver(
197
                    $server->host,
198
                    $server->port,
199
                    $server->persistent,
200
                    $server->weight,
201
                    $timeout,
202
                    $server->retryInterval,
203
                    $server->status,
204
                    $server->failureCallback
205
                );
206
            }
207
        }
208
    }
209
210
    /**
211
     * Returns the underlying memcache (or memcached) object.
212
     * @return \Memcache|\Memcached the memcache (or memcached) object used by this cache component.
213
     * @throws InvalidConfigException if memcache or memcached extension is not loaded
214
     */
215 16
    public function getMemcache()
216
    {
217 16
        if ($this->_cache === null) {
218 16
            $extension = $this->useMemcached ? 'memcached' : 'memcache';
219 16
            if (!extension_loaded($extension)) {
220
                throw new InvalidConfigException("MemCache requires PHP $extension extension to be loaded.");
221
            }
222
223 16
            if ($this->useMemcached) {
224 16
                $this->_cache = $this->persistentId !== null ? new \Memcached($this->persistentId) : new \Memcached;
225 16
                if ($this->username !== null || $this->password !== null) {
226
                    $this->_cache->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
227
                    $this->_cache->setSaslAuthData($this->username, $this->password);
228
                }
229 16
                if (!empty($this->options)) {
230
                    $this->_cache->setOptions($this->options);
0 ignored issues
show
Bug introduced by
The method setOptions() does not exist on Memcached. Did you maybe mean setOption()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
231
                }
232
            } else {
233
                $this->_cache = new \Memcache;
234
            }
235
        }
236
237 16
        return $this->_cache;
238
    }
239
240
    /**
241
     * Returns the memcache or memcached server configurations.
242
     * @return MemCacheServer[] list of memcache server configurations.
243
     */
244 16
    public function getServers()
245
    {
246 16
        return $this->_servers;
247
    }
248
249
    /**
250
     * @param array $config list of memcache or memcached server configurations. Each element must be an array
251
     * with the following keys: host, port, persistent, weight, timeout, retryInterval, status.
252
     * @see http://php.net/manual/en/memcache.addserver.php
253
     * @see http://php.net/manual/en/memcached.addserver.php
254
     */
255
    public function setServers($config)
256
    {
257
        foreach ($config as $c) {
258
            $this->_servers[] = new MemCacheServer($c);
259
        }
260
    }
261
262
    /**
263
     * Retrieves a value from cache with a specified key.
264
     * This is the implementation of the method declared in the parent class.
265
     * @param string $key a unique key identifying the cached value
266
     * @return mixed|false the value stored in cache, false if the value is not in the cache or expired.
267
     */
268 13
    protected function getValue($key)
269
    {
270 13
        return $this->_cache->get($key);
271
    }
272
273
    /**
274
     * Retrieves multiple values from cache with the specified keys.
275
     * @param array $keys a list of keys identifying the cached values
276
     * @return array a list of cached values indexed by the keys
277
     */
278 2
    protected function getValues($keys)
279
    {
280 2
        return $this->useMemcached ? $this->_cache->getMulti($keys) : $this->_cache->get($keys);
0 ignored issues
show
Bug introduced by
The method getMulti does only exist in Memcached, but not in Memcache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
281
    }
282
283
    /**
284
     * Stores a value identified by a key in cache.
285
     * This is the implementation of the method declared in the parent class.
286
     *
287
     * @param string $key the key identifying the value to be cached
288
     * @param mixed $value the value to be cached.
289
     * @see [Memcache::set()](http://php.net/manual/en/memcache.set.php)
290
     * @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
291
     * @return bool true if the value is successfully stored into cache, false otherwise
292
     */
293 12
    protected function setValue($key, $value, $duration)
294
    {
295
        // Use UNIX timestamp since it doesn't have any limitation
296
        // @see http://php.net/manual/en/memcache.set.php
297
        // @see http://php.net/manual/en/memcached.expiration.php
298 12
        $expire = $duration > 0 ? $duration + time() : 0;
299
300 12
        return $this->useMemcached ? $this->_cache->set($key, $value, $expire) : $this->_cache->set($key, $value, 0, $expire);
301
    }
302
303
    /**
304
     * Stores multiple key-value pairs in cache.
305
     * @param array $data array where key corresponds to cache key while value is the value stored
306
     * @param int $duration the number of seconds in which the cached values will expire. 0 means never expire.
307
     * @return array array of failed keys.
308
     */
309 3
    protected function setValues($data, $duration)
310
    {
311 3
        if ($this->useMemcached) {
312
            // Use UNIX timestamp since it doesn't have any limitation
313
            // @see http://php.net/manual/en/memcache.set.php
314
            // @see http://php.net/manual/en/memcached.expiration.php
315 3
            $expire = $duration > 0 ? $duration + time() : 0;
316
317
            // Memcached::setMulti() returns boolean
318
            // @see http://php.net/manual/en/memcached.setmulti.php
319 3
            return $this->_cache->setMulti($data, $expire) ? [] : array_keys($data);
0 ignored issues
show
Bug introduced by
The method setMulti does only exist in Memcached, but not in Memcache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
320
        } else {
321
            return parent::setValues($data, $duration);
322
        }
323
    }
324
325
    /**
326
     * Stores a value identified by a key into cache if the cache does not contain this key.
327
     * This is the implementation of the method declared in the parent class.
328
     *
329
     * @param string $key the key identifying the value to be cached
330
     * @param mixed $value the value to be cached
331
     * @see [Memcache::set()](http://php.net/manual/en/memcache.set.php)
332
     * @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
333
     * @return bool true if the value is successfully stored into cache, false otherwise
334
     */
335 2
    protected function addValue($key, $value, $duration)
336
    {
337
        // Use UNIX timestamp since it doesn't have any limitation
338
        // @see http://php.net/manual/en/memcache.set.php
339
        // @see http://php.net/manual/en/memcached.expiration.php
340 2
        $expire = $duration > 0 ? $duration + time() : 0;
341
342 2
        return $this->useMemcached ? $this->_cache->add($key, $value, $expire) : $this->_cache->add($key, $value, 0, $expire);
343
    }
344
345
    /**
346
     * Deletes a value with the specified key from cache
347
     * This is the implementation of the method declared in the parent class.
348
     * @param string $key the key of the value to be deleted
349
     * @return bool if no error happens during deletion
350
     */
351 1
    protected function deleteValue($key)
352
    {
353 1
        return $this->_cache->delete($key, 0);
354
    }
355
356
    /**
357
     * Deletes all values from cache.
358
     * This is the implementation of the method declared in the parent class.
359
     * @return bool whether the flush operation was successful.
360
     */
361 11
    protected function flushValues()
362
    {
363 11
        return $this->_cache->flush();
364
    }
365
}
366