2013-07-05 27 views
5

私は同じ引数で繰り返し呼び出される習慣を持つクラスのセットを持っています。これらのメソッドは一般的にデータベース要求を実行し、オブジェクトなどの配列を作成するので、この重複を取り除くために、最適化するためにいくつかのキャッシングメソッドを構築しました。これらは、そのように使用されていますPHPでメソッドの結果キャッシュにデコレータパターンを実装する最も良い方法

適用キャッシングする前に:

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で簡単に実装する方法を理解できません。

すべてのいずれかのクラスのメソッドが自動的にこれを行うか、メソッドなどに1行追加するだけでよいでしょうか?

return文などをオーバーライドする方法を見てきましたが、実際には何も見えません。

ありがとうございます!

答えて

10

あなたはタイプ安全を必要としない場合、あなたは一般的なキャッシュのデコレータを使用することができます。

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

$cachedInstancefoo()メソッドを持っていません。ここのテクニックはutilize the magic __call method to intercept all calls to inaccessible or non-existing methodsにあり、装飾されたインスタンスにそれらを委譲します。このようにして、Decoratorを介して装飾されたインスタンスのパブリックAPI全体を公開します。

ご覧のとおり、__callメソッドには、そのメソッドに定義されたキャッシングがあるかどうかをチェックするコードも含まれています。そうであれば、キャッシュされたメソッド呼び出しを返します。そうでなければ、インスタンスを呼び出してリターンをキャッシュします。

また、デコレータ自体でキャッシュを実装する代わりに、専用のCacheBackendをデコレータに渡すこともできます。デコレータは、装飾されたインスタンスとバックエンドの間のメディエータとしてのみ機能します。

この一般的なアプローチの欠点は、キャッシュデコレータがデコレートされたインスタンスのタイプを持たないことです。あなたの消費コードがInstance型のインスタンスを期待すると、エラーが発生します。


あなたは、タイプセーフなデコレータが必要な場合は、「古典的」なアプローチを使用する必要があります。装飾されたインスタンスのパブリックAPIのインターフェイスを作成

  1. を。それは多くの作業だ場合は、手動で行うか、することができ、装飾されたインスタンスは、それを実装したインターフェイス
  2. に装飾されたインスタンスを期待して、すべての方法で私のInterface Distiller
  3. 変更にTypeHintsを使用しています。
  4. はデコレータは一言で言えば

    でデコレータ

を使用するすべてのクラスの

  • 繰り返しキャッシュを必要とするすべてのメソッドを変更し、それを実装し、飾らインスタンス
  • に任意のメソッドを委譲持っています
    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 
        } 
    } 
    

    欠点は、それ以上の作業です。キャッシングを使用するすべてのクラスでこれを行う必要があります。また、チェックをキャッシュバックエンドにすべての関数(コードの重複)に配置するだけでなく、キャッシングが不要な呼び出しを装飾されたインスタンスに委譲する必要があります(冗長でエラーが発生しやすい)。ここで

  • +0

    リスコフ置換の原則に違反し、Proxy Managerプロジェクトを見て、助けてくれると助かりました – decebal

    +0

    @decebal '__call'を使ったアプローチは明らかですが、明示的にタイプセーフではないことが暗示されています。 「古典的な」デコレータは、同じインタフェースを実装しているためLSPに違反しません。 – Gordon

    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() 
    

    これは内部ストレージを使用して使用されていますが、このためにDBを使用して、独自の一時ストレージを作成することができます。存在する方法には、_CachedまたはCachedを追加するだけです。明らかに、あなたは寿命を変えることができます。

    これはまさに概念の証明です。多くの改善の余地があります:)

    +0

    これはLiskov置換の原則に少し違反していますが、これは良い例ですが、apcとmemcacheをどのように実装すればよいでしょうか? – decebal

    0

    は、SOLID実装方法で実現して記事around the subject of caching in 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; 
        } 
    } 
    

    からの抽出物が、私にはより多くの理にかなっています。 私は注釈で同じものを実装するのが大好きではなく、より簡単なものを好むでしょう。

    関連する問題