我有一组类,它们习惯于用相同的参数重复调用。这些方法通常运行数据库请求和构建对象数组等,因此为了消除这种重复,我构建了两种缓存方法来优化。这些是这样使用的:
应用缓存之前:
public function method($arg1, $arg2) {
$result = doWork();
return $result;
}
应用缓存后:
public function method($arg1, $arg2, $useCached=true) {
if ($useCached) {return $this->tryCache();}
$result = doWork();
return $this->cache($result);
}
不幸的是,我现在要手动将其添加到所有方法中,这项任务有点费力——我相信这是decorator模式的一个用例,但我不知道如何在PHP中以更简单的方式实现它。
最好的方法是什么?希望这些类中的所有方法都能自动做到这一点,或者我只需要在方法中添加一行等等?
我已经研究了覆盖return语句之类的方法,但实际上什么都看不到。
谢谢!
如果您不需要类型安全,您可以使用通用的缓存装饰器:
class Cached
{
public function __construct($instance, $cacheDir = null)
{
$this->instance = $instance;
$this->cacheDir = $cacheDir === null ? sys_get_temp_dir() : $cacheDir;
}
public function defineCachingForMethod($method, $timeToLive)
{
$this->methods[$method] = $timeToLive;
}
public function __call($method, $args)
{
if ($this->hasActiveCacheForMethod($method, $args)) {
return $this->getCachedMethodCall($method, $args);
} else {
return $this->cacheAndReturnMethodCall($method, $args);
}
}
// … followed by private methods implementing the caching
然后,您可以将一个需要缓存的实例包装到这个Decorator中,如下所示:
$cachedInstance = new Cached(new Instance);
$cachedInstance->defineCachingForMethod('foo', 3600);
显然,$cachedInstance
没有foo()
方法。这里的技巧是利用神奇的__call
方法来拦截对不可访问或不存在方法的所有调用,并将它们委托给装饰实例。通过这种方式,我们通过Decorator公开装饰实例的整个公共API。
如您所见,__call
方法还包含用于检查是否为该方法定义了缓存的代码。如果是,它将返回缓存的方法调用。如果没有,它将调用实例并缓存返回。
或者,将专用的CacheBackend传递给Decorator,而不是在Decorator本身中实现Caching。然后,Decorator将仅作为装饰实例和后端之间的中介器工作。
这种通用方法的缺点是缓存装饰器将不具有装饰实例的类型。当您的消费代码需要Instance类型的实例时,您将收到错误。
如果您需要类型安全的装饰器,则需要使用"经典"方法:
- 创建装饰实例公共API的接口。你可以手动完成,或者,如果工作量很大,可以使用我的Interface Distiller)
- 更改每个方法上的TypeHints,这些方法需要接口的装饰实例
- 让Decorated实例实现它
- 让Decorator实现它,并将任何方法委托给装饰实例
- 修改所有需要缓存的方法
- 对所有要使用decorator的类重复此操作
简而言之,
class CachedInstance implements InstanceInterface
{
public function __construct($instance, $cachingBackend)
{
// assign to properties
}
public function foo()
{
// check cachingBackend whether we need to delegate call to $instance
}
}
的缺点是工作量更大。对于每个应该使用缓存的类,都需要这样做。您还需要对每个函数的缓存后端进行检查(代码重复),并将任何不需要缓存的调用委托给修饰的实例(乏味且容易出错)。
使用__call
魔术方法
class Cachable {
private $Cache = array();
public function Method1(){
return gmstrftime('%Y-%m-%d %H:%M:%S GMT');
}
public function __call($Method, array $Arguments){
// Only 'Cached' or '_Cached' trailing methods are accepted
if(!preg_match('~^(.+)_?Cached?$~i', $Method, $Matches)){
trigger_error('Illegal Cached method.', E_USER_WARNING);
return null;
}
// The non 'Cached' or '_Cached' trailing method must exist
$NotCachedMethod = $Matches[1];
if(!method_exists($this, $NotCachedMethod)){
trigger_error('Cached method not found.', E_USER_WARNING);
return null;
}
// Rebuild if cache does not exist or is too old (5+ minutes)
$ArgumentsHash = md5(serialize($Arguments)); // Each Arguments product different output
if(
!isset($this->Cache[$NotCachedMethod])
or !isset($this->Cache[$NotCachedMethod][$ArgumentsHash])
or ((time() - $this->Cache[$NotCachedMethod][$ArgumentsHash]['Updated']) > (5 * 60))
){
// Rebuild the Cached Result
$NotCachedResult = call_user_func_array(array($this, $NotCachedMethod), $Arguments);
// Store the Cache again
$this->Cache[$NotCachedMethod][$ArgumentsHash] = array(
'Method' => $NotCachedMethod,
'Result' => $NotCachedResult,
'Updated' => time(),
);
}
// Deliver the Cached result
return $this->Cache[$NotCachedMethod][$ArgumentsHash]['Result'];
}
}
$Cache = new Cachable();
var_dump($Cache->Method1());
var_dump($Cache->Method1Cached()); // or $Cache->Method1_Cached()
sleep(5);
var_dump($Cache->Method1());
var_dump($Cache->Method1Cached()); // or $Cache->Method1_Cached()
这是使用内部存储使用的,但您可以使用DB来创建自己的瞬态存储。只需将_Cached
或Cached
附加到任何存在的方法即可。显然,你可以改变寿命甚至更多。
这只是概念的证明。还有很大的改进空间:)
以下是一篇关于php 中缓存主题的文章的摘录
/**
* Caching aspect
*/
class CachingAspect implements Aspect
{
private $cache = null;
public function __construct(Memcache $cache)
{
$this->cache = $cache;
}
/**
* This advice intercepts the execution of cacheable methods
*
* The logic is pretty simple: we look for the value in the cache and if we have a cache miss
* we then invoke original method and store its result in the cache.
*
* @param MethodInvocation $invocation Invocation
*
* @Around("@annotation(Annotation'Cacheable)")
*/
public function aroundCacheable(MethodInvocation $invocation)
{
$obj = $invocation->getThis();
$class = is_object($obj) ? get_class($obj) : $obj;
$key = $class . ':' . $invocation->getMethod()->name;
$result = $this->cache->get($key);
if ($result === false) {
$result = $invocation->proceed();
$this->cache->set($key, $result);
}
return $result;
}
}
对我来说更有意义,因为它以一种坚实的实现方式交付。我不太喜欢用注释实现同样的功能,我更喜欢更简单的东西。