Development

Documentation/it_IT/book/1.1/12-Caching

You must first sign up to be able to contribute.

Version 2 (modified by garak, 9 years ago)
corrette path immagini

Capitolo 12 - Caching

Uno dei metodi per velocizzare un'applicazione è quello di memorizzare pezzi di codice HTML, o anche pagine intere, per richieste future. Tale tecnica prende il nome di caching, e può essere utilizzata sia lato server che lato client.

Symfony offre un sistema di caching lato server molto flessibile. Permette di salvare l'intera risposta, il risultato di un'azione, un partial, un segmento di template in un file, attraverso un setup molto intuitivo basato su file YAML. Quando i dati sottostanti cambiano, puoi pulire facilmente parti della cache tramite la linea di comando od un'azione speciale. Symfony fornisce anche un semplice modo di controllare la cache lato client tramite gli header HTTP 1.1. Questo capitolo affronterà queste tematiche, e ti darà qualche suggerimento per monitorare i miglioramenti che il caching può portare alla tua applicazione.

Caching della risposta

Il principio del caching HTML è piuttosto semplice: parte o tutto il codice HTML spedito ad un utente dopo una richiesta può essere riutilizzato per richieste simili. Tale codice HTML viene memorizzato in una cartella particolare (in symfony nella cartella cache/), dove il front controller guarderà prima di eseguire un'azione. Se viene trovata una versione in cache, viene spedita senza eseguire l'azione, cosi' il processo viene velocizzato. Se non viene trovata alcuna versione, l'azione viene eseguita ed il suo risultato (la vista) viene memorizzato nella cartella di cache per le richieste future.

Dato che tutte le pagine possono contenere informazioni dinamiche, la cache HTML è disabilitata per default. Sta all'amministratore abilitarla per migliorare le performance.

Symfony gestisce tre tipi di cache HTML:

  • Cache di un'azione (con o senza layout)
  • Cache di un partial, di un component od un component slot
  • Cache di un fragment

I primi due vengono gestiti tramite file YAML. Il terzo tramite chiamate a helper nelle template.

Impostazioni globali della cache

Per ogni applicazione di un progetto, il meccanismo di cache HTML può essere abilitato o disabilitato (default), per ambiente, nell'impostazione cache del file settings.yml. Il Listato 12-1 mostra come abilitarla.

Listato 12-1 - Attivazione della cache, in frontend/config/settings.yml

dev:
  .settings:
    cache:                  on

Cache di un'azione

Le azioni che mostrano informazioni statiche (dati non dipendenti dal database o dalla sessione) od azioni che leggono dati da un database ma non lo modificano (tipicamente richieste GET) sono spesso ideali per la cache. La Figura 12-1 mostra quali elementi della pagina vengono messi in cache in questo caso: il risultato di un'azione (il suo template) od il risultato insieme al layout.

Figure 12-1 - Cache di un'azione

Cache di un'azione

Ad esempio, considera un'azione user/list che restituisce la lista degli utenti di un sito. A meno che un utente venga modificato, aggiunto o rimosso (e questo argomento sarà discusso in seguito nella sezione "Rimuovere oggetti dalla cache"), la lista sarà sempre la stessa, per cui è una buona candidata per la cache.

L'attivazione e le impostazioni della cache, azione per azione, sono definiti nel file cache.yml, dentro la cartella config/ del modulo. Il Listato 12-2 ne mostra un esempio.

Listato 12-2 - Attivare la cache per un'azione, in frontend/modules/user/config/cache.yml

list:
  enabled:     on
  with_layout: false   # Default value
  lifetime:    86400   # Default value

Questa configurazione definisce che la cache è attiva per l'azione list, ed il layout non viene incluso (che è il comportamento di default). Ciò significa che anche se una versione dell'azione viene trovata in cache, il layout (con partial e component) viene eseguito lo stesso. Se l'opzione with_layout viene impostata a true, anche il layout viene messo in cache con l'azione e non viene eseguito.

Per testare le impostazioni della cache, chiama l'azione nell'ambiente di sviluppo dal tuo browser:

http://myapp.example.com/frontend_dev.php/user/list

Noterai un bordo attorno all'area dell'azione nella pagina. La prima volta tale area ha un header blu, che significa che la pagina non proviene dalla cache. Aggiorna la pagina, e noterai che ora l'header è giallo, che significa che stavolta proveniva dalla cache (con un significativo decremento del tempo di risposta). Più avanti in questo capitolo saranno approfonditi i metodi per testare e monitorare la cache.

NOTE Gli slot sono parte dei template, quindi fare il cache di un'azione significa anche memorizzare il valore dello slot definito dal template dell'azione. Per cui la cache funziona nativamente per gli slot.

Il sistema di cache funziona anche per le pagine con argomenti. Il modulo user potrebbe avere, ad esempio, un'azione show che si aspetta un id per mostrare i dettagli di un utente. Modifica il file cache.yml per abilitare la cache anche per questo caso, come mostrato nel Listato 12-3.

Per organizzare i tuoi cache.yml, puoi raggruppare le impostazioni comuni a tutte le azioni di un modulo sotto la chiave all:, anch'essa mostrata nel Listato 12-3.

Listato 12-3 - Esempio di cache.yml, in frontend/modules/user/config/cache.yml

