Created
July 20, 2012 14:16
-
-
Save jakzal/3150963 to your computer and use it in GitHub Desktop.
Function cache
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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