5

我有一组类,它们习惯于用相同的参数重复调用。这些方法通常运行数据库请求并构建对象数组等,因此为了消除这种重复,我构建了几个缓存方法来优化。这些是这样使用的:

在应用缓存之前:

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);
}

不幸的是,我现在要完成手动将其添加到所有方法的稍微费力的任务——我相信这是装饰器模式的一个用例,但我不知道如何在 PHP 中以更简单的方式实现它这个案例。

最好的方法是什么,希望这些类中的所有方法都自动执行此操作,或者我只需在方法中添加一行等?

我已经查看了覆盖 return 语句等的方法,但实际上什么都看不到。

谢谢!

4

3 回答 3

12

如果您不需要 Type Safety,则可以使用通用缓存装饰器:

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

然后,您将需要缓存的实例包装到此装饰器中,如下所示:

$cachedInstance = new Cached(new Instance);
$cachedInstance->defineCachingForMethod('foo', 3600);

显然,$cachedInstance没有foo()方法。这里的诀窍是利用魔法__call方法来拦截对不可访问或不存在的方法的所有调用,并将它们委托给装饰实例。通过这种方式,我们通过装饰器公开了装饰实例的整个公共 API。

如您所见,该__call方法还包含检查是否为该方法定义的缓存的代码。如果是这样,它将返回缓存的方法调用。如果没有,它将调用实例并缓存返回。

或者,您将专用的 CacheBackend 传递给装饰器,而不是在装饰器本身中实现缓存。然后,装饰器将仅作为被装饰实例和后端之间的中介。

这种通用方法的缺点是您的缓存装饰器将没有装饰实例的类型。当您的消费代码需要 Instance 类型的实例时,您将收到错误。


如果您需要类型安全的装饰器,则需要使用“经典”方法:

  1. 创建装饰实例公共 API 的接口。您可以手动完成,或者,如果工作量很大,请使用我的Interface Distiller
  2. 将期望装饰实例的每个方法上的 TypeHint 更改为接口
  3. 让 Decorated 实例实现它。
  4. 让装饰器实现它并将任何方法委托给装饰实例
  5. 修改所有需要缓存的方法
  6. 对所有想要使用装饰器的类重复此操作

简而言之

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
    }
}

缺点是工作量更大。您需要为每个应该使用缓存的类执行此操作。您还需要将对缓存后端的检查放入每个函数(代码重复),并将不需要缓存的任何调用委托给装饰实例(繁琐且容易出错)。

于 2013-07-05T10:10:32.530 回答
1

使用__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()

这用于使用内部存储,但您可以为此使用数据库并创建自己的瞬态存储。只需附加_Cached或添加Cached到任何存在的方法。显然,你可以改变寿命等等。

这只是概念证明。有很大的改进空间:)

于 2013-07-05T10:04:32.457 回答
0

这是一篇关于 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;
   }
}

对我来说更有意义,因为它以 SOLID 实现方式提供。我不太喜欢用注释来实现相同的功能,我更喜欢更简单的东西。

于 2016-03-11T11:54:12.120 回答