list:
  enabled:    on
show:
  enabled:    on

all:
  with_layout: false   # Default value
  lifetime:    86400   # Default value

Ora, ogni chiamata all'azione user/show con un id diverso produrrà nuovi record nella cache. Per cui la cache per:

http://myapp.example.com/user/show/id/12

sarà diversa da quella per:

http://myapp.example.com/user/show/id/25

CAUTION Azione chiamate in POST o GET non vengono memorizzate in cache.

L'impostazione with_layout merita qualche parola in più. Essa determina effettivamente che tipo di dati sono memorizzati nella cache. Per cache senza layout, vengono immagazzinati solo il risultato dell'esecuzione di un template e le variabili dell'azione. Per cache con layout, tutta la risposta viene memorizzata. Ciò significa che la cache con il layout è molto più veloce di quella senza.

Se funzionalmente ti puoi permettere di farlo (ovvero se il layout non si basa sulla sessione) dovresti scegliere sempre la cache con layout. Sfortunatamente, il layout contiene spesso elementi dinamici (ad esempio, il nome dell'utente connesso), per cui la cache senza layout è l'impostazione piu' comune. Comunque, i feed RSS, i pop-up, e le pagine che non dipendono dai cookie possono essere messe in cache con il loro layout.

Cache di un partial, component, o component slot

Il Capitolo 7 ha spiegato come riutilizzare frammenti di codice in diversi template, utilizzando l'helper include_partial(). Un partial è facile da mettere in cache quanto un'azione, e l'attivazione segue le stesse regole, come mostrato in Figura 12-2.

Figura 12-2 - Cache di un partial, component, o component slot

Cache di un partial, component, o component slot

Ad esempio, il Listato 12-4 mostra come modificare il file cache.yml per abilitare la cache di un partial _my_partial.php del modulo user. Nota che l'opzione with_layout non ha senso in questo caso.

Listato 12-4 - Cache di un partial, in frontend/modules/user/config/cache.yml

_my_partial:
  enabled:    on
list:
  enabled:    on
...

Ora tutti i template che usano questo partial non ne eseguiranno effettivamente il codice PHP, bensì utilizzeranno la versione in cache.

[php]
<?php include_partial('user/my_partial') ?>

Come per le azioni, anche il caching dei partial diventa importante quando il risultato di tale partial dipende da parametri. Il sistema di cache memorizzerà tante versioni del template quanti sono i parametri.

[php]
<?php include_partial('user/my_other_partial', array('foo' => 'bar')) ?>

TIP La cache di un'azione è più potente di quella di un partial, in quanto quando un'azione viene messa in cache il template non viene neanche eseguita; se il template contiene chiamate ai partial, tali chiamate non vengono eseguite. Perciò, il caching dei partial diventa utile quando non metti in cache l'azione chiamante o per partial inclusi nel layout.

Un piccolo promemoria dal Capitolo 7: un component è una leggera azione situata all'inizio del partial, ed un component slot è un component per il quale l'azione cambia a seconda delle azioni chiamanti. Questi due tipi di inclusioni sono molto simili ai partial, e supportano il caching nello stesso modo. Ad esempio, se il tuo layout globale include un component chiamato day con include_component('general/day') per mostrare la data odierna, per abilitarne la cache imposta il file cache.yml del modulo general nel modo seguente:

_day:
  enabled: on

Quando metti in cache un component od un partial, devi decidere se memorizzare una singola versione per tutte le template chiamanti oppure una versione per ognuna di esse. Per default, un component è memorizzato indipendentemente dalla template che lo chiama. Ma component contestuali, ad esempio quelli che visualizzano una barra laterale differente con ogni azione, dovrebbero essere memorizzati tante volte quante sono i template che li chiamano. Il sistema di cache gestisce questo caso, se tu imposti il parametro contextual a true, nel modo seguente:

_day:
  contextual: true
  enabled:   on

NOTE I componenti globali (quelli situati nella cartella templates/ dell'applicazione) possono essere messi in cache, se dichiari le loro impostazioni nel file cache.yml dell'applicazione.

Cache di un template fragment

La cache delle azioni si applica solo ad un loro sottoinsieme. Per le altre, ovvero quelle che aggiornano dati o mostrano nella template informazioni dipendenti dalla sessione, c'è ancora spazio per miglioramenti dovuti alla cache, ma in modo diverso. Symfony mette a disposizione un terzo tipo di cache, dedicato ai fragment dei template ed abilitato direttamente al loro interno. In questa modalità, l'azione viene sempre eseguita, e il template viene diviso in fragment ed essi messi in cache, come mostrato dalla Figura 12-3.

Figura 12-3 - Caching di un fragment

Caching a template fragment

Ad esempio, potresti avere una lista di utenti che mostrano un link all'utente acceduto per ultimo, e tale informazione è dinamica. L'helper cache() definisce le parti di una template che devono andare in cache. Vedi il Listato 12-5 per dettagli sulla sintassi.

Listato 12-5 - Usare l'helper cache(), in frontend/modules/user/templates/listSuccess.php

[php]
<!-- Codice eseguito ogni volta -->
<?php echo link_to('last accessed user', 'user/show?id='.$last_accessed_user_id) ?>

<!-- Codice in cache -->
<?php if (!cache('users')): ?>
  <?php foreach ($users as $user): ?>
    <?php echo $user->getName() ?>
  <?php endforeach; ?>
  <?php cache_save() ?>
<?php endif; ?>

Ecco come funziona:

  • Se viene trovata in cache una versione del fragment 'users', viene sostituito al codice tra le linee <?php if (!cache($unique_fragment_name)): ?> e <?php endif; ?>.
  • Altrimenti, il codice tra tali linee viene processato e salvato in cache, ed identificato con il nome $unique_fragment_name.

Il codice non incluso tra tali linee viene sempre processato e mai salvato in cache.

CAUTION L'azione (listnell'esempio) non deve avere la cache abilitata, altrimenti l'intera template verrebbe bypassata e la dichiarazione di cache del fragment ignorata.

L'aumento di velocità dovuto alla cache dei fragment non è significatico quanto quello dovuto al caching delle azioni, dato che l'azione viene sempre eseguita, il template parzialmente processata ed il layout sempre usato per la decoration.

Puoi dichiarare fragment addizionali nella stessa template, però devi dare ad ognuno di essi un nome unico, in modo che il sistema di cache riesca ad identificarli in seguito.

Come per le azioni ed i component, anche i fragment in cache possono accettare un lifetime come secondo parametro, specificato in secondi, per l'helper cache():

[php]
<?php if (!cache('users', 43200)): ?>

Se non ne viene specificato alcuno, viene usato il valore di default (86400 secondi, ovvero un giorno).

TIP Una maniera alternativa per rendere un'azione "cacheabile" è quella di inserire le variabili che la rendono dinamica nel suo pattern di routing. Ad esempio, se una home page mostra il nome dell'utente connesso, non può essere messa in cache a meno che la URL non ne contenga lo username. Un altro esempio è per le applicazioni internazionalizzate: se vuoi abilitare la cache di una pagina che ha diverse traduzioni, il codice della lingua deve in qualche modo essere incluso nell'URL. Questa scorciatoia moltiplicherà il numero delle pagine in cache, ma può essere di grande aiuto per velocizzare applicazioni pesantemente interattive.

Configurazione dinamica della cache

Il file cache.yml è un modo per definire le impostazioni della cache, ma ha l'inconveniente di essere fisso. Comunque, come al solito in symfony, puoi usare PHP invece di YAML, e questo ti permette di configurare la cache dinamicamente.

Perché vorresti poter cambiare la cache dinamicamente? Un buon esempio è una pagina il cui contenuto varia a seconda che un utente sia autenticato o meno, ma la sua URL rimane la stessa. Immagina una pagina article/show con un sistema di votazione per gli articoli. Tale funzionalità di votazione è disabilitata per gli utenti non autenticati; per loro, il link della votazione porta alla pagina di login. Questa versione della pagina può essere messa in cache. D'altra parte, per gli utenti autenticati, cliccare sul link di votazione scatena una richiesta in POST e crea un nuovo voto. Questa volta la cache deve essere disabilitata, in modo che symfony possa costruirla dinamicamente.

Il posto giusto per definire le impostazioni della cache dinamica è in un filtro eseguito prima di sfCacheFilter. Infatti, in symfony la cache non è altro che un filtro, proprio come la web debug toolbar e le funzionalità di sicurezza. Per abilitare la cache per la pagina article/show solo se l'utente non è autenticato, crea un conditionalCacheFilter nella cartella lib/ dell'applicazione, come mostrato nel Listato 12-6.

Listato 12-6 - Configurare la cache in PHP, in frontend/lib/conditionalCacheFilter.class.php

[php]
class conditionalCacheFilter extends sfFilter
{
  public function execute($filterChain)
  {
    $context = $this->getContext();
    if (!$context->getUser()->isAuthenticated())
    {
      foreach ($this->getParameter('pages') as $page)
      {
        $context->getViewCacheManager()->addCache($page['module'], $page['action'],array('lifeTime' => 86400));
      }
    }

    // Esegue il prossimo filtro
    $filterChain->execute();
  }
}

Devi registrare questo filtro nel file filters.yml prima di sfCacheFilter, come mostrato nel Listato 12-7.

Listato 12-7 - Registrare un filtro personalizzato, in frontend/config/filters.yml

...
security: ~

conditionalCache:
  class: conditionalCacheFilter
  param:
    pages:
      - { module: article, action: show }

cache: ~
...

Pulisci la cache (per auto-caricare il nuovo filtro), e la cache condizionale è pronta. Essa abiliterà la cache delle pagine definite nel parametro `pages}} solo per utenti non autenticati.

Il metodo addCache() dell'oggetto sfViewCacheManager si aspetta il nome di un modulo, di un'azione ed un array associativo con gli stessi parametri che definiresti in un file cache.yml. Ad esempio, se tu volessi definire che l'azione article/show debba essere messa in cache con il layout ed un lifetime di 3600 secondi, scrivi:

[php]
$context->getViewCacheManager()->addCache('article', 'show', array(
  'withLayout' => true,
  'lifeTime'   => 3600,
));

SIDEBAR Sistema di memorizzazione della cache alternativo

Per default, symfony memorizza i dati in file sul disco rigido del web server. Potresti voler immagazzinare i dai in memoria (ad esempio tramite memcache) oppure in un database (specialmente se vuoi condividere la cache tra più server o velocizzarne la rimozione). Puoi modificare facilmente tale impostazione in quanto è definita nel file factories.yml.

Il sistema di memorizzazione di default è la classe sfFileCache:

view_cache:
    class: sfFileCache
    param:
      automaticCleaningFactor: 0
      cacheDir:                %SF_TEMPLATE_CACHE_DIR%

Puoi sostituire la class con il tuo sistema di memorizzazione personalizzato o con una delle classi alternative di symfony (inclusi sfAPCCache, sfEAcceleratorCache, sfMemcacheCache, e sfSQLiteCache). I parametri definiti sotto la chiave param sono passati al costruttore della classe di cache come array associativo. Ogni metodo alternativo di memorizzazione deve implementare tutti i metodi che si trovano nella classe astratta sfCache. Per maggiori informazioni su questo argomento consulta il Capitolo 19.

Utilizzare la cache super veloce

Anche una pagina in cache coinvolge l'esecuzione di codice PHP. Per tali pagine, symfony carica la configurazione, costruisce la risposta e così via. Se tu fossi veramente sicuro che una pagina non cambierà per un certo periodo di tempo, potresti bypassare symfony completamente mettendone il codice HTML risultante direttamente dentro la cartella web/. Questo funziona grazie alle impostazioni mod_rewrite di Apache, supposto che le tue regole di routing specifichino pattern senza suffisso o terminanti con .html.

Puoi fare ciò a mano, pagina per pagina, con una semplice chiamata a linea di comando:

> curl http://myapp.example.com/user/list.html > web/user/list.html

Dopo questo, ogni volta che viene richiesta l'azione user/list, Apache trova la corrispondente pagina list.html e bypassa symfony completamente. La controparte è che non puoi controllare più il cashing della pagina tramite symfony (lifetime, delegazione automatica e così via), ma il guadagno in velocità è veramente impressionante.

Alternativamente, puoi usare il plugin di symfony sfSuperCache, che automatizza questo processo e supporta lifetime e pulizia della cache. Consulta il Capitolo 17 per maggiori informazioni sui plugin.

SIDEBAR Altre tattiche di velocizzazione

In aggiunta alla cache HTML, symfony possiede altri due meccanismi di cache, che sono completamente automatici e trasparenti allo sviluppatore. Nell'ambiente di produzione, la configurazione e le traduzioni delle template sono cachate nella cartelle myproject/cache/config/ e myproject/cache/i18n/ senza alcun intervento.

Gli acceleratori PHP (eAccelerator, APC, XCache e così via), anche chiamati moduli di caching opcode, incrementano le performance degli script PHP mettendoli in cache in uno stato compilato, in modo che l'overhead dovuto al parsing ed alla compilazione venga quasi completamente eliminato. Questo è particolarmente efficiente per le classi Propel, che contengono una grande quantità di codice. Questi acceleratori sono compatibili con symfony e possono facilmente triplicare la velocità di un'applicazione. Essi sono raccomandati in ambienti di produzione per qualsiasi applicazione symfony che abbia una grande audience.

Con un acceleratore PHP, puoi immagazzinare manualmente dati persistenti in memoria, evitando così lo stesso processo ad ogni richiesta, tramite la classe sfProcessCache. E se volessi memorizzare in cache il risultato di un'operazione molto impegnativa per la CPU, probabilmente userai l'oggetto sfFunctionCache. Consulta il Capitolo 18 per maggiori informazioni su questi meccanismi.

Rimuovere elementi dalla cache

Se gli script od i dati della tua applicazione cambiano, la cache conterrà informazioni scadute. Per evitare incoerenze e bug, puoi eliminare elementi dalla cache in diversi modi, a seconda delle tue esigenze.

Pulire l'intera cache

Il task cache:clear della linea di comando di symfony elimina l'intera cache (HTML, configurazione e i18N). Puoi passare degli argomenti per eliminare solo alcune parti, come mostrato dal Listato 12-8. Ricorda di chiamarlo solo dalla root di un progetto symfony.

Listato 12-8 - Eliminare la cache

// Eliminare l'intera cache
> php symfony cache:clear

// Sintassi abbreviata
> php symfony cc

// Eliminare solo la cache dell'applicazione frontend
> php symfony cache:clear --app=frontend

// Eliminare solo la cache HTML dell'applicazione frontend
> php symfony cache:clear --app=frontend --type=template

// Eliminare solo la configurazione in cache dell'applicazione frontend
// I tipi possibili sono config, i18n, routing, e template.
> php symfony cache:clear --app=frontend --type=config --env=prod

Eliminare parti specifiche della cache

Quando un database viene aggiornato, la cache delle azioni relative ai dati modificati deve essere cancellata. Potresti pulire l'intera cache, ma questo sarebbe uno spreco per tutte le azioni non relative alle modifiche del modello. Qui è dove il metodo remove() dell'ggetto sfViewCacheManager ci viene in aiuto. Esso si aspetta come argomento una URI interna (lo stesso tipo di parametro che passeresti a link_to()), ed elimina la relativa azione dalla cache.

Ad esempio, immagina che l'azione update del modulo user modifica le colonne dell'oggetto User. La versione in cache delle azioni list e show hanno bisogno di essere eliminate, altrimenti esse, con dati erronei, sarebbero visualizzate. Per gestire questo caso, usa il metodo remove(), come mostrato nel Listato 12-9.

Listato 12-9 - Eliminare la cache per una data azione, in modules/user/actions/actions.class.php

[php]
public function executeUpdate($request)
{
  // Aggiorna un utente
  $user_id = $request->getParameter('id');
  $user = UserPeer::retrieveByPk($user_id);
  $this->foward404Unless($user);
  $user->setName($request->getParameter('name'));
  ...
  $user->save();

  // Pulisci la cache per le azioni relative a tale utente
  $cacheManager = $this->getContext()->getViewCacheManager();
  $cacheManager->remove('user/list');
  $cacheManager->remove('user/show?id='.$user_id);
  ...
}

Eliminare partial, component e component slot è leggermente più complicato. Dato che gli puoi passare qualsiasi tipo di parametro (inclusi oggetti), è quasi impossibile modificare la loro versione in cache. Concentriamo la nostra attenzione sui partial, comunque la spiegazione vale anche per gli altri componenti delle template. Symfony identifica un partial in cache con un prefisso speciale (sf_cache_partial), il nome del modulo ed il nome del partial, più un hash di tutti i parametri usati per chiamarlo, come segue:

[php]
// Un partial chiamato da
<?php include_partial('user/my_partial', array('user' => $user) ?>

// Viene identificato in cache come
@sf_cache_partial?module=user&action=_my_partial&sf_cache_key=bf41dd9c84d59f3574a5da244626dcc8

In teoria, potresti rimuovere un partial in cache tramite il metodo remove() se tu sapessi il valore dei parametri hash usati per identificarlo, ma ciò è veramente impraticabile. Fortunatamente, se aggiungi un parametro sf_cache_key alla chiamata dell'helper include_partial(), puoi identificare il partial in cache con tale chiave. Come puoi vedere dal Listato 12-10, pulire la cache da un singolo partial (ad esempio per eliminare i dati in cache relativi ad uno User modificato) diventa facile.

Listato 12-10 - Rimozione di un partial dalla cache

[php]
<?php include_partial('user/my_partial', array(
  'user'         => $user,
  'sf_cache_key' => $user->getId()
) ?>

// Viene identificato in cache come
@sf_cache_partial?module=user&action=_my_partial&sf_cache_key=12

// Elimina _my_partial per uno specifico utente in cache con $cacheManager->remove('@sf_cache_partial?module=user&action=_my_partial&sf_cache_key='.$user->getId());

Non puoi usare questo metodo per eliminare dalla cache tutte le occorrenze di un partial. Imparerai come farlo piu' avanti in questo capitolo, nella sezione "Svuotare la cache manualmente".

Per eliminare dalla cache template fragment, usa lo stesso metodo remove(). La chiave che identifica il fragment nella cache è lo stesso prefisso sf_cache_partial, il nome del modulo, quello dell'azione ed il parametro sf_cache_key. Il Listato 12-11 ne mostra un esempio.

Listato 12-11 - Eliminare fragment dalla cache

[php]
<!-- Codice in cache -->
<?php if (!cache('users')): ?>
  ... // Whatever
  <?php cache_save() ?>
<?php endif; ?>

// Viene identificato in cache come
@sf_cache_partial?module=user&action=list&sf_cache_key=users

// Eliminalo con
$cacheManager->remove('@sf_cache_partial?module=user&action=list&sf_cache_key=users');

SIDEBAR La pulizia selettiva della cache può danneggiare il tuo cervello

La parte piu' complicata dell'operazione di pulizia della cache è capire quali azioni sono influenzate da un aggiornamento dei dati.

Ad esempio, immagina che la tua applicazione corrente abbia un modulo publication dove le pubblicazioni vengano elencate (azione list) e descritte singolarmente (azione show), insieme a qualche dettaglio sull'autore (istanza della classe User). Modificare un utente coinvolgerà tutte le descrizioni ed anche l'elenco. Ciò significa che devi aggiungere all'azione update del modulo user qualcosa come:

[php]
$c = new Criteria();
$c->add(PublicationPeer::AUTHOR_ID, $request->getParameter('id'));
$publications = PublicationPeer::doSelect($c);

$cacheManager = sfContext::getInstance()->getViewCacheManager();
foreach ($publications as $publication)
{
  $cacheManager->remove('publication/show?id='.$publication->getId());
}
$cacheManager->remove('publication/list');

Quando cominci ad usare la cache HTML, devi avere una visione chiara delle dipendenze tra modello e azioni, in modo che non accadano errori dovuti ad incomprensioni. Tieni in mente che tutte le azioni che modificano il modello dovrebbero contenere una manciata di chiamate al metodo remove(), se la cache HTML è usata da qualche parte nell'applicazione.

E, se non vuoi danneggiare il tuo cervello con analisi troppo complicate, puoi sempre svuotare l'intera cache ogni volta che aggiorni il modello.

Pulire diverse parti di cache contemporanamente (nuovo in symfony 1.1)

Il metodo remove() accetta chiavi con caratteri jolly. Ti permette di rimuovere diverse parti di cache con una sola chiamata. Per esempio puoi fare:

$cacheManager->remove('user/show?id=*');    // Remove for all user records

Un altro buon esempio è la gestione di applicazioni con diverse lingue, dove i codici delle lingue appaiono in tutte le URL. La URL per la pagina di un profilo utente dovrebbe essere così:

http://www.myapp.com/en/user/show/id/12

Per rimuovere un profilo utente in cache con id 12 in tutte le lingue, puoi fare semplicemente:

$cache->remove('user/show?sf_culture=*&id=12');

Questo funziona anche per i partial:

$cacheManager->remove('@sf_cache_partial?module=user&action=_my_partial&sf_cache_key=*');    // Remove for all keys

Il metodo remove() accetta due parametri in più, consentendoti di definire quali host e header Vary vuoi pulire nella cache. Questo perché symfony tiene una versione di cache per ogni host e header Vary, quindi due applicazioni che condividono lo stesso codice ma non lo stesso host usano cache diverse. Questo puà essere molto utile, ad esempio, quando un'applicazione interpreta il sottodominio come parametro di richiesta (come http://php.askeet.com e http://life.askeet.com). Se non vuoi passare gli ultimi due parametri, symfony pulirà la cache per l'host corrente e per tutti gli header Vary. Alternativamente, se vuoi pulire la cache per un altro host, richiama remove() come segue:

$cacheManager->remove('user/show?id=*');                     // Cancella i record per l'host corrente e tutti gli utenti
$cacheManager->remove('user/show?id=*', 'life.askeet.com');  // Cancella i record per l'host life.askeet.com e tutti gli utenti
$cacheManager->remove('user/show?id=*', '*');                // Cancella i record per tutti gli host e tutti gli utenti

Il metodo remove() funziona in tutte le strategie di caching che puoi definire in factories.yml (non solo sfFileCache, ma anche sfAPCCache, sfEAcceleratorCache, sfMemcacheCache, sfSQLiteCache, e sfXCacheCache).

Pulire la cache tra applicazioni (nuovo in symfony 1.1)

Pulire la cache tra applicazioni diverse può essere un problema. Per esempio, se l'amministratore modifica un record nella tabella utenti nell'applicazione backend, tutte le azioni che dipendono da quell'utente nell'applicazione frontend hanno bisogno di essere pulite dalla cache. Ma il gestore di cache view disponibile nell'applicazione backend non conosce le regole di routing dell'applicazione frontend (le applicazioni sono isolate tra loro). Quindi non puoi scrivere questo codice in backend:

$cacheManager = sfContext::getInstance()->getViewCacheManager(); // Recuper il gestore di cache view del backend
$cacheManager->remove('user/show?id=12');                        // Il percorso non viene trovato, perché il template è in cache nel frontend

La soluzione è inizializzare un oggetto sfCache a mano, con le stesse impostazioni del gestore di cache del frontend. Fortunatamente, tutte le classi della cache di symfony forniscono un metodo removePattern che fornisce lo stesso servizio del remove del gestore di cache di view.

Per esempio, se l'applicazione backend ha bisogno di pulire la cache per l'azione user/show nell'applicazione frontend per l'utente con id 12, può usare il seguente:

$frontend_cache_dir = sfConfig::get('sf_root_cache_dir').DIRECTORY_SEPARATOR.'frontend'.DIRECTORY_SEPARATOR.SF_ENV.DIRECTORY_SEPARATOR.'template';
$cache = new sfFileCache(array('cache_dir' => $frontend_cache_dir)); // Usa le stesse impostazioni definite in factories.yml del frontend
$cache->removePattern('user/show?id=12');

Per diverse strategie di caching, hai bisogno solo di cambiare l'inizializzazione dell'oggetto cache, ma il processo di pulizia resta lo stesso:

$cache = new sfMemcacheCache(array('prefix' => 'frontend'));
$cache->removePattern('user/show?id=12');

Testare e monitorare la cache

La cache HTML, se non gestita correttamente, può creare inconsistenze nella visualizzazione dei dati. Ogni volta che disabiliti la cache per un elemento, devi testarlo estensivamente e controllare la velocità di caricamento.

Building a Staging Environment

Il sistema di cache è incline ad errori nell'ambiente di produzione che non appaiono in quello di sviluppo, in quanto per default nell'ambiente di sviluppo la cache non è abilitata. Se abiliti la cache HTML per qualche azione, devi aggiungere un nuovo ambiente, chiamato di staging in questa sezione, con le stesse impostazioni dell'ambiente prod (quindi, con la cache abilitata), ma con web_debug impostato su on.

Per impostarlo, modifica il file settings.yml della tua applicazione aggiungendo all'inizio del file il codice del Listato 12-12.

Listato 12-12 - Impostazione di un ambiente di staging, in frontend/config/settings.yml

staging:
  .settings:
    web_debug:  on
    cache:      on

In aggiunta, crea un nuovo front controller copiando quello di produzione (probabilmente myproject/web/index.php) con nome frontend_staging.php. Modificalo per cambiare i valori di SF_ENVIRONMENT e SF_DEBUG come segue:

[php]
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'staging', true);

è tutto, hai un nuovo ambiente. Usalo aggiungendo il nome del front controller dopo il nome del dominio:

http://myapp.example.com/frontend_staging.php/user/list

Monitorare le performance

Il Capitolo 16 affronterà nel dettaglio la web debug toolbar e le sue funzioni. Comunque, dato che la toolbar offre informazioni importanti riguardo la cache, seguono alcune informazioni sulle sue funzioni per la cache.

Quando navighi una pagina che contiene elementi "cacheabili" (azioni, partial, fragment e cosi' via) la web debug toolbar (nell'angolo in alto a destra) mostra un pulsante per ignorare la cache (una freccetta verde arrotondata), come mostrato in Figura 12-4. Tale pulsante ricarica la pagina e forza la processione degli elementi in cache. Fai attenzione che questo non svuota la cache.

L'ultimo numero sul lato destro è la durata dell'esecuzione della richiesta. Se abiliti la cache in una pagina, questo numero dovrebbe diminuire il numero dei secondi necessari al suo caricamento, dato che symfony usa i dati della cache invece di riprocessare gli script. Puoi monitorare facilmente le prestazioni della cache con questo indicatore.

Figura 12-4 - La web debug toolbar per pagine che usano la cache

Web debug toolbar for pages using caching

La debug toolbar mostra anche il numero di query eseguite durante il processo della richiesta, ed i dettagli delle durate per categoria (clicca sul totale della durata per visualizzare i dettagli). Monitorare tali dati, insieme alla durata totale, ti aiuterà a capire i miglioramenti dovuti alla cache.

Benchmarking

La modalità di debug decrementa notevolmente la velocità della tua applicazione, dato che vengono loggati molto dati e resi disponibili alla toolbar. Per cui il tempo di calcolo visualizzato quando navighi l'ambiente staging non è rappresentativo per quello di produzione, dove la modalità di debug è off.

Per avere una migliore panoramica del tempo di processo di ogni richiesta, dovresti utilizzare strumenti per misurare le prestazioni, come Apache Bench o JMeter. Questi strumenti permettono il test del carico e forniscono 2 importanti tipi di informazioni: la media del tempo di caricamento di una singola pagina e la capacità massima del tuo server. La media del tempo di caricamento è molto utile per monitorare i miglioramenti delle performance dovuti all'utilizzo della cache.

Identificare parti della cache

Quando la web debug toolbar è abilitata, gli elementi in cache sono individuati in una pagina con un bordo rosso, ognuno avente un piccolo riquadro di informazione sull'angolo in alto a sinistra, come mostrato in Figura 12-5. Il riquadro ha uno sfondo blu se l'elemento è stato eseguito, o giallo se viene dalla cache. Cliccando sul link vedrai l'identificatore dell'elemento in cache, il suo lifetime, ed il tempo trascorso dall'ultima modifica. Questo ti aiuterà ad identificare i problemi nella gestione di elementi fuori dal contesto, per vedere quale elemento è stato creato e quale parte di una template puoi effettivamente mettere in cache.

Figura 12-5 - Identificazione di un elemento in cache

Identification for cached elements in a page

HTTP 1.1 e cache lato client

Il protocollo HTTP 1.1 definisce una manciata di header che possono essere di grande utilizzo per incrementare la velocità di un applicazione controllando il sistema di cache del browser.

Le specifiche HTTP 1.1 del World Wide Web Consortium (W3C, [http://www. w3.org/Protocols/rfc2616/rfc2616-sec14.html]) descrivono tali header in dettaglio. Se un'azione ha la cache abilitata, ed usa l'opzione with_layout, può utilizzare uno o piu' meccanismi descritti in questa sezione.

Anche se qualche browser degli utenti del tuo sito potrebbero non supportare HTTP 1.1, non vi è alcun rischio nell'utilizzare le funzionalità di cache del protocollo. Un browser che riceve header che non conosce semplicemente gli ignora, per cui sei incoraggiato ad usare i meccanismi di cache HTTP 1.1.

Inoltre, gli header HTTP 1.1 sono compresi anche dai proxy e dai server di cache. Anche se il browser di un utente non comprende il protocollo, ci potrebbe essere sul percorso fino ad esso un server che se ne avvantaggia.

Aggiungere un header ETag per evitare di rispedire contenuto non modificato

Quando la funzionalità ETag è abilitata, il web server aggiunge alla risposta un header speciale che contiene la firma della risposta stessa.

ETag: 1A2Z3E4R5T6Y7U

Il browser dell'utente memorizza tale firma, e la spedisce insieme alla richiesta la volta successiva in cui fabbisogna della stessa pagina. Se la nuova firma mostra che la pagina non è cambiata dalla richiesta precedente, il browser non rimanda indietro la risposta. Spedisce invece un header 304: Not modified, cosa che fa risparmiare tempo di CPU (ad esempio se la compressione gzip fosse abilitata) e banda (trasferimento della pagina) al server, e tempo (trasferimento della pagina) al client. Soprattutto, le pagine in cache con ETag sono piu' veloci da caricare di quelle senza.

In symfony, puoi abilitare la funzionalità ETag per l'intera applicazione nel file settings.yml. Ecco l'impostazione di default:

all:
  .settings:
    etag: on

Per azioni in cache con layout, la risposta viene presa direttamente dalla cartella cache/, cosa che velocizza maggiormente l'intero processo.

Aggiungere un header Last-Modified per evitare di rispedire contenuto ancora valido

Quando il server spedisce la risposta al browser, può aggiungere un header che specifica quando il contenuto della pagina è stato modificato l'ultima volta:

Last-Modified: Sat, 23 Nov 2006 13:27:31 GMT

I browser possono capire tale header, e quando richiedono nuovamente la stessa pagina, aggiungono un If-Modified di conseguenza:

If-Modified-Since: Sat, 23 Nov 2006 13:27:31 GMT

Il server può quindi confrontare il valore del client e quello restituito dalla propria applicazione. Se i due corrispondono, il server restituisce l'header 304: Not modified, risparmiando proprio come con ETag tempo di CPU e banda.

In symfony, puoi impostare l'header di risposta Last-Modified proprio come faresti per un altro header. Ad esempio, puoi usarlo in un'azione nel seguente modo:

[php]
$this->getResponse()->setHttpHeader('Last-Modified', $this->getResponse()->getDate($timestamp));

Questa data può essere effettivamente quella dell'ultimo aggiornamento dei dati della pagina, presa dal tuo database o dal tuo filesystem. Il metodo getDate() dell'oggetto sfResponse converte un timestamp in una data nel formato di cui necessiti nell'header Last-Modified (RFC1123).

Aggiungere l'header Vary per permettere versioni differenti di una pagina in cache

Un altro header HTTP 1.1 è Vary. Esso definisce da quali parametri dipende una pagina, ed è utilizzato da browser e proxy per costruire chiavi di cache. Ad esempio, se il contenuto di una pagina dipende da cookie, puoi impostare l'header Vary come segue:

Vary: Cookie

Molto spesso è difficile abilitare la cache sulle azioni a causa del fatto che la pagina potrebbe cambiare a seconda dei cookie, della lingua o qualcos'altro. Se per te non è un problema espandere la dimensione della cache, imposta correttamente l'header Vary. Ciò può essere fatto per l'intera applicazione o per azione, utilizzando il file di configurazione cache.yml od il relativo metodo sfResponse come segue:

[php]
$this->getResponse()->addVaryHttpHeader('Cookie');
$this->getResponse()->addVaryHttpHeader('User-Agent');
$this->getResponse()->addVaryHttpHeader('Accept-Language');

Symfony memorizzerà diverse versioni della pagina in cache per ogni valore di tali parametri. Questo aumenterà significativamente la dimensione della cache, ma ogni qualvolta il server riceverà una richiesta corrispondente a tali header, la risposta sarà presa dalla cache invece di essere processata. Si tratta di un grande strumento di aumento delle prestazioni per le pagine che variano solo in base agli header di richiesta.

Aggiungere un header Cache-Control per abilitare la cache lato client

Fino ad ora, anche aggiungendo gli header visti, il browser continua a spedire richieste al server anche quando possiede una versione in cache di una pagina. Puoi evitare questo comportamento aggiungendo gli header Cache-Control ed Expires alla risposta. Questi header per default sono disabilitati in PHP, ma symfony può farne l'override per evitare richieste non necessarie al server.

Come al solito, puoi scatenare tale comportamento chiamando un metodo dell'oggetto sfResponse. In un'azione, definisci il tempo massimo in secondi in cui una pagina dovrebbe essere messa in cache:

[php]
$this->getResponse()->addCacheControlHttpHeader('max_age=60');

Puoi anche specificare sotto quali condizioni una pagina può essere messa in cache, in modo da non lasciare memorizzati dati privati (come ad esempio un numero di conto corrente) in cache:

[php]
$this->getResponse()->addCacheControlHttpHeader('private=True');

utilizzando la direttiva HTTP Cache-Control, avrai la capacità di regolare i diversi meccanismi di cache fra il tuo server ed il browser client. Per maggiori dettagli su tali direttive, consulta le specifiche W3C di Cache-Control.

Un ultimo header può essere spedito tramite symfony: Expires.

[php]
$this->getResponse()->setHttpHeader('Expires', $this->getResponse()->getDate($timestamp));

CAUTION La conseguenza principale dell'abilitazione del meccanismo Cache-Control è che il tuo server non mostrerà tutte le richieste eseguite dagli utenti, ma solo quelle ricevute effettivamente. Se le performance migliorano, l'apparente popolarità del sito potrebbe diminuire nelle statistiche.

Sommario

Il sistema di cache fornisce accelerazioni variabili delle performance a seconda del tipo di cache scelta. Dal maggior guadagno al minimo, i tipi di cache sono i seguenti:

  • Super cache
  • Cache di azioni con layout
  • Cache di azioni senza layout
  • Cache di fragment nelle template

Inoltre è possibile mettere in cache anche partial e component.

Se il cambiamento di dati nel modello o nella sessione ti obbliga a svuotare la cache per una questione di coerenza, puoi farlo con fine granularità per ottimizzare le prestazioni, ovvero elimina solo gli elementi che sono cambiati e mantieni gli altri.

Ricorda di testare con maggior attenzione le pagine con cache abilitata, dato che potrebbero apparire nuovi bug se metti in cache gli elmenti sbagliati o se dimentichi di svuotarla quando aggiorni i dati sottostanti. Un ambiente di staging, dedicato al test della cache, è di grande utilità a questo scopo.

Infine, trai il meglio dagli header del protocollo HTTP 1.1 grazie alle funzionalità avanzate di symfony, con il coinvolgimento del client nelle operazioni di caching ed un ulteriore incremento delle prestazioni.