Skip to content

Commit

Permalink
Merge pull request #1067 from davidpersson/cache-scoping
Browse files Browse the repository at this point in the history
Implementing cache key scoping.
  • Loading branch information
nateabele committed Feb 4, 2014
2 parents d97ad03 + 50d2c73 commit 47e494f
Show file tree
Hide file tree
Showing 14 changed files with 635 additions and 51 deletions.
10 changes: 10 additions & 0 deletions storage/Cache.php
Expand Up @@ -37,6 +37,16 @@
* );
* }}}
*
* Adapter configurations can be scoped, adapters will then handle the
* namespacing of the keys transparently for you:
*
* {{{
* Cache::config(array(
* 'primary' => array('adapter' => 'Apc', 'scope' => 'primary'),
* 'secondary' => array('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.
Expand Down
40 changes: 40 additions & 0 deletions storage/cache/Adapter.php
Expand Up @@ -115,6 +115,46 @@ public function clean() {
public static function enabled() {
return true;
}

/**
* Adds scope prefix to keys using separator.
*
* @param string $scope Scope to use when prefixing.
* @param array $keys Array of keys either with or without mapping to values.
* @param string $separator String to use when separating scope from key.
* @return array Prefixed keys array.
*/
protected function _addScopePrefix($scope, array $keys, $separator = ':') {
$results = array();
$isMapped = !is_int(key($keys));

foreach ($keys as $key => $value) {
if ($isMapped) {
$results["{$scope}{$separator}{$key}"] = $value;
} else {
$results[$key] = "{$scope}{$separator}{$value}";
}
}
return $results;
}

/**
* Removes scope prefix from keys.
*
* @param string $scope Scope initially used when prefixing.
* @param array $keys Array of keys mapping to values.
* @param string $separator Separator used when prefix keys initially.
* @return array Keys array with prefix removed from each key.
*/
protected function _removeScopePrefix($scope, array $data, $separator = ':') {
$results = array();
$prefix = strlen("{$scope}{$separator}");

foreach ($data as $key => $value) {
$results[substr($key, $prefix)] = $value;
}
return $results;
}
}

?>
58 changes: 47 additions & 11 deletions storage/cache/adapter/Apc.php
Expand Up @@ -23,7 +23,8 @@
* operations as well as clearing the entire user-space cache.
*
* Cached item persistence is not guaranteed. Infrequently used items will
* be evicted from the cache when there is no room to store new ones.
* be evicted from the cache when there is no room to store new ones. Scope
* is available but not natively.
*
* A simple configuration can be accomplished as follows:
*
Expand All @@ -43,11 +44,17 @@ class Apc extends \lithium\storage\cache\Adapter {
/**
* Class constructor.
*
* @param array $config
* @see lithium\storage\Cache::config()
* @param array $config Configuration parameters for this cache adapter. These settings are
* indexed by name and queryable through `Cache::config('name')`.
* The defaults are:
* - 'scope' : Scope which will prefix keys; per default not set.
* - 'expiry' : Default expiry time used if none is explicitly set when calling
* `Cache::write()`.
*/
public function __construct(array $config = array()) {
$defaults = array(
'prefix' => '',
'scope' => null,
'expiry' => '+1 hour'
);
parent::__construct($config + $defaults);
Expand All @@ -64,15 +71,21 @@ public function __construct(array $config = array()) {
*/
public function write(array $keys, $expiry = null) {
$expiry = $expiry || $expiry === Cache::PERSIST ? $expiry : $this->_config['expiry'];
$scope = $this->_config['scope'];

return function($self, $params) use ($expiry) {
return function($self, $params) use ($expiry, $scope) {
if (!$expiry || $expiry === Cache::PERSIST) {
$ttl = 0;
} elseif (is_int($expiry)) {
$ttl = $expiry;
} else {
$ttl = strtotime($expiry) - time();
}
if ($scope) {
$params['keys'] = $self->invokeMethod('_addScopePrefix', array(
$scope, $params['keys']
));
}
return apc_store($params['keys'], null, $ttl) === array();
};
}
Expand All @@ -87,8 +100,20 @@ public function write(array $keys, $expiry = null) {
* not be included in the results array.
*/
public function read(array $keys) {
return function($self, $params) {
return apc_fetch($params['keys']);
$scope = $this->_config['scope'];

return function($self, $params) use ($scope) {
if ($scope) {
$params['keys'] = $self->invokeMethod('_addScopePrefix', array(
$scope, $params['keys']
));
}
$results = apc_fetch($params['keys']);

if ($scope) {
$results = $self->invokeMethod('_removeScopePrefix', array($scope, $results));
}
return $results;
};
}

Expand All @@ -99,7 +124,14 @@ public function read(array $keys) {
* @return Closure Function returning `true` on successful delete, `false` otherwise.
*/
public function delete(array $keys) {
return function($self, $params) {
$scope = $this->_config['scope'];

return function($self, $params) use ($scope) {
if ($scope) {
$params['keys'] = $self->invokeMethod('_addScopePrefix', array(
$scope, $params['keys']
));
}
return apc_delete($params['keys']) === array();
};
}
Expand All @@ -116,8 +148,10 @@ public function delete(array $keys) {
* @return Closure Function returning item's new value on successful decrement, else `false`
*/
public function decrement($key, $offset = 1) {
return function($self, $params) use ($offset) {
return apc_dec($params['key'], $offset);
$scope = $this->_config['scope'];

return function($self, $params) use ($offset, $scope) {
return apc_dec($scope ? "{$scope}:{$params['key']}" : $params['key'], $offset);
};
}

Expand All @@ -133,8 +167,10 @@ public function decrement($key, $offset = 1) {
* @return Closure Function returning item's new value on successful increment, else `false`
*/
public function increment($key, $offset = 1) {
return function($self, $params) use ($offset) {
return apc_inc($params['key'], $offset);
$scope = $this->_config['scope'];

return function($self, $params) use ($offset, $scope) {
return apc_inc($scope ? "{$scope}:{$params['key']}" : $params['key'], $offset);
};
}

Expand Down
33 changes: 28 additions & 5 deletions storage/cache/adapter/File.php
Expand Up @@ -27,7 +27,7 @@
* is provided.
*
* This adapter does *not* provided increment/decrement functionality and also can't handle
* serialization natively.
* serialization natively. Scope support is available but not natively.
*
* A simple configuration can be accomplished as follows:
*
Expand Down Expand Up @@ -60,13 +60,14 @@ class File extends \lithium\storage\cache\Adapter {
* The defaults are:
* - 'path' : Path where cached entries live, for example
* `Libraries::get(true, 'resources') . '/tmp/cache'`.
* - 'scope' : Scope which will prefix keys; per default not set.
* - 'expiry' : Default expiry time used if none is explicitly set when calling
* `Cache::write()`.
*/
public function __construct(array $config = array()) {
$defaults = array(
'path' => Libraries::get(true, 'resources') . '/tmp/cache',
'prefix' => '',
'scope' => null,
'expiry' => '+1 hour'
);
parent::__construct($config + $defaults);
Expand All @@ -84,15 +85,21 @@ public function __construct(array $config = array()) {
public function write(array $keys, $expiry = null) {
$path = $this->_config['path'];
$expiry = $expiry || $expiry === Cache::PERSIST ? $expiry : $this->_config['expiry'];
$scope = $this->_config['scope'];

return function($self, $params) use (&$path, $expiry) {
return function($self, $params) use (&$path, $expiry, $scope) {
if (!$expiry || $expiry === Cache::PERSIST) {
$expires = 0;
} elseif (is_int($expiry)) {
$expires = $expiry + time();
} else {
$expires = strtotime($expiry);
}
if ($scope) {
$params['keys'] = $self->invokeMethod('_addScopePrefix', array(
$scope, $params['keys'], '_'
));
}
foreach ($params['keys'] as $key => $value) {
$data = "{:expiry:{$expires}}\n{$value}";

Expand All @@ -115,8 +122,14 @@ public function write(array $keys, $expiry = null) {
*/
public function read(array $keys) {
$path = $this->_config['path'];
$scope = $this->_config['scope'];

return function($self, $params) use (&$path) {
return function($self, $params) use (&$path, $scope) {
if ($scope) {
$params['keys'] = $self->invokeMethod('_addScopePrefix', array(
$scope, $params['keys'], '_'
));
}
$results = array();

foreach ($params['keys'] as $key) {
Expand All @@ -136,6 +149,10 @@ public function read(array $keys) {
}
$results[$key] = preg_replace('/^\{\:expiry\:\d+\}\\n/', '', $data, 1);
}

if ($scope) {
$results = $self->invokeMethod('_removeScopePrefix', array($scope, $results, '_'));
}
return $results;
};
}
Expand All @@ -148,8 +165,14 @@ public function read(array $keys) {
*/
public function delete(array $keys) {
$path = $this->_config['path'];
$scope = $this->_config['scope'];

return function($self, $params) use (&$path) {
return function($self, $params) use (&$path, $scope) {
if ($scope) {
$params['keys'] = $self->invokeMethod('_addScopePrefix', array(
$scope, $params['keys'], '_'
));
}
foreach ($params['keys'] as $key) {
$file = new SplFileInfo($p = "{$path}/{$key}");

Expand Down

0 comments on commit 47e494f

Please sign in to comment.