Completed
Pull Request — development (#2330)
by Joshua
41:37 queued 30:33
created

Cache::enable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 6
ccs 0
cts 3
cp 0
rs 9.4286
cc 1
eloc 3
nc 1
nop 1
crap 2
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 26 and the first side effect is on line 20.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
/**
4
 * This file contains functions that deal with getting and setting cache values.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This software is a derived product, based on:
11
 *
12
 * Simple Machines Forum (SMF)
13
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
14
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
15
 *
16
 * @version 1.1 dev Release Candidate 1
17
 *
18
 */
19
20
if (!defined('ELK'))
21
	die('No access...');
22
23
/**
24
 * Class Cache - Methods that deal with getting and setting cache values.
25
 */
26
class Cache
27
{
28
	/**
29
	 * Holds our static instance of the class
30
	 * @var object
31
	 */
32
	protected static $_instance = null;
33
34
	/**
35
	 * Array of options for the methods (if needed)
36
	 * @var mixed[]
37
	 */
38
	protected $_options = array();
39
40
	/**
41
	 * If the cache is enabled or not.
42
	 * @var bool
43
	 */
44
	protected $enabled = false;
45
46
	/**
47
	 * The caching level
48
	 * @var int
49
	 */
50
	protected $level = 0;
51
52
	/**
53
	 * The prefix to append to the cache key
54
	 * @var string
55
	 */
56
	protected $_key_prefix = null;
57
58
	/**
59
	 * The caching object
60
	 * @var object
61
	 */
62
	protected $_cache_obj = null;
63
64
	/**
65
	 * Initialize the class, defines the options and the caching method to use
66
	 *
67
	 * @param int $level The level of caching
68
	 * @param string $accelerator The accelerator used
69
	 * @param mixed[] $options Any setting necessary to the caching engine
70
	 */
71
	public function __construct($level, $accelerator, $options)
72
	{
73
		$this->setLevel($level);
74
75
		if ($level > 0)
76
		{
77
			$this->enable(true);
78
		}
79
80
		// If the cache is disabled just go out
81
		if (!$this->isEnabled())
82
			return;
83
84
		$this->_options = $options;
85
86
		if (empty($accelerator))
87
			$accelerator = 'filebased';
88
89
		$cache_class = '\\ElkArte\\sources\\subs\\CacheMethod\\' . ucfirst($accelerator);
90
		$this->_cache_obj = new $cache_class($this->_options);
91
92
		if ($this->_cache_obj !== null)
93
			$this->_cache_enable = $this->_cache_obj->init();
0 ignored issues
show
Bug introduced by
The property _cache_enable does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
94
95
		$this->_build_prefix();
96
	}
97
98
	/**
99
	 * Try to retrieve a cache entry. On failure, call the appropriate function.
100
	 * This callback is sent as $file to include, and $function to call, with
101
	 * $params parameters.
102
	 *
103
	 * @param string $key cache entry key
104
	 * @param string $file file to include
105
	 * @param string $function function to call
106
	 * @param mixed[] $params parameters sent to the function
107
	 * @param int $level = 1
108
	 * @return string
109
	 */
110
	public function quick_get($key, $file, $function, $params, $level = 1)
111
	{
112
		if (!$this->isEnabled())
113
			return;
114
115
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
116
117
		/* Refresh the cache if either:
118
			1. Caching is disabled.
119
			2. The cache level isn't high enough.
120
			3. The item has not been cached or the cached item expired.
121
			4. The cached item has a custom expiration condition evaluating to true.
122
			5. The expire time set in the cache item has passed (needed for Zend).
123
		*/
124
		if ($this->level < $level || !is_array($cache_block = $this->get($key, 3600)) || (!empty($cache_block['refresh_eval']) && eval($cache_block['refresh_eval'])) || (!empty($cache_block['expires']) && $cache_block['expires'] < time()))
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
125
		{
126
			require_once(SOURCEDIR . '/' . $file);
127
			$cache_block = call_user_func_array($function, $params);
128
129
			if ($this->_cache_enable >= $level)
130
				$this->put($key, $cache_block, $cache_block['expires'] - time());
131
		}
132
133
		// Some cached data may need a freshening up after retrieval.
134
		if (!empty($cache_block['post_retri_eval']))
135
			eval($cache_block['post_retri_eval']);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
136
137
		call_integration_hook('post_cache_quick_get', array($cache_block));
138
139
		return $cache_block['data'];
140
	}
141
142
	/**
143
	 * Puts value in the cache under key for ttl seconds.
144
	 *
145
	 * - It may "miss" so shouldn't be depended on
146
	 * - Uses the cache engine chosen in the ACP and saved in settings.php
147
	 * - It supports:
148
	 *   - Turck MMCache: http://turck-mmcache.sourceforge.net/index_old.html#api
149
	 *   - Xcache: http://xcache.lighttpd.net/wiki/XcacheApi
150
	 *   - memcache: http://www.php.net/memcache
151
	 *   - APC: http://www.php.net/apc
152
	 *   - eAccelerator: http://bart.eaccelerator.net/doc/phpdoc/
153
	 *   - Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
154
	 *   - Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
155
	 *
156
	 * @param string $key
157
	 * @param string|int|mixed[]|null $value
158
	 * @param int $ttl = 120
159
	 */
160 14
	public function put($key, $value, $ttl = 120)
161
	{
162 14
		global $db_show_debug;
163
164 14
		if (!$this->isEnabled())
165 14
			return;
166
167
		if ($db_show_debug === true)
168
		{
169
			$cache_hit = array(
170
				'k' => $key,
171
				'd' => 'put',
172
				's' => $value === null ? 0 : strlen(serialize($value))
173
			);
174
			$st = microtime(true);
175
		}
176
177
		$key = $this->_key($key);
178
		$value = $value === null ? null : serialize($value);
179
180
		$this->_cache_obj->put($key, $value, $ttl);
181
182
		call_integration_hook('Cache::instance()->put', array($key, $value, $ttl));
183
184
		if ($db_show_debug === true)
185
		{
186
			$cache_hit['t'] = microtime(true) - $st;
0 ignored issues
show
Bug introduced by
The variable $cache_hit does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
187
			Debug::get()->cache($cache_hit);
188
		}
189
	}
190
191
	/**
192
	 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
193
	 *
194
	 * - It may often "miss", so shouldn't be depended on.
195
	 * - It supports the same as cache::put().
196
	 *
197
	 * @param string $key
198
	 * @param int $ttl = 120
199
	 */
200 9
	public function get($key, $ttl = 120)
201
	{
202 9
		global $db_show_debug;
203
204 9
		if (!$this->isEnabled())
205 9
			return;
206
207
		if ($db_show_debug === true)
208
		{
209
			$cache_hit = array(
210
				'k' => $key,
211
				'd' => 'get'
212
			);
213
			$st = microtime(true);
214
		}
215
216
		$key = $this->_key($key);
217
		$value = $this->_cache_obj->get($key, $ttl);
218
219
		if ($db_show_debug === true)
220
		{
221
			$cache_hit['t'] = microtime(true) - $st;
0 ignored issues
show
Bug introduced by
The variable $cache_hit does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
222
			$cache_hit['s'] = isset($value) ? strlen($value) : 0;
223
			Debug::get()->cache($cache_hit);
224
		}
225
226
		call_integration_hook('Cache::instance()->get', array($key, $ttl, $value));
227
228
		return empty($value) ? null : @unserialize($value);
229
	}
230
231
	/**
232
	 * Same as $this->get but sets $var to the result and return if it was a hit
233
	 *
234
	 * @param mixed $var The variable to be assigned the result
235
	 * @param string $key
236
	 * @param int $ttl
237
	 * @return bool if it was a hit
238
	 */
239 8
	public function getVar(&$var, $key, $ttl = 120)
240
	{
241 8
		$var = $this->get($key, $ttl);
242 8
		return !$this->isMiss();
243
	}
244
245
	/**
246
	 * Empty out the cache in use as best it can
247
	 *
248
	 * It may only remove the files of a certain type (if the $type parameter is given)
249
	 * Type can be user, data or left blank
250
	 *  - user clears out user data
251
	 *  - data clears out system / opcode data
252
	 *  - If no type is specified will perform a complete cache clearing
253
	 * For cache engines that do not distinguish on types, a full cache flush will be done
254
	 *
255
	 * @param string $type = ''
256
	 */
257
	public function clean($type = '')
258
	{
259
		if (!$this->isEnabled())
260
			return;
261
262
		$this->_cache_obj->clean($type);
263
264
		// Invalidate cache, to be sure!
265
		// ... as long as CACHEDIR/index.php can be modified, anyway.
266
		@touch(CACHEDIR . '/index.php');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
267
268
		// Give addons a way to trigger cache cleaning.
269
		call_integration_hook('integrate_clean_cache');
270
271
		clearstatcache();
272
	}
273
274
	/**
275
	 * Enable or disable caching
276
	 *
277
	 * @param bool $enable
278
	 * @return $this
279
	 */
280
	public function enable($enable)
281
	{
282
		$this->enabled = (bool) $enable;
283
284
		return $this;
285
	}
286
287
	/**
288
	 * Check if caching is enabled
289
	 * @return bool
290
	 */
291 15
	public function isEnabled()
292
	{
293 15
		return $this->enabled;
294
	}
295
296
	/**
297
	 * Set the caching level. Setting it to <= 0 disables caching
298
	 *
299
	 * @param int $level
300
	 * @return $this
301
	 */
302
	public function setLevel($level)
303
	{
304
		$this->level = (int) $level;
305
306
		if ($this->level <= 0)
307
		{
308
			$this->enable(false);
309
		}
310
311
		return $this;
312
	}
313
314
	/**
315
	 * @return int
316
	 */
317
	public function getLevel()
318
	{
319
		return $this->level;
320
	}
321
322
	public function checkLevel($level)
323
	{
324
		return $this->isEnabled() && $this->level >= $level;
325
	}
326
327
	/**
328
	 * @var bool If the result of the last get was a miss
329
	 */
330 8
	public function isMiss()
331
	{
332 8
		return $this->isEnabled() ? $this->_cache_obj->isMiss() : true;
333
	}
334
335
	/**
336
	 * Get the key for the cache.
337
	 *
338
	 * @param string $key
339
	 * @return string
340
	 */
341
	protected function _key($key)
342
	{
343
		return $this->_key_prefix . $this->_cache_obj->fixkey($key);
344
	}
345
346
	/**
347
	 * Set $_key_prefix to a "unique" value based on timestamp of a file
348
	 */
349
	protected function _build_prefix()
350
	{
351
		global $boardurl;
352
353
		$this->_key_prefix = md5($boardurl . filemtime(CACHEDIR . '/index.php')) . '-ELK-';
354
	}
355
356
	/**
357
	 * Find and return the instance of the Cache class if it exists,
358
	 * or create it if it doesn't exist
359
	 */
360 15
	public static function instance()
361
	{
362 15
		if (self::$_instance === null)
363 15
		{
364
			global $cache_accelerator, $cache_enable, $cache_uid, $cache_password;
365
366
			$options = array();
367
			if ($cache_accelerator === 'xcache')
368
			{
369
				$options = array(
370
					'cache_uid' => $cache_uid,
371
					'cache_password' => $cache_password,
372
				);
373
			}
374
			Elk_Autoloader::getInstance()->register(SUBSDIR . '/CacheMethod', '\\ElkArte\\sources\\subs\\CacheMethod');
375
376
			self::$_instance = new Cache($cache_enable, $cache_accelerator, $options);
377
		}
378
379 15
		return self::$_instance;
380
	}
381
}