Skip to content

Instantly share code, notes, and snippets.

@jakzal
Created July 20, 2012 14:16
Show Gist options
  • Save jakzal/3150963 to your computer and use it in GitHub Desktop.
Save jakzal/3150963 to your computer and use it in GitHub Desktop.
Function cache
<?php
namespace Zalas\Cache;
use Doctrine\Common\Cache\AbstractCache;
/**
* This class can be used to cache the result and output of function and method calls.
*
* Loosly based on sfFuncionCache: http://trac.symfony-project.org/browser/branches/1.4/lib/cache/sfFunctionCache.class.php
*/
class FunctionCache
{
/**
* @var \Doctrine\Common\Cache\AbstractCache $cache
*/
protected $cache = null;
/**
* @var \Closure $cacheKeyCallback
*/
protected $cacheKeyCallback = null;
/**
* @var \Closure $serializeCallback
*/
protected $serializeCallback = null;
/**
* @var \Closure $serializeCallback
*/
protected $unserializeCallback = null;
/**
* @param \Doctrine\Common\Cache\AbstractCache $cache
*
* @return null
*/
public function __construct(AbstractCache $cache)
{
$this->cache = $cache;
$this->cacheKeyCallback = function ($callable, $arguments) {
return md5(serialize($callable).serialize($arguments));
};
}
/**
* @param mixed $callable
* @param array $arguments
* @param integer $lifeTime
*
* @return mixed
*/
public function call($callable, array $arguments = array(), $lifeTime = 0)
{
if (!is_callable($callable)) {
throw new \InvalidArgumentException(sprintf('The first argument must be a valid callable.'));
}
$cacheKey = $this->computeCacheKey($callable, $arguments);
if ($this->isCached($cacheKey)) {
return $this->unserialize($this->fetchFromCache($cacheKey));
}
$result = $this->doCall($callable, $arguments);
$this->saveInCache($cacheKey, $this->serialize($result), $lifeTime);
return $result;
}
/**
* @param string $callable
* @param array $arguments
*
* @return mixed
*/
protected function doCall($callable, $arguments)
{
return call_user_func_array($callable, $arguments);
}
/**
* @param \Closure $callback
*
* @return null
*/
public function setCacheKeyCallback(\Closure $callback)
{
$this->cacheKeyCallback = $callback;
}
/**
* @param \Closure $serializeCallback
* @param \Closure $unserializeCallback
*
* @return null
*/
public function setSerializeCallbacks(\Closure $serializeCallback, \Closure $unserializeCallback)
{
$this->serializeCallback = $serializeCallback;
$this->unserializeCallback = $unserializeCallback;
}
/**
* @param mixed $callable
* @param array $arguments
*
* @return string
*/
protected function computeCacheKey($callable, $arguments)
{
return call_user_func_array($this->cacheKeyCallback, array($callable, $arguments));
}
/**
* @param string $cacheKey
*
* @return boolean
*/
protected function isCached($cacheKey)
{
return $this->cache->contains($cacheKey);
}
/**
* @param string $cacheKey
*
* @return mixed
*/
protected function fetchFromCache($cacheKey)
{
return $this->cache->fetch($cacheKey);
}
/**
* @param string $cacheKey
* @param mixed $result
* @param integer $lifeTime
*
* @return boolean
*/
protected function saveInCache($cacheKey, $result, $lifeTime)
{
return $this->cache->save($cacheKey, $result, (int) $lifeTime);
}
/**
* @param mixed $value
*
* @return string|mixed
*/
protected function serialize($value)
{
if (!is_null($this->serializeCallback)) {
return call_user_func_array($this->serializeCallback, array($value));
}
return $value;
}
/**
* @param string $value
*
* @return mixed
*/
protected function unserialize($value)
{
if (!is_null($this->unserializeCallback)) {
return call_user_func_array($this->unserializeCallback, array($value));
}
return $value;
}
}
<?php
namespace Zalas\Cache\Tests;
use Zalas\Cache\FunctionCache;
class FunctionCacheTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Zalas\Cache\FunctionCache $functionCache
*/
private $functionCache = null;
/**
* @var \Doctrine\Common\Cache\ArrayCache $cache
*/
private $cache = null;
public function setUp()
{
$this->cache = $this->getMock('Doctrine\Common\Cache\ArrayCache');
$this->functionCache = new FunctionCache($this->cache);
}
public function testThatFunctionIsCalledFirstTime()
{
$cacheKey = md5(serialize('strtoupper').serialize(array('Hello')));
$this->cache->expects($this->once())->method('contains')->with($cacheKey)->will($this->returnValue(false));
$this->cache->expects($this->once())->method('save')->with($cacheKey, 'HELLO');
$result = $this->functionCache->call('strtoupper', array('Hello'));
$this->assertSame('HELLO', $result);
}
public function testThatCachedResultIsReturnedIfExists()
{
$cacheKey = md5(serialize('strtoupper').serialize(array('Hello')));
$this->cache->expects($this->once())->method('contains')->with($cacheKey)->will($this->returnValue(true));
$this->cache->expects($this->once())->method('fetch')->with($cacheKey)->will($this->returnValue('HELLO cached'));
$this->cache->expects($this->never())->method('save');
$result = $this->functionCache->call('strtoupper', array('Hello'));
$this->assertSame('HELLO cached', $result);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testThatFunctionHasToBeCallable()
{
$this->cache->expects($this->never())->method('contains');
$this->cache->expects($this->never())->method('save');
$result = $this->functionCache->call('something_unknown', array('Hello'));
}
public function testThatCacheKeyCallbackCanBeOverwritten()
{
$cacheKey = 'CUSTOM_KEY';
$this->functionCache->setCacheKeyCallback(function ($callable, $arguments) use ($cacheKey) {
return $cacheKey;
});
$this->cache->expects($this->once())->method('contains')->with($cacheKey)->will($this->returnValue(false));
$this->cache->expects($this->once())->method('save')->with($cacheKey, 'HELLO');
$result = $this->functionCache->call('strtoupper', array('Hello'));
$this->assertSame('HELLO', $result);
}
public function testThatLifeTimeCanBeDefined()
{
$cacheKey = md5(serialize('strtoupper').serialize(array('Hello')));
$this->cache->expects($this->once())->method('contains')->with($cacheKey)->will($this->returnValue(false));
$this->cache->expects($this->once())->method('save')->with($cacheKey, 'HELLO', 3600);
$result = $this->functionCache->call('strtoupper', array('Hello'), 3600);
$this->assertSame('HELLO', $result);
}
public function testThatSerializeCallbackCanBeOverwritten()
{
$cacheKey = md5(serialize('strtoupper').serialize(array('Hello')));
$this->cache->expects($this->once())->method('contains')->with($cacheKey)->will($this->returnValue(false));
$this->cache->expects($this->once())->method('save')->with($cacheKey, serialize('HELLO'));
$serializeCallback = function ($value) { return serialize($value); };
$unserializeCallback = function ($value) { return unserialize($value); };
$this->functionCache->setSerializeCallbacks($serializeCallback, $unserializeCallback);
$result = $this->functionCache->call('strtoupper', array('Hello'));
$this->assertSame('HELLO', $result);
}
public function testThatUnerializeCallbackCanBeOverwritten()
{
$cacheKey = md5(serialize('strtoupper').serialize(array('Hello')));
$this->cache->expects($this->once())->method('contains')->with($cacheKey)->will($this->returnValue(true));
$this->cache->expects($this->once())->method('fetch')->with($cacheKey)->will($this->returnValue(serialize('HELLO')));
$serializeCallback = function ($value) { return serialize($value); };
$unserializeCallback = function ($value) { return unserialize($value); };
$this->functionCache->setSerializeCallbacks($serializeCallback, $unserializeCallback);
$result = $this->functionCache->call('strtoupper', array('Hello'));
$this->assertSame('HELLO', $result);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment