Development

Documentation/it_IT/book/1.0/12-Caching

You must first sign up to be able to contribute.

Version 15 (modified by gscaglia, 10 years ago)
--

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 puo' essere riutilizzato per richieste simili. Tale codice HTML viene memorizzato in una cartella particolare (in symfony nella cartella cache/), dove il front controller guardera' 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 e' 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 puo' essere abilitato o disabilitato (default), per ambiente, nell'impostazione cache del file settings.yml. Il Listato 12-1 mostra come abilitarla.

Figura 12-1 - Cache di un'azione

dev:
  .settings:
    cache:                  on

Cache di un'azione

Le azioni che mostrano informazioni statiche (dati non dipendenti dal db 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 cachati in questo caso: il risultato di un'azione (la sua template) od il risultato insieme al layout.

Figure 12-1 - Caching an action

Caching an action

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 sara' discusso in seguito nella sezione "Rimuovere oggetti dalla cache"), la lista sara' sempre la stessa, per cui e' 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 myapp/modules/user/config/cache.yml

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

Questa configurazione definisce che la cache e' attiva per l'azione list, ed il layout non viene incluso (che e' il comportamento di default). Cio' 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/myapp_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 e' giallo, che significa che stavolta proveniva dalla cache (con un significativo decremento del tempo di risposta). Piu' avanti in questo capitolo saranno approfonditi i metodi per testare e monitorare la cache.

NOTE Gli slot sono parte delle template, quindi fare il cache di un'azione significa anche memorizzare il valore dello slot definito dalla 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 i settaggi 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 myapp/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 produrra' nuovi record nella cache. Per cui la cache per:

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

sara' 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 piu'. Essa determina effettivamente che tipo di dati sono memorizzati nella cache. Per cache senza layout, vengono immagazzinati solo il risultato dell'esecuzione di una template e le variabili dell'azione. Per cache con layout, tutta la risposta viene memorizzata. Cio' significa che la cache con il layout e' molto piu' 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 e' 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 diverse template, utilizzando l'helper include_partial(). Un partial e' 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

Caching a partial, component, or 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 myapp/modules/user/config/cache.yml

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

Ora tutte le 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 della 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 la template non viene neanche eseguita; se la 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 le 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 delle template ed abilitato direttamente al loro interno. In questa modalità, l'azione viene sempre eseguita, e la template viene divisa 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 myapp/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, la 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 myapp/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 myapp/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 (sfSQLiteCache ad esempio). I parametri definiti sotto la chiave param sono passati al metodo initialize() della tua classe 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 le API (http://www. symfony-project.com/api/symfony.html).

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 un file 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 clear-cache 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
> symfony clear-cache

// Sintassi abbreviata
> symfony cc

// Eliminare solo la cache dell'applicazione myapp
> symfony clear-cache myapp

// Eliminare solo la cache HTML dell'applicazione myapp
> symfony clear-cache myapp template

// Eliminare solo la configurazione in cache dell'applicazione myapp
> symfony clear-cache myapp config

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()
{
  // Aggiorna un utente
  $user_id = $this->getRequestParameter('id');
  $user = UserPeer::retrieveByPk($user_id);
  $this->foward404Unless($user);
  $user->setName($this->getRequestParameter('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/user/_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 cio' e' 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/user/_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 e' 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/user/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 puo' danneggiare il tuo cervello

La parte piu' complicata dell'operazione di pulizia della cache e' 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 coinvolgera' tutte le descrizioni ed anche l'elenco. Cio' significa che devi aggiungere all'azione update del modulo user qualcosa come:

[php]
$c = new Criteria();
$c->add(PublicationPeer::AUTHOR_ID, $this->getRequestParameter('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 e' 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.

Struttura delle cartelle della cache

La cartella cache/ della tua applicazione ha la seguente struttura:

cache/                 # sf_root_cache_dir
  [APP_NAME]/          # sf_base_cache_dir
    [ENV_NAME]/        # sf_cache_dir
      config/          # sf_config_cache_dir
      i18n/            # sf_i18n_cache_dir
      modules/         # sf_module_cache_dir
      template/        # sf_template_cache_dir
        [HOST_NAME]/
          all/

Le template in cache vengono memorizzate nella cartella [HOST_NAME] (dove i punti vengono sostituiti con underscore per compatibilita' con i file system), in una struttura di cartelle corrispondenti alle loro URL. Ad esempio, la template in cache di una pagina con indirizzo

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

sara' memorizzata in:

cache/myapp/prod/template/www_myapp_com/all/user/show/id/12.cache

Non dovresti scrivere i percorsi dei file direttamente nel codice; dovresti invece usare le relative costanti. Ad esempio, per recuperare il path assoluto della cartella template/ della tua applicazione corrente nell'ambiente corrente, usa sfConfig::get('sf_template_cache_dir').

Conoscere questa struttura di cartelle ti aiutera' nella pulizia manuale della cache.

Pulizia manuale della cache

Pulire la cache di diverse applicazioni potrebbe divenire un problema. Ad esempio, se un amministratore modifica un record della tabella user nell'applicazione backend, tutte le azioni dipendenti da questo utente nell'applicazione frontend dovrebbero essere rimosse dalla cache. Il metodo remove() si aspetta una URI interna, ma le applicazioni non sanno niente delle regole di routing di altre applicazioni (ogni applicazione e' isolata dalle altre), per cui non puoi usare tale metodo per pulire la cache.

La soluzione e' quella di eliminare manualmente i file della cartella cache/, a seconda del loro percorso. Ad esempio, se l'applicazione backend avesse bisogno di pulire la cache dell'azione user/show nell'applicazione frontend per l'utente di id 12, potrebbe usare il seguente codice:

[php]
$sf_root_cache_dir = sfConfig::get('sf_root_cache_dir');
$cache_dir = $sf_root_cache_dir.'/frontend/prod/template/www_myapp_com/all';
unlink($cache_dir.'/user/show/id/12.cache');

Ma questo codice non e' davvero soddisfacente. Questo comando pulira' solo la cache dell'ambiente corrente, e ti forza a scrivere nel percorso il nome dell'host e dell'ambiente. Per evitare queste limitazioni, puoi usare il metodo sfToolkit::clearGlob(). Esso accetta come parametro un pattern con wildcard. Per l'esempio precedente puoi usare:

[php]
$cache_dir = $sf_root_cache_dir.'/frontend/*/template/*/all';
sfToolkit::clearGlob($cache_dir.'/user/show/id/12.cache');

Questo metodo e' di grande utilita' anche quando devi pulire la cache di un'azione senza tener conto di certi parametri. Se ad esempio la tua applicazione gestisce diverse lingue, potresti aver scelto di avere il codice della lingua nell'URL. Per cui il link al profilo di un utente sarebbe fatto cosi':

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

Per rimuovere il profilo in cache di un utente con id 12 in tutte le lingue, devi semplicemente usare

[php]
sfToolkit::clearGlob($cache_dir.'/*/user/show/id/12.cache');

Testare e monitorare la cache

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

Building a Staging Environment

Il sistema di cache e' 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 e' 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 myapp/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 myapp_staging.php. Modificalo per cambiare i valori di SF_ENVIRONMENT e SF_DEBUG come segue:

[php]
define('SF_ENVIRONMENT', 'staging');
define('SF_DEBUG',        true);

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

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

TIP Invece di copiarne uno esistente, puoi creare un nuovo con la linea di comando di symfony. Ad esempio, per creare un ambiente staging per l'applicazione myapp, chiamato myapp_staging.php e dove SF_DEBUG e' true, chiama semplicemente symfony init-controller myapp staging myapp_staging.php true.

Monitorare le performance

Il Capitolo 16 affrontera' 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 e' 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 aiutera' a capire i miglioramenti dovuti alla cache.

Benchmarking

La modalita' di debug decrementa notevolmente la velocita' 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 e' rappresentativo per quello di produzione, dove la modalita' di debug e' 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 capacita' massima del tuo server. La media del tempo di caricamento e' molto utile per monitorare i miglioramenti delle performance dovuti all'utilizzo della cache.

Identificare parti della cache

Quando la web debug toolbar e' 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 e' 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 aiutera' ad identificare i problemi nella gestione di elementi fuori dal contesto, per vedere quale elemento e' 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 velocita' 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, puo' 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 e' alcun rischio nell'utilizzare le funzionalita' 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 funzionalita' ETag e' 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 e' 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 funzionalita' 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, puo' aggiungere un header che specifica quando il contenuto della pagina e' 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 puo' 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 puo' 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 e' Vary. Esso definisce da quali parametri dipende una pagina, ed e' 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 e' 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 e' un problema espandere la dimensione della cache, imposta correttamente l'header Vary. Cio' puo' 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 memorizzera' diverse versioni della pagina in cache per ogni valore di tali parametri. Questo aumentera' significativamente la dimensione della cache, ma ogni qualvolta il server ricevera' una richiesta corrispondente a tali header, la risposta sara' 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 puo' 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 puo' 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 capacita' 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 puo' essere spedito tramite symfony: Expires.

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

CAUTION La conseguenza principale dell'abilitazione del meccanismo Cache-Control e' che il tuo server non mostrera' tutte le richieste eseguite dagli utenti, ma solo quelle ricevute effettivamente. Se le performance migliorano, l'apparente popolarita' 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 e' 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 granularita' 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, e' di grande utilita' a questo scopo.

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