2013-07-05 17 views
5

Ho un set di classi che hanno l'abitudine di essere richiamate ripetutamente con gli stessi argomenti. Questi metodi generalmente eseguono richieste di database e costruiscono matrici di oggetti e simili, e quindi per eliminare questa duplicazione ho costruito un paio di metodi di memorizzazione nella cache da ottimizzare. Questi sono usati in questo modo:Il modo migliore per implementare un pattern di decoratore per la cache dei risultati del metodo in PHP

Prima di caching applicato:

public function method($arg1, $arg2) { 
$result = doWork(); 
return $result; 
} 

Dopo caching applicato:

public function method($arg1, $arg2, $useCached=true) { 
if ($useCached) {return $this->tryCache();} 
$result = doWork(); 
return $this->cache($result); 
} 

Purtroppo ora sono lasciato con il compito un po 'laboriosa di aggiungere manualmente questo a tutti i metodi- credo che questo sia un caso d'uso del pattern di decoratore ma non riesco a capire come implementarlo in un modo più semplice in PHP per questo caso.

Qual è il modo migliore per eseguire questa operazione, si spera che sia tutti i metodi in una di queste classi eseguano automaticamente questa operazione oppure devo semplicemente aggiungere una riga nel metodo ecc.?

Ho dato un'occhiata ai modi per sovrascrivere la dichiarazione di ritorno e tale, ma non posso davvero vedere nulla.

Grazie!

risposta

10

Se non avete bisogno di sicurezza Tipo, è possibile utilizzare un generico decoratore cache:

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 

Si potrebbe poi avvolgere un'istanza che ha bisogno di caching in questo Decorator in questo modo:

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

Ovviamente, il $cachedInstance non ha un metodo foo(). Il trucco qui è utilize the magic __call method to intercept all calls to inaccessible or non-existing methods e delegarli all'istanza decorata. In questo modo esponiamo l'intera API pubblica dell'istanza decorata tramite Decorator.

Come si può vedere, il metodo __call contiene anche il codice per verificare se esiste una cache definita per tale metodo. In tal caso, restituirà la chiamata al metodo memorizzato nella cache. In caso contrario, chiamerà l'istanza e memorizzerà il reso nella cache.

In alternativa, si passa un CacheBackend dedicato al Decorator invece di implementare la memorizzazione nella cache nel decoratore stesso. Il decoratore avrebbe quindi funzionato solo come mediatore tra l'istanza decorata e il back-end.

L'inconveniente di questo approccio generico è che il Cache Decorator non avrà il tipo di Istanza decorata.Quando il tuo codice utente si aspetta istanze di tipo Istanza, otterrai degli errori.


Se avete bisogno di decoratori type-safe, è necessario utilizzare il metodo "classico":

  1. creare un'interfaccia dell'istanza decorato API pubblica. Potete farlo manualmente o, se si tratta di un sacco di lavoro, utilizzare il mio Interface Distiller)
  2. Modificare le TypeHints su ogni metodo in attesa l'istanza decorato all'interfaccia
  3. Hanno l'istanza Decorato attuarlo.
  4. Avere il decoratore applicarla e delegare tutti i metodi per l'istanza decorato
  5. Modificare tutti i metodi che hanno bisogno di caching
  6. Ripetere l'operazione per tutte le classi che vogliono utilizzare il decoratore

In poche parole

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

L'inconveniente è che è più lavoro. Devi farlo per ogni classe che si suppone utilizzi il caching. Dovrai anche inserire il controllo sul backend della cache in ogni funzione (duplicazione del codice) e delegare eventuali chiamate che non richiedono la memorizzazione nella cache dell'istanza decorata (noioso e soggetto a errori).

+0

Violi Principio di sostituzione di Liskov, dare un'occhiata al progetto Proxy Manager, forse aiuta, mi ha aiutato – decebal

+0

@ decebal L'approccio usando '__call' fa ovviamente, ma ho esplicitamente menzionato che non è sicuro, quindi è implicito. Il decoratore "classico" non viola LSP perché implementa la stessa interfaccia. – Gordon

1

Utilizzare il metodo magico __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() 

Questo è usato con memoria interna, ma è possibile utilizzare il DB per questo e creare il proprio memorizzazione transitoria. Basta aggiungere _Cached o Cached a qualsiasi metodo esistente. Ovviamente, puoi cambiare la durata della vita e altro ancora.

Questa è solo una prova del concetto. C'è spazio per un miglioramento molto :)

+0

questo viola un po 'il principio di sostituzione di Liskov, ma è un buon esempio, mi rende curioso come implementereste apc e memcache? – decebal

0

Ecco un estratto da un articolo 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; 
    } 
} 

ha più senso per me in quanto offre in modo implementazione SOLIDO. Non sono un grande fan di implementare lo stesso con le annotazioni, preferirei qualcosa di più semplice.

Problemi correlati