You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
490 lines
17 KiB
490 lines
17 KiB
<?php
|
|
/**
|
|
* li₃: the most RAD framework for PHP (http://li3.me)
|
|
*
|
|
* Copyright 2009, Union of RAD. All rights reserved. This source
|
|
* code is distributed under the terms of the BSD 3-Clause License.
|
|
* The full license text can be found in the LICENSE.txt file.
|
|
*/
|
|
|
|
namespace lithium\storage;
|
|
|
|
use lithium\aop\Filters;
|
|
use lithium\core\ConfigException;
|
|
use lithium\security\Hash;
|
|
|
|
/**
|
|
* The `Cache` static class provides a consistent interface to configure and utilize the different
|
|
* cache adapters included with Lithium, as well as your own adapters.
|
|
*
|
|
* The Cache layer of Lithium inherits from the common `Adaptable` class, which provides the generic
|
|
* configuration setting & retrieval logic, as well as the logic required to locate & instantiate
|
|
* the proper adapter class.
|
|
*
|
|
* In most cases, you will configure various named cache configurations in your bootstrap process,
|
|
* which will then be available to you in all other parts of your application.
|
|
*
|
|
* A simple example configuration:
|
|
*
|
|
* ```
|
|
* Cache::config([
|
|
* 'local' => [
|
|
* 'adapter' => 'Apc'
|
|
* ],
|
|
* 'distributed' => [
|
|
* 'adapter' => 'Memcached',
|
|
* 'host' => '127.0.0.1:11211'
|
|
* ],
|
|
* 'default' => [
|
|
* 'adapter' => 'File',
|
|
* 'strategies => ['Serializer']
|
|
* ]
|
|
* ];
|
|
* ```
|
|
*
|
|
* Adapter configurations can be scoped, adapters will then handle the
|
|
* namespacing of the keys transparently for you:
|
|
*
|
|
* ```
|
|
* Cache::config([
|
|
* 'primary' => ['adapter' => 'Apc', 'scope' => 'primary'],
|
|
* 'secondary' => ['adapter' => 'Apc', 'scope' => 'secondary']
|
|
* ];
|
|
* ```
|
|
*
|
|
* Cache adapters differ in the functionality they provide and how the provide it. To see
|
|
* if an adapter meets your requirement and for more information on the specifics
|
|
* (i.e. atomicity of operations), consult the documentation the adapter first.
|
|
*
|
|
* All adapters will provide `write`, `read`, `delete` and `increment`/`decrement` functionality. On
|
|
* top of that adapters may provide `clean` and `clear` functionality as well as direct access to
|
|
* additional methods. Which allows for a very wide range of flexibility at the cost of portability.
|
|
*
|
|
* ```
|
|
* Cache::adapter('default')->methodName($argument);
|
|
* ```
|
|
*
|
|
* @see lithium\core\Adaptable
|
|
* @see lithium\storage\cache\Adapter
|
|
* @see lithium\storage\cache\adapter
|
|
*/
|
|
class Cache extends \lithium\core\Adaptable {
|
|
|
|
/**
|
|
* Can be used for expiry parameters or configuration options to
|
|
* specify that a cached item should persist as long and expire as
|
|
* late as possible.
|
|
*/
|
|
const PERSIST = 0;
|
|
|
|
/**
|
|
* Stores configurations for cache adapters.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static $_configurations = [];
|
|
|
|
/**
|
|
* Libraries::locate() compatible path to adapters for this class.
|
|
*
|
|
* @var string Dot-delimited path.
|
|
*/
|
|
protected static $_adapters = 'adapter.storage.cache';
|
|
|
|
/**
|
|
* Libraries::locate() compatible path to strategies for this class.
|
|
*
|
|
* @var string Dot-delimited path.
|
|
*/
|
|
protected static $_strategies = 'strategy.storage.cache';
|
|
|
|
/**
|
|
* Generates one or multiple safe cache keys optionally adding a suffix
|
|
* with a hash over the provided data. The hash value is generated in
|
|
* an optimized way dependending on the type of data.
|
|
*
|
|
* Simple usage (in this case noop):
|
|
* ```
|
|
* Cache::key('default', 'post');
|
|
* // returns `'post'`
|
|
*
|
|
* Cache::key('default', ['posts', 'banners']);
|
|
* // returns `['posts', 'banners']`
|
|
*
|
|
* Cache::key('default', ['posts' => 'foo', 'banners' => 'bar]);
|
|
* // returns `['posts' => 'foo', 'banners' => 'bar']`
|
|
* ```
|
|
*
|
|
* Make a key safe to use with adapter (exact result depends
|
|
* on key constraints enforced by the selected adapter:
|
|
* ```
|
|
* Cache::key('default', 'posts for Helgi Þorbjörnsson');
|
|
* // returns `'posts_for_Helgi__orbj_rnsson_c7f8433a'`
|
|
* ```
|
|
*
|
|
* Using additional scalar or non-scalar data to generate key:
|
|
* ```
|
|
* Cache::key('default', 'post', 2);
|
|
* // returns `'post:1ad5be0d'`
|
|
*
|
|
* Cache::key('default', 'post', array(2, 'json'));
|
|
* // returns `'post:723f0e19'`
|
|
*
|
|
* Cache::key('default', ['posts', 'banners'], 'json');
|
|
* // returns `['posts:6b072545', 'banners:6b072545']`
|
|
*
|
|
* Cache::key('default', ['posts' => 'foo', 'banners' => 'bar'], 'json');
|
|
* // returns `['posts:38ec40e5' => 'foo', 'banners:38ec40e5' => 'bar']`
|
|
* ```
|
|
*
|
|
* Or with a resuable key generator function:
|
|
* ```
|
|
* $posts[0] = ['id' => 1];
|
|
* $posts[1] = ['id' => 2];
|
|
*
|
|
* $key = function($data) { return 'post:' . $data['id']};
|
|
*
|
|
* Cache::key('default', $key, $post[0]); // returns `'post:1'`
|
|
* Cache::key('default', $key, $post[1]); // returns `'post:2'`
|
|
* ```
|
|
*
|
|
* This example shows a key mutating generator function:
|
|
* ```
|
|
* $base = 'post';
|
|
* $key = function($id) use (&base) { return $base .= ":{$id}"; };
|
|
*
|
|
* Cache::key('default', $key, 1); // returns `'post:1'`
|
|
* Cache::key('default', $key, 2); // returns `'post:1:2'`
|
|
* ```
|
|
*
|
|
* @param string $name Configuration to be used for generating key/s.
|
|
* @param mixed $key String or an array of strings that will be used as the cache key/s.
|
|
* Also accepts associative arrays where the key part will be modified, but
|
|
* the value left untouched. Also accepts a key generator function that
|
|
* is passed $data and must return a string that will be used as the key.
|
|
* @param mixed $data Additional data to use when generating key. Can be any kind of type except
|
|
* a resource. The method will calculate a hash of the data and append that to
|
|
* the key/s. When $key is a function the data is passed to it instead.
|
|
* @return string|array The generated cache key/s.
|
|
*/
|
|
public static function key($name, $key, $data = null) {
|
|
$adapter = static::adapter($name);
|
|
|
|
if (is_callable($key)) {
|
|
return current($adapter->key([$key($data)]));
|
|
}
|
|
$keys = ($isMulti = is_array($key)) ? $key : [$key];
|
|
$keys = ($hasData = !is_integer(key($keys))) ? array_keys($keys) : $keys;
|
|
|
|
if ($data !== null) {
|
|
$data = Hash::calculate($data, ['type' => 'crc32b']);
|
|
$keys = array_map(function($key) use ($data) { return $key .= ":{$data}"; }, $keys);
|
|
}
|
|
$keys = $adapter->key($keys);
|
|
$keys = $hasData ? array_combine($keys, array_values((array) $key)) : $keys;
|
|
|
|
return $isMulti ? $keys : current($keys);
|
|
}
|
|
|
|
/**
|
|
* Writes to the specified cache configuration.
|
|
*
|
|
* Can handle single- and multi-key writes.
|
|
*
|
|
* This method has two valid syntaxes depending on if you're storing
|
|
* data using a single key or multiple keys as outlined below.
|
|
* ```
|
|
* // To write data to a single-key use the following syntax.
|
|
* Cache::write('default', 'foo', 'bar', '+1 minute');
|
|
*
|
|
* // For multi-key writes the $data parameter's role becomes
|
|
* // the one of the $expiry parameter.
|
|
* Cache::write('default', ['foo' => 'bar', ... ], '+1 minute');
|
|
* ```
|
|
*
|
|
* These two calls are synonymical and demonstrate the two
|
|
* possible ways to specify the expiration time.
|
|
* ```
|
|
* Cache::write('default', 'foo', 'bar', '+1 minute');
|
|
* Cache::write('default', 'foo', 'bar', 60);
|
|
* ```
|
|
*
|
|
* @param string $name Configuration to be used for writing.
|
|
* @param mixed $key Key to uniquely identify the cache entry or an array of key/value pairs
|
|
* for multi-key writes mapping cache keys to the data to be cached.
|
|
* @param mixed $data Data to be cached.
|
|
* @param string|integer $expiry A `strtotime()` compatible cache time. Alternatively an integer
|
|
* denoting the seconds until the item expires (TTL). If no expiry time is
|
|
* set, then the default cache expiration time set with the cache adapter
|
|
* configuration will be used. To persist an item use `Cache::PERSIST`.
|
|
* @param array $options Options for the method and strategies.
|
|
* - `'strategies'` _boolean_: Indicates if strategies should be used,
|
|
* defaults to `true`.
|
|
* - `'conditions'` _mixed_: A function or item that must return or
|
|
* evaluate to `true` in order to continue write operation.
|
|
* @return boolean `true` on successful cache write, `false` otherwise. When writing
|
|
* multiple items and an error occurs writing any of the items the
|
|
* whole operation fails and this method will return `false`.
|
|
* @filter
|
|
*/
|
|
public static function write($name, $key, $data = null, $expiry = null, array $options = []) {
|
|
$options += ['conditions' => null, 'strategies' => true];
|
|
|
|
if (is_callable($options['conditions']) && !$options['conditions']()) {
|
|
return false;
|
|
}
|
|
try {
|
|
$adapter = static::adapter($name);
|
|
} catch (ConfigException $e) {
|
|
return false;
|
|
}
|
|
|
|
if (is_array($key)) {
|
|
$keys = $key;
|
|
$expiry = $data;
|
|
} else {
|
|
$keys = [$key => $data];
|
|
}
|
|
|
|
if ($options['strategies']) {
|
|
foreach ($keys as $key => &$value) {
|
|
$value = static::applyStrategies(__FUNCTION__, $name, $value, [
|
|
'key' => $key, 'class' => __CLASS__
|
|
]);
|
|
}
|
|
}
|
|
$params = compact('keys', 'expiry');
|
|
|
|
return Filters::run(get_called_class(), __FUNCTION__, $params, function($params) use ($adapter) {
|
|
return $adapter->write($params['keys'], $params['expiry']);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reads from the specified cache configuration.
|
|
*
|
|
* Can handle single- and multi-key reads.
|
|
*
|
|
* Read-through caching can be used by passing expiry and the to-be-cached value
|
|
* in the `write` option. Following three ways to achieve this.
|
|
* ```
|
|
* Cache::read('default', 'foo', [
|
|
* 'write' => ['+5 days' => 'bar']
|
|
* ]); // returns `'bar'`
|
|
*
|
|
* Cache::read('default', 'foo', [
|
|
* 'write' => ['+5 days' => function() { return 'bar'; }]
|
|
* ]);
|
|
*
|
|
* Cache::read('default', 'foo', [
|
|
* 'write' => function() { return ['+5 days' => 'bar']; }
|
|
* ]);
|
|
* ```
|
|
*
|
|
* @param string $name Configuration to be used for reading.
|
|
* @param mixed $key Key to uniquely identify the cache entry or an array of keys
|
|
* for multikey-reads.
|
|
* @param array $options Options for the method and strategies.
|
|
* - `'write'`: Allows for read-through caching see description for usage.
|
|
* - `'strategies'` _boolean_: Indicates if strategies should be used,
|
|
* defaults to `true`.
|
|
* - `'conditions'` _mixed_: A function or item that must return or
|
|
* evaluate to `true` in order to continue write operation.
|
|
* @return mixed For single-key reads will return the result if the cache
|
|
* key has been found otherwise returns `null`. When reading
|
|
* multiple keys a results array is returned mapping keys to
|
|
* retrieved values. Keys where the value couldn't successfully
|
|
* been read will not be contained in the results array.
|
|
* @filter
|
|
*/
|
|
public static function read($name, $key, array $options = []) {
|
|
$options += ['conditions' => null, 'strategies' => true, 'write' => null];
|
|
|
|
if (is_callable($options['conditions']) && !$options['conditions']()) {
|
|
return false;
|
|
}
|
|
try {
|
|
$adapter = static::adapter($name);
|
|
} catch (ConfigException $e) {
|
|
return false;
|
|
}
|
|
|
|
if ($isMulti = is_array($key)) {
|
|
$keys = $key;
|
|
} else {
|
|
$keys = [$key];
|
|
}
|
|
$params = compact('keys');
|
|
|
|
$results = Filters::run(get_called_class(), __FUNCTION__, $params, function($params) use ($adapter) {
|
|
return $adapter->read($params['keys']);
|
|
});
|
|
|
|
if ($write = $options['write']) {
|
|
$isEvaluated = false;
|
|
|
|
foreach ($keys as $key) {
|
|
if (isset($results[$key])) {
|
|
continue;
|
|
}
|
|
if (!$isEvaluated) {
|
|
$write = is_callable($write) ? $write() : $write;
|
|
$expiry = key($write);
|
|
$value = current($write);
|
|
$value = is_callable($value) ? $value() : $value;
|
|
|
|
$isEvaluated = true;
|
|
}
|
|
if (!static::write($name, $key, $value, $expiry)) {
|
|
return false;
|
|
}
|
|
$results[$key] = static::applyStrategies('write', $name, $value, [
|
|
'key' => $key, 'mode' => 'LIFO', 'class' => __CLASS__
|
|
]);
|
|
}
|
|
}
|
|
|
|
if ($options['strategies']) {
|
|
foreach ($results as $key => &$result) {
|
|
$result = static::applyStrategies(__FUNCTION__, $name, $result, [
|
|
'key' => $key, 'mode' => 'LIFO', 'class' => __CLASS__
|
|
]);
|
|
}
|
|
}
|
|
return $isMulti ? $results : ($results ? reset($results) : null);
|
|
}
|
|
|
|
/**
|
|
* Deletes using the specified cache configuration.
|
|
*
|
|
* Can handle single- and multi-key deletes.
|
|
*
|
|
* @param string $name The cache configuration to delete from.
|
|
* @param mixed $key Key to be deleted or an array of keys to delete.
|
|
* @param array $options Options for the method and strategies.
|
|
* - `'conditions'` _mixed_: A function or item that must return or
|
|
* evaluate to `true` in order to continue write operation.
|
|
* @return boolean `true` on successful cache delete, `false` otherwise. When deleting
|
|
* multiple items and an error occurs deleting any of the items the
|
|
* whole operation fails and this method will return `false`.
|
|
* @filter
|
|
*/
|
|
public static function delete($name, $key, array $options = []) {
|
|
$options += ['conditions' => null, 'strategies' => true];
|
|
|
|
if (is_callable($options['conditions']) && !$options['conditions']()) {
|
|
return false;
|
|
}
|
|
try {
|
|
$adapter = static::adapter($name);
|
|
} catch (ConfigException $e) {
|
|
return false;
|
|
}
|
|
|
|
if (is_array($key)) {
|
|
$keys = $key;
|
|
} else {
|
|
$keys = [$key];
|
|
}
|
|
$params = compact('keys');
|
|
|
|
return Filters::run(get_called_class(), __FUNCTION__, $params, function($params) use ($adapter) {
|
|
return $adapter->delete($params['keys']);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Performs a increment operation on specified numeric cache item
|
|
* from the given cache configuration.
|
|
*
|
|
* @param string $name Name of the cache configuration to use.
|
|
* @param string $key Key of numeric cache item to increment
|
|
* @param integer $offset Offset to increment - defaults to 1.
|
|
* @param array $options Options for this method.
|
|
* - `'conditions'`: A function or item that must return or evaluate to
|
|
* `true` in order to continue operation.
|
|
* @return integer|boolean Item's new value on successful increment, false otherwise.
|
|
* @filter
|
|
*/
|
|
public static function increment($name, $key, $offset = 1, array $options = []) {
|
|
$options += ['conditions' => null];
|
|
|
|
if (is_callable($options['conditions']) && !$options['conditions']()) {
|
|
return false;
|
|
}
|
|
try {
|
|
$adapter = static::adapter($name);
|
|
} catch (ConfigException $e) {
|
|
return false;
|
|
}
|
|
$params = compact('key', 'offset');
|
|
|
|
return Filters::run(get_called_class(), __FUNCTION__, $params, function($params) use ($adapter) {
|
|
return $adapter->increment($params['key'], $params['offset']);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Performs a decrement operation on specified numeric cache item
|
|
* from the given cache configuration.
|
|
*
|
|
* @param string $name Name of the cache configuration to use.
|
|
* @param string $key Key of numeric cache item to decrement
|
|
* @param integer $offset Offset to decrement - defaults to 1.
|
|
* @param array $options Options for this method.
|
|
* - `'conditions'`: A function or item that must return or evaluate to
|
|
* `true` in order to continue operation.
|
|
* @return integer|boolean Item's new value on successful decrement, false otherwise.
|
|
* @filter
|
|
*/
|
|
public static function decrement($name, $key, $offset = 1, array $options = []) {
|
|
$options += ['conditions' => null];
|
|
|
|
if (is_callable($options['conditions']) && !$options['conditions']()) {
|
|
return false;
|
|
}
|
|
try {
|
|
$adapter = static::adapter($name);
|
|
} catch (ConfigException $e) {
|
|
return false;
|
|
}
|
|
$params = compact('key', 'offset');
|
|
|
|
return Filters::run(get_called_class(), __FUNCTION__, $params, function($params) use ($adapter) {
|
|
return $adapter->decrement($params['key'], $params['offset']);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Perform garbage collection on specified cache configuration. All invalidated cache
|
|
* keys - *without* honoring a configured scope - from the specified configuration are
|
|
* removed.
|
|
*
|
|
* @param string $name The cache configuration to be cleaned.
|
|
* @return boolean `true` on successful cleaning, `false` if failed partially or entirely.
|
|
*/
|
|
public static function clean($name) {
|
|
try {
|
|
return static::adapter($name)->clean();
|
|
} catch (ConfigException $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears entire cache by flushing it. All cache keys - *without* honoring
|
|
* a configured scope - from the specified configuration are removed.
|
|
*
|
|
* @param string $name The cache configuration to be cleared.
|
|
* @return boolean `true` on successful clearing, `false` if failed partially or entirely.
|
|
*/
|
|
public static function clear($name) {
|
|
try {
|
|
return static::adapter($name)->clear();
|
|
} catch (ConfigException $e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
?>
|