Development

Documentation/it_IT/book/1.0/06-Interno-Controller-Layer

You must first sign up to be able to contribute.

Capitolo 6 - All'interno del Layer Controller

In symfony il controller, che contiene il codice che collega la business logic alla presentazione, è diviso in alcune componenti che puoi usare per finalità diverse:

  • Il front controller è l'unico punto di accesso all'applicazione. Carica la configurazione e determina l'azione da eseguire.
  • Le azioni contengono la logica applicativa. Controllano l'integrità della richiesta e preparano i dati di cui ha bisogno il layer di presentazione.
  • La richiesta, la risposta e gli oggetti di sessione forniscono accesso ai parametri di richiesta, agli header di risposta ed ai dati utente persistenti. Essi sono usati molto di frequente a livello controller.
  • I filtri sono porzioni di codice eseguiti per ogni richiesta, prima o dopo l'azione. Ad esempio, i filtri di sicurezza e validazione sono usati molto spesso in applicazioni web. Puoi estendere il framework creando i tuoi filtri.

Questo capitolo descrive queste componenti, ma non ti fare intimidire dal loro numero. Per una pagina di base non avrai bisogno d'altro se non scrivere qualche linea in un'azione. Gli altri componenti del controller verranno utilizzati in situazioni specifiche.

Il front controller

Tutte le richieste sono gestite da un unico front controller, che e' anche l'unico punto di accesso all'intera applicazione in un dato ambiente.

Quando il front controller riceve una richiesta, usa il sistema di routing per trovare la corrispondenza della URL che l'utente ha inserito (o clikkato) con una coppia azione/modulo. Ad esempio, l'URL seguente chiama lo script index.php (che e' il front controller) e sara' recepita come chiamata per l'azione myAction del modulo mymodule:

http://localhost/index.php/mymodule/myAction

Se non sei interessato alle fondamenta di symfony, questo e' tutto cio' che hai bisogno di sapere a proposito del front controller. E' una componente fondamentale dell'implementazione MVC in symfony, ma raramente avrai bisogno di modificarlo. Puoi saltare direttamente alla prossima sezione se non sei veramente interessato ai punti di forza del front controller.

Il lavoro del front controller in dettaglio

Il fatto che il front controller gestisca le richieste ha un significato un po' piu' profondo di capire solo quale azione va eseguita. Infatti, esso esegue il codice comune a tutte le azioni, incluso:

  1. Definire le costanti del core.
  2. Trova le librerie di symfony.
  3. Carica e instanzia le classi del core del framework.
  4. Carica la configurazione.
  5. Decodifica la URL di richiesta ed i parametri relativi per capire quale azione eseguire.
  6. Se l'azione non esiste, rediriziona all'azione di errore 404.
  7. Attiva i filtri (ad esempio se la richiesta necessita autenticazione).
  8. Esegue i filtri, primo passaggio.
  9. Esegue l'azione e renderizza la vista.
  10. Esegue i filtri, secondo passaggio.
  11. Output della risposta.

Il front controller di default

Il front controller di default, chiamato index.php e situato nella cartella web/ del progetto, e' un semplice file PHP come mostrato nel Listato 6-1.

Listato 6-1 - Front controller di default (ambiente di produzione)

<?php
 
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);
 
require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
 
sfContext::getInstance()->getController()->dispatch();

La definizione delle costanti corrisponde al primo step dell'elenco precedente. Poi il front controller include il file config.php, che si occupa dei punti da 2 a 4. La chiamata al metodo dispatch() dell'oggetto sfController (che e' il controller di core dell'architettura MVC di symfony) distribuisce le richieste, occupandosi dei punti da 5 a 7. Gli ultimi step sono gestiti dalla catena dei filtri, come descritto piu' avanti in questo capitolo.

Chiamare un altro front controller per cambiare ambiente

Esiste un front controller per ambiente. In effetti e' proprio l'esistenza di un front controller che definisce un ambiente. L'ambiente e' definito nella costante SF_ENVIRONMENT.

Per poter cambiare l'ambiente nel quale stai navigando l'applicazione, scegli semplicemente un altro front controller. I front controller di default quando crei una nuova applicazione con il comando symfony init-app si chiamano index.php per l'ambiente di produzione e myapp_dev.php per l'ambiente di test (quando la tua applicazione ovviamente si chiama myapp. La configurazione di default di mod_rewrite utilizzera' index.php quando non trovera' un riferimento ad un front controller nella URL. Cosi', entrambe le URL seguenti mostreranno la stessa pagina (mymodule/index) nell'ambiente di produzione:

http://localhost/index.php/mymodule/index
http://localhost/mymodule/index

e l'URL seguente mostra la stessa pagina nell'ambiente di sviluppo:

http://localhost/myapp_dev.php/mymodule/index

Creare un nuovo ambiente e' facile quanto creare un nuovo front controller. Ad esempio, potresti aver bisogno di un ambiente di test dove il tuo cliente puo' provare l'applicazione prima che vada in produzione. Per creare tale ambiente, copia semplicemente web/myapp_dev.php in web/myapp_staging.php, e cambia il valore della costante SF_ENVIRONMENT in staging. Ora, in tutti i file di configurazione, puoi aggiungere una nuova sezione staging: che contiene valori specifici per questo ambiente, come mostrato nel Listato 6-2.

Listato 6-2 - Esempio di app.yml con valori specifici per l'ambiente staging

staging:
  mail:
    webmaster:    dummy@mysite.com
    contact:      dummy@mysite.com
all:
  mail:
    webmaster:    webmaster@mysite.com
    contact:      contact@mysite.com

Se vuoi vedere come reagisce l'applicazione in questo ambiente, chiama il front controller relativo:

http://localhost/myapp_staging.php/mymodule/index

File batch

Potresti voler eseguire uno script da linea di comando (o tramite il cron) con pieno accesso a tutte le classi e funzionalita' di symfony, ad esempio per lanciare in batch e-mail di notifica oppure per aggiornare il tuo modello periodicamente. Per uno script del genere, devi includere le stesse linee con le quali comincia un front controller. Il Listato 6-3 mostra un esempio della parte iniziale di un file batch.

Listato 6-3 - Script bacth di esempio

<?php
 
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);
 
require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
 
// aggiungi codice qui

Come puoi vedere, l'unica linea mancante rispetto ad un front controller e' il metodo dispatch() dell'oggetto sfController, che puo' essere utilizzato esclusivamente con un web server e non in batch. Definire una applicazione ed un ambiente ti fornisce accesso ad una configurazione specifica. Includendo il config.php si inizia il contesto e l'autoloading.

La CLI di symfony possiede una funzionalita' init-batch, che crea automaticamente uno scheletro simile a quello del Listato 6-3 dentro la cartella batch/. Dagli semplicemente come argomenti un nome di applicazione, un nome di ambiente ed un nome per il file batch.

Azioni

Le azioni sono il cuore di un'applicazione, perche' esse ne contengono tutta la logica. Usano il modello e definiscono variabili per le viste. Quando esegui una richiesta in un'applicazione symfony, la URL definisce l'azione ed i parametri.

La classe Action

Le azioni sono metodi chiamati executeActionName della classe moduleNameActions che eredita da sfActions, e raggruppate per moduli. Le azioni di una classe sono memorizzate in un file chiamato actions.class.php dentro la cartella actions/ del modulo.

Il Listato 6-4 mostra un esempio di actions.class.php con la sola azione index per l'intero modulo mymodule.

Listato 6-4 - Esempio di azione, in apps/myapp/modules/mymodule/actions/actions.class.php

class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
 
  }
}

Anche se i nomi dei metodi non sono case-sensitive in PHP, lo sono in symfony. Per cui non dimenticare che il nome di un'azione deve cominciare con execute minuscolo, seguito dal nome esatto dell'azione con la prima lettera maiuscola.

Per poter richiedere un'azione, devi chiamare il front controller con il nome del modulo e dell'azione come parametri. Per default, questo si fa aggiungendo la coppia module_name/action_name allo script. Cio' significa che l'azione definita nel Listato 6-4 si puo' chiamare tramite:

http://localhost/index.php/mymodule/index

Aggiungere piu' azioni significa semplicemente aggiungere piu' metodi execute all'oggetto sfActions, come mostrato nel Listato 6-5.

Listato 6-5 - Classe con due azioni, in myapp/modules/mymodule/actions/actions.class.php

class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    ...
  }
 
  public function executeList()
  {
    ...
  }
}

Se la dimensione dell'azione cresce troppo, probabilmente avrai bisogno di fare un po' d'ordine nel codice (refactoring), spostandone qualche parte nel modello. Le azioni dovrebbero essere corte (non piu' di qualche riga), e la business logic di solito dovrebbe essere contenuta nel modello.

Inoltre, il numero di azioni in un modello potrebbe influenzare la decisione di dividere il modello stesso in due.

Sintassi alternativa per le azioni

Si puo' utilizzare anche una sintassi alternativa per poter dividere le azioni in file separati, un'azione per ogni file. In questo caso, ogni azione estende sfAction (invece di sfActions) e prende il nome actionNameAction. Il nome effettivo del metodo e' semplicemente execute. Il nome del file e' lo stesso di quello della classe. Cio' significa che il Listato 6-5 puo' essere scritto come nei Listati 6-6 e 6-7.

Listato 6-6 - File con azione singola, in myapp/modules/mymodule/actions/indexAction.class.php

class indexAction extends sfAction
{
  public function execute()
  {
    ...
  }
}

Listato 6-7 - File con azione singola, in myapp/modules/mymodule/actions/listAction.class.php

class listAction extends sfAction
{
  public function execute()
  {
    ...
  }
}

Recuperare informazioni nell'azione

La classe action offre un modo di accedere ad informazioni relative al controller ed al core di symfony. Il Listato 6-8 mostra come usarle.

Listato 6-8 - Metodi comuni di sfActions

class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    // Retrieving request parameters
    $password    = $this->getRequestParameter('password');
 
    // Retrieving controller information
    $moduleName  = $this->getModuleName();
    $actionName  = $this->getActionName();
 
    // Retrieving framework core objects
    $request     = $this->getRequest();
    $userSession = $this->getUser();
    $response    = $this->getResponse();
    $controller  = $this->getController();
    $context     = $this->getContext();
 
    // Setting action variables to pass information to the template
    $this->setVar('foo', 'bar');
    $this->foo = 'bar';            // Shorter version
 
  }
}

Terminazione dell'azione

Sono possibili diversi comportamenti alla conclusione dell'esecuzione di un'azione. Il valore di ritorno dal metodo dell'azione determina come la vista deve essere renderizzata. Le costanti della classe sfView sono utilizzate per specificare quale template deve essere utilizzata per visualizzare il risultato dell'azione.

Se c'e' una vista di default (come nella maggior parte dei casi), l'azione dovrebbe terminare nel modo seguente:

return sfView::SUCCESS;

In tale maniera symfony cerchera' un file chiamato actionNameSuccess.php. Questo e' anche il default, per cui se ometti il return nell'azione symfony cerchera' di nuovo la template actionNameSuccess.php. Anche azioni vuote provocano questo comportamento. Il Listato 6-9 mostra esempi di azioni che terminano con successo.

Listato 6-9 - Azioni che chiamano le template indexSuccess.php e listSuccess.php

public function executeIndex()
{
  return sfView::SUCCESS;
}
 
public function executeList()
{
}

Se ci fosse un errore da chiamare, l'azione dovrebbe terminare cosi':

return sfView::ERROR;

Per una vista personalizzata, utilizza questa terminazione:

return 'MyResult';

In questo modo symfony cerchera' la template actionNameMyResult.php..

Se non c'e' una vista da chiamare, come ad esempio un'azione chiamata in un file batch, l'azione dovrebbe terminare con:

return sfView::NONE;

In questo caso non sara' eseguita alcuna template. Cio' significa che puoi bypassare totalmente il layer vista, mostrando in output codice direttamente dall'azione. Come mostrato nel Listato 6-10, symfony fornisce un metodo specifico renderText() per questa eventualita'. Puo' tornare utile quando hai bisogno di molta velocita' in un'azione, come ad esempio con interazioni Ajax, come discusso nel Capitolo 11.

Listato 6-10 - Bypass della vista e stampa del risultato con sfView::NONE

public function executeIndex()
{
  echo "<html><body>Hello, World!</body></html>";
 
  return sfView::NONE;
}
 
// E' equivalente a
public function executeIndex()
{
  return $this->renderText("<html><body>Hello, World!</body></html>");
}

In qualche caso potresti aver bisogno di spedire una risposta vuota ma contenente qualche header (specialmente nel caso di header X-JSON). In tal caso definisci gli header tramite l'oggetto sfResponse, discusso nel prossimo capitolo, e ritorna la costante sfView::HEADER_ONLY, come mostrato nel Listato 6-11.

Listato 6-11 - Evitare il rendering della vista e spedire solo header

public function executeRefresh()
{
  $output = '<"title","My basic letter"],["name","Mr Brown">';
  $this->getResponse()->setHttpHeader("X-JSON", '('.$output.')');
 
  return sfView::HEADER_ONLY;
}

Nel caso in cui l'azione dovesse essere renderizzata da una template specifica, puoi ignorare il metodo return ed utilizzare invece il metodo setTemplate():

$this->setTemplate('myCustomTemplate');

Saltare ad un'altra azione

In qualche caso l'esecuzione di un'azione finisce richiedendo l'esecuzione di un'altra azione. Ad esempio, un'azione che gestisce i parametri di una richiesta in POST da una form, di solito redireziona ad un'altra azione dopo aver aggiornato il database. Un altro esempio e' l'alias di un'azione: index e' spesso un modo di visualizzare una lista, ed in effetti redireziona ad un'azione list.

La classe action fornisce due metodi per eseguirne un'altra:

  • Se l'azione redireziona ad un'altra azione:
    $this->forward('otherModule', 'index');
    
    
  • Se l'azione come risultato deve fare una redirezione web:
    $this->redirect('otherModule/index');
    $this->redirect('http://www.google.com/');
    
    

Il codice seguente ad una redirezione o ad un forward non viene mai eseguito. Puoi considerare queste chiamate equivalenti ad un return. Esse generano una chiamata sfStopException per fermare l'esecuzione dell'azione; tale eccezione e' catturata in seguito da symfony e semplicemente ignorata.

La scelta tra un forward ed un redirect qualche volta e' tricky. Per scegliere la soluzione migliore, tieni a mente che il forward e' interno all'applicazione e trasparente all'utente. Per quel che riguarda l'utente, l'URL visualizzata e' la stessa della richiesta. Per contro, un redirect e' un messaggio al browser dell'utente, che comprende una richiesta da quest'ultimo ed un cambiamento nell'URL che ne risulta alla fine.

Se l'azione e' chiamata dal submit di una form con method="post", dovresti sempre fare un redirect. Il vantaggio sta nel fatto che se l'utente aggiorna la pagina risultante, la richiesta non e' inoltrata di nuovo; inoltre, il pulsante "indietro" del browser si comportera' come l'utente si aspetta, ovvero mostrando la form e non chiedendo all'utente se vuole spedire di nuovo i dati in POST.

C'e' un tipo speciale di forward usato molto di frequente. Il metodo forward404() redireziona ad un'azione "pagina non trovata". Questo metodo e' chiamato ogni qualvolta un parametro necessario all'azione non e' presente nella richiesta (in questo modo viene rilevata una URL sbagliata). Il Listato 6-12 mostra un'esempio di azione show che si aspetta un parametro id.

Listato 6-12 - Utilizzo del metodo forward404()

public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
  if (!$article)
  {
    $this->forward404();
  }
}

Se stai cercando l'azione e la template dell'errore 404, le troverai nella cartella $sf_symfony_ data_dir/modules/default/.Puoi personalizzare questa pagina ggiungendo un nuovo modulo default nella tua applicazione, facendo cosi' l'override di quello del framework, e definendo una nuova azione error404 ed una nuova template error404Success. Alternativamente, puoi impostare le costanti error_404_module e error_404_action del file settings.yml per utilizzare un'azione esistente.

L'esperienza mostra che il piu' delle volte un'azione esegue un redirect od un forward dopo aver testato qualcosa, come nel Listato 6-12. Questo e' il motivo per cui la classe sfActions a qualche metodo in piu', come forwardIf(), forwardUnless(), forward404If(), forward404Unless(), redirectIf(), e redirectUnless(). Tali metodi accettano semplicemente un parametro che rappresenta una condizione, che porta all'esecuzione se vera (per i metodi xxxIf()) o falsa (per i metodi xxxUnless()), come mostrato nel Listato 6-13.

Listato 6-13 - Utilizzo del metodo forward404If()

// Questa azione e' equivalente a quella del Listato 6-12
public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
  $this->forward404If(!$article);
}
 
// Ed anche questa
public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
  $this->forward404Unless($article);
}

Utilizzare questi metodi non solo manterra' il tuo codice piu' corto, ma anche piu' leggibile.

Quando l'azione chiama il metodo forward404() od uno dei suoi compagni, symfony genera una sfError404Exception che gestisce la risposta 404. Questo significa che se tu avessi bisogno di visualizzare un messaggio 404 da una parte dalla quale non vuoi accedere al controller, puoi generare semplicemente una eccezione simile.

Ripetere codice per diverse azioni di un modulo

La convenzione di chiamare le azioni executeActionName() (nel caso della classe sfActions), oppure execute() (nel caso della classe sfAction) garantisce il fatto che symfony trovi il metodo dell'azione. Ti da' la possibilita' di aggiungere altri metodi che non saranno considerati come azioni, se il loro nome non comincia con execute.

C'e' un'altra convenzione molto utile nel caso in cui hai bisogno di ripetere diversi statement in ogni azione prima della sua effettiva esecuzione. Puoi inserirli nel metodo preExecute() della tua azione. Puoi indovinare da solo come fare la stessa cosa ma dopo che l'esecuzione e' avvenuta: inseriscili nel metodo postExecute(). La sintassi di questi metodi e' mostrata nel Listato 6-14.

Listato 6-14 - Utilizzare preExecute(), postExecute() e metodi personalizzati in un'azione

class mymoduleActions extends sfActions
{
  public function preExecute()
  {
    // Il codice inserito qui viene eseguito all'inizio di ogni chiamata dell'azione
    ...
  }
 
  public function executeIndex()
  {
    ...
  }
 
  public function executeList()
  {
    ...
    $this->myCustomMethod();  // I metodi della classe sono accessibili
  }
 
  public function postExecute()
  {
    // Il codice inserito qui viene eseguito alla fine di ogni chiamata dell'azione
    ...
  }
 
  protected function myCustomMethod()
  {
    // Puoi anche aggiungere tuoi metodi, se il loro nome non comincia con "execute"
    // In tal caso, meglio dichiararli private o protected
    ...
  }
}

Accedere alla richiesta

Hai gia' familiarita' con il metodo getRequestParameter('myparam'), utilizzato per ricevere un parametro di richiesta tramite il suo nome. In pratica, questo metodo e' un proxy per una catena di chiamate al gestore delle richieste getRequest()->getParameter('myparam'). L'azione ha accesso alla richiesta, chiamato sfWebRequest in symfony, ed a tutti i suoi metodi, tramite il metodo getRequest(). La Tabella 6-1 mostra i metodi sfWebRequest piu' utili.

Tabella 6-1 - Metodi dell'oggetto sfWebRequest

NomeFunzioneEsempio di output
Informazioni sulla richiesta
getMethod()Metodo della richiestaRestituisce costanti sfRequest::GET o sfRequest::POST
getMethodName()Nome del metodo della richiesta'POST'
getHttpHeader('Server')Valore di un dato header HTTP'Apache/2.0.59 (Unix) DAV/2 PHP/5.1.6'
getCookie('foo')Valore di un dato cookie'bar'
isXmlHttpRequest()*E' una richiesta Ajax?true
isSecure()E' una richiesta SSL?true
Parametri di richiesta
hasParameter('foo')C'e' quel parametro nella richiesta?true
getParameter('foo')Valore di un certo parametro'bar'
getParameterHolder()->getAll()Array con tutti i parametri della richiesta
Informazioni relative all'URI
getUri()URI completa'http://localhost/myapp_dev.php/mymodule/myaction'
getPathInfo()Informazioni sul path'/mymodule/myaction'
getReferer()**Referrer'http://localhost/myapp_dev.php/'
getHost()Nome host 'localhost'
getScriptName()Path e nome del front controller'myapp_dev.php'
Informazioni sul browser del client
getLanguages()Array di lingue accettateArray( [0] => fr [1] => fr_FR [2] => en_US [3] => en )
getCharsets()Array di charset accettatiArray( [0] => ISO-8859-1 [1] => UTF-8 [2] => * )
getAcceptableContentType()Array di content type accettatiArray( [0] => text/xml [1] => text/html
* Funziona solo con Prototype
** Alcune volte bloccato da proxy

La classe sfActions dispone di alcuni proxy per poter accedere piu' velocemente ai metdodi per le richieste, come mostrato nel Listato 6-15.

Listato 6-15 - Utilizzare i metodi sfRequest da un'azione

class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    $hasFoo = $this->getRequest()->hasParameter('foo');
    $hasFoo = $this->hasRequestParameter('foo');  // Versione abbreviata
    $foo     = $this->getRequest()->getParameter('foo');
    $foo     = $this->getRequestParameter('foo');  // Versione abbreviata
  }
}

Per richieste multipart nelle quali l'utente puo' allegare file, l'oggetto sfWebRequest fornisce un modo per accedere e muovere tali file, come mostrato nel Listato 6-16.

Listato 6-16 - L'oggetto sfWebRequest sa come gestire file allegati

class mymoduleActions extends sfActions
{
  public function executeUpload()
  {
    if ($this->getRequest()->hasFiles())
    {
      foreach ($this->getRequest()->getFileNames() as $fileName)
      {
        $fileSize  = $this->getRequest()->getFileSize($fileName);
        $fileType  = $this->getRequest()->getFileType($fileName);
        $fileError = $this->getRequest()->hasFileError($fileName);
        $uploadDir = sfConfig::get('sf_upload_dir');
        $this->getRequest()->moveFile('file', $uploadDir.'/'.$fileName);
      }
    }
  }
}

Non ti devi preoccupare di sapere se il tuo server supporta le variabili $_SERVER o $_ENV, o dei valori di default o di problemi di compatibilita'; i metodi sfWebRequest si occupano di tutto al posto tuo. Inoltre i loro nomi sono talmente evidenti da non rendere necessario andare a controllare la documentazione di PHP per capire come gestire la richiesta.

Sessione utente

Symfony gestisce automaticamente le sessioni utente, ed e' capace di mantenere dati persistenti fra le richieste dell'utente. Esso utilizza i meccanismi di gestione delle sessioni di PHP, e li migliora rendendoli piu' configurabili e piu' facili da usare.

Accedere alla sessione utente

La sessione dell'utente corrente e' disponibile nelle azioni tramite il metodo getUser(), che e' un'istanza della classe sfUser. Tale classe possiede una gestione di parametri che ti permette di memorizzare attributi dell'utente. Questi dati saranno disponibili anche alle altre richieste, fino alla scadenza della sessione utente, come mostrato nel Listato 6-17. Gli attributi utente possono contenere qualsiasi tipo di dati (stringhe, array, array associativi). Possono essere impostati per ogni singolo utente, anche se l'utente stesso non e' identificato.

Listato 6-17 - L'oggetto sfUser puo' contenere attributi dell'utente

class mymoduleActions extends sfActions
{
  public function executeFirstPage()
  {
    $nickname = $this->getRequestParameter('nickname');
 
    // Memorizza il dato nella sessione utente
    $this->getUser()->setAttribute('nickname', $nickname);
  }
 
  public function executeSecondPage()
  {
    // Recupera il dato dalla sessione utente con un valore di default
    $nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward');
  }
}

Potresti memorizzare oggetti nelle sessioni, ma e' fortemente sconsigliato. Questo perche' la sessione viene serializzata e memorizzata in un file durante le richieste. Quando la sessione viene de-serializzata, la classe che contiene l'oggetto deve essere gia' caricata, e questo non succede sempre. Inoltre, se tu memorizzi oggetti Propel, essi potrebbero andare in "stallo".

Come diversi altri getter in symfony, il metodo getAttribute() supporta un secondo parametro come valore di default, da utilizzare quando l'attributo non e' presente. Per controllare se un attrbuto e' stato definito per un utente, utilizza il metodo hasAttribute(). Gli attributi sono memorizzati in un gestore di parametri che puo' essere acceduto tramite il metodo getAttributeHolder(). Permette una facile pulizia degli attributi con i soliti metodi per la gestione di parametri, come mostrato nel Listato 6-18.

Listato 6-18 - Eliminare dati dalla sessione utente

class mymoduleActions extends sfActions
{
  public function executeRemoveNickname()
  {
    $this->getUser()->getAttributeHolder()->remove('nickname');
  }
 
  public function executeCleanup()
  {
    $this->getUser()->getAttributeHolder()->clear();
  }
}

Gli attributi della sessione utente sono anche disponibili nelle template tramite la variabile $sf_user, che contiene l'oggetto sfUser corrente, come mostrato nel Listato 6-19.

Listato 6-19 - Anche le template hanno accesso agli attributi utente in sessione

<p>
  Hello, <?php echo $sf_user->getAttribute('nickname') ?>
</p>

Se hai bisogno di memorizzare informazioni solo per la durata della richiesta corrente (ad esempio, per passare informazioni attraverso una catena di chiamate ad azioni), potresti preferire la classe sfRequest la quale possiede anch'essa i metodi has getAttribute() e setAttribute(). Solo gli attributi dell'oggetto sfUser sono persistenti fra le richieste.

Attributi flash

Un problema ricorrente con gli attributi utente e' la pulizia della sessione quando l'attributo non serve piu'. Ad esempio, potresti voler mostrare una conferma dopo che una form e' stata riempita e spedita. Dato che la gestione delle form nell'azione fa un redirect, l'unico modo per poter passare informazioni da tale azione a quella cui redireziona e' usare la sessione utente. Ma dopo che il messaggio e' stato visualizzato, bisognerebbe eliminare l'attributo; altrimenti rimarrebbe in sessione fino alla sua scadenza.

L'attributo flash e' un attributo effimero che puoi definire e poi dimenticare, certo che scomparira' alla richiesta successiva, lasciando la sessione pulita. Nell'azione lo puoi definire cosi':

$this->setFlash('attrib', $value);

La template sara' renderizzata e spedita all'utente, che fara' una richiesta per un'altra azione. In tale seconda azione, puoi recuperare il valore dell'attributo flash nella seguente maniera:

$value = $this->getFlash('attrib');

Dopodiche' te lo puoi dimenticare, in quanto dopo aver spedito la seconda pagina, l'attributo flash attrib sara' eliminato. Lo sara' anche se non ne hai bisogno nella seconda azione.

Se avessi bisogno di accedervi in una template, puoi usare l'oggetto $sf_flash:

<?php if ($sf_flash->has('attrib')): ?>
  <?php echo $sf_flash->get('attrib') ?>
<?php endif; ?>

o anche solo:

  <?php echo $sf_flash->get('attrib') ?>

Gli attributi flash sono un metodo molto pulito di passare informazioni alla richiesta successiva.

Gestione della sessione

Le funzionalita' della gestione delle sessioni di symfony nasconde completamente allo sviluppatore la memorizzazione degli ID di sessione server e client. Pero' se tu volessi modificare il comportamento di default di tale meccanismi lo potresti fare. E' per utenti avanzati.

Lato client, le sessioni sono gestite dai cookie. Il cookie di sessione di symfony si chiama symfony, ma ne potresti cambiare il nome nel file di configurazione factories.yml, come mostrato nel Listato 6-20.

Listato 6-20 - Cambiare il nome del cookie di sessione, in apps/myapp/config/factories.yml

all:
  storage:
    class: sfSessionStorage
    param:
      session_name: my_cookie_name

La sessione parte (tramite la funzione PHP session_start()) solo se il parametro auto_start e' impostato a true nel file factories.yml (come di default). Se vuoi avviare la sessione manualmente, disabilita tale parametro.

La gestione delle sessioni di symfony e' basata sulla gestione delle sessioni di PHP. Questo significa che se tu volessi le sessioni lato client gestite da URL invece di cookie, dovresti semplicemente cambiare il parametro use_trans_sid di php.ini. Questo e' pero' sconsigliato.

session.use_trans_sid = 1

Lato server, symfony gestisce le sessioni per default su file. Le puoi memorizzare su database cambiando il valore del parametro class dentro al file di configurazione factories.yml, come mostrato nel Listato 6-21.

Listato 6-21 - Modifica della memorizzazione delle sessioni, in apps/myapp/config/factories.yml

all:
  storage:
    class: sfMySQLSessionStorage
    param:
      db_table: SESSION_TABLE_NAME      # Nome della tabella in cui memorizzare le sessioni
      database: DATABASE_CONNECTION     # Nome della connessione di database da utilizzare

Le classi disponibili per la memorizzazione delle sessioni sono sfMySQLSessionStorage, sfPostgreSQLSessionStorage, e sfPDOSessionStorage; l'ultima e' la preferita. Il settaggio opzionale database definisce la connessione da usare; quindi symfony utilizzera' il file di configurazione databases.yml (vedi Capitolo 8) per determinare i settaggi (host, nome database, utente e pssword) da utilizzare.

Le sessioni scadono automaticamente dopo sf_timeout secondi. Questa costante e' impostata a 30 minuti per default, e puo' essere modificata per ogni ambiente nel file di configurazione settings.yml, come mostrato nel Listato 6-22.

Listato 6-22 - Cambiare il lifetime delle sessioni, in apps/myapp/config/settings.yml

default:
  .settings:
    timeout:     1800           # Lifetime delle sessioni in secondi

Sicurezza delle azioni

La possibilita' di eseguire un'azione puo' essere ristretta ad un certo insieme di utenti con particolari privilegi. Le funzionalita' fornite da symfony a tale scopo permettono di realizzare applicazioni sicure, dove gli utenti devono essere autenticati per poter accedere ad alcune parti dell'applicazione. Mettere un'applicazione in sicurezza richiede due step: dichiarare i requisiti di sicurezza per ogni azione e far accedere gli utenti con privilegi tali da poter utilizzare tali azioni.

Restrizione dell'accesso

Prima di essere eseguita, ogni azione passa da un filtro per verificare che l'utente corrente abbia i permessi di eseguirla. In symfony, i privilegi sono composti di due parti:

  • Azioni sicure necessitano che l'utente sia utenticato.
  • Le credenziali sono privilegi di sicurezza che permettono di organizzarla in gruppi.

Restringere l'accesso ad un'azione significa semplicemente creare e modificare un file chiamato security.yml dentro la cartella config/ del modulo. In questo file, puoi specificare i requisiti di sicurezza che gli utenti devono possedere per ogni azione o per tutte. Il Listato 6-23 ne mostra un esempio.

Listato 6-23 - Impostare restrizioni di accesso, in apps/myapp/modules/mymodule/config/security.yml

read:
  is_secure:   off       # Tutti gli utenti possono eseguire l'azione read

update:
  is_secure:   on        # L'azione update e' concessa solo ad utenti autenticati

delete:
  is_secure:   on        # Solo per utenti autenticati
  credentials: admin     # con credenziali di amministrazione

all:
  is_secure:  off        # off e' il valore di default

Le azioni non sono sicure per default, quindi se non c'e' un file security.yml oppure tale file esiste ma un'azione non vi e' menzionata, tale azione e' accessibile a chiunque. Se esiste il file security.yml, symfony cerca il nome dell'azione richiesta, e se la trova, ne controlla il soddisfacimento dei requisiti di sicurezza. Quello che succede quando un utente cerca di accedere ad una pagina protetta dipende dalla sue credenziali:

  • Se l'utente e' autenticato e possiede le giuste credenziali, l'azione e' eseguita.
  • Se l'utente non e' identificato, sara' rediretto all'azione di login di default.
  • Se l'utente e' autenticato ma non possiede le giuste credenziali, sara' rediretto all'azione di default mostrata in Figura 6-1.

Le pagine di login e protezione di default sono abbastanza semplici, e probabilmente vorrai modificarle. Puoi configurare quale azione deve essere chiamata in caso di privilegi insufficienti nel file settings.yml cambiando il valore della proprieta' mostrata nel Listato 6-24.

Figura 6-1 - Pagina protetta di default

Listato 6-24 - Le azioni di sicurezza di default sono definite in apps/myapp/config/settings.yml

all:
  .actions:
    login_module:           default
    login_action:           login

    secure_module:          default
    secure_action:          secure

Fornire accesso

Per poter accedere a certe azioni, gli utenti devono essere autenticati e/o possedere determinati privilegi. Puoi estendere i privilegi di un utente utilizzando i metodi dell'oggetto sfUser. Lo stato di autenticazione e' impostato tramite il metodo setAuthenticated(). Il Listato 6-25 mostra un semplice esempio di autenticazione.

Listato 6-25 - Impostare lo stato di autenticazione di un utente

class myAccountActions extends sfActions
{
  public function executeLogin()
  {
    if ($this->getRequestParameter('login') == 'foobar')
    {
      $this->getUser()->setAuthenticated(true);
    }
  }
 
  public function executeLogout()
  {
    $this->getUser()->setAuthenticated(false);
  }
}

Le credenziali sono leggermente piu' complicate da gestire, in quanto possono essere assegnate, rimosse od eliminate. Il Listato 6-26 descrive i metodi delle credenziali per la classe sfUser.

Listato 6-26 - Gestire le credenziali utente in azioni

class myAccountActions extends sfActions
{
  public function executeDoThingsWithCredentials()
  {
    $user = $this->getUser();
 
    // Assegnare una o piu' credenziali
    $user->addCredential('foo');
    $user->addCredentials('foo', 'bar');
 
    // Controllare se un utente possiede una credenziale
    echo $user->hasCredential('foo');                     =>   true
 
    // Controllare se un utente possiede almeno una delle credenziali
    echo $user->hasCredential(array('foo', 'bar'));       =>   true
 
    // Controllare se un utente possiede entrambe le credenziali
    echo $user->hasCredential(array('foo', 'bar'), true); =>   true
 
    // Rimuovere una credenziale
    $user->removeCredential('foo');
    echo $user->hasCredential('foo');                     =>   false
 
    // Rimuove tutte le credenziali (utile per il logout)
    $user->clearCredentials();
    echo $user->hasCredential('bar');                     =>   false
  }
}

Se l'utente possiede la credenziale 'foo', potra' accedere alle azioni per le quali il file security.yml richiede tale credenziale. Le credenziali possono essere utilizzate anche per mostrare contenuto protetto in una template, come mostrato nel Listato 6-27.

Listato 6-27 - Credenziali in una template

<ul>
  <li><?php echo link_to('section1', 'content/section1') ?></li>
  <li><?php echo link_to('section2', 'content/section2') ?></li>
  <?php if ($sf_user->hasCredential('section3')): ?>
  <li><?php echo link_to('section3', 'content/section3') ?></li>
  <?php endif; ?>
</ul>

Come per l'autenticazione, anche le credenziali sono apesso assegnate ad un utente durante il processo di login. Questo è il motivo per cui l'oggetto sfUser viene spesso esteso con i metodi login e logout, così da poter impostare lo stato di sicurezza di un utente in un unico punto centrale.

Tra i vari plugin di symfony, sfGuardPlugin estende la classe di sessione per facilitare login e logout. Controlla il Capitolo 17 per maggiori informazioni.

Credenziali complesse

La sintassi YAML utilizzata nel file security.yml ti permette di restringere l'accesso ad utenti che possiedono una combinazione di credenziali, utilizzando associazioni AND oppure OR. Tramite combinazioni del genere, puoi creare un workflow complesso ed un sistema di gestione dei privilegi; ad esempio, il back-office di un content management system (CMS) accessibile solo ad utenti con privilegi di amministrazione, mentre gli articoli possono essere modificati solo da utenti con credenziali editor e pubblicati solo da quelli con credenziali publisher. Il Listato 6-28 mostra questo esempio.

Listato 6-28 - Sintassi di combinazione di credenziali

editArticle:
  credentials: [ admin, editor ]              # admin AND editor

publishArticle:
  credentials: [ admin, publisher ]           # admin AND publisher

userManagement:
  credentials: [[ admin, superuser ]]         # admin OR superuser

Ogni volta che aggiungi un livello di parentesi quadre, la logica alterna tra AND e OR. In tal maniera puoi creare combinazioni di credenziali veramente complesse, come:

credentials: [[root, [supplier, [owner, quasiowner]], accounts]]
             # root OR (supplier AND (owner OR quasiowner)) OR accounts

Metodi per la validazione e per la gestione dell'errore

La validazione dell'input (per lo più parametri di richiesta) è un lavoro noioso e ripetitivo. Symfony dispone di un sistema per la validazione incorporato, utilizzando metodi delle azioni.

Cominciamo con un esempio. Quando un utente esegue una richiesta per myAction, per prima cosa symfony cerca sempre se esiste un metodo validateMyAction(). Se lo trova, lo esegue. Il valore di ritorno di tale metodo determina la prossima azione da eseguire: se true, viene eseguita executeMyAction(), altrimenti viene eseguita handleErrorMyAction(). Se anche quest'ultimo metodo non esiste, symfony cerca un metodo generico handleError(). Se infine anche questo non viene trovato, symfony restituisce semplicemente sfView::ERROR per eseguire la template myActionError. La Figura 6-2 mostra questo processo.

Figura 6-2 - Il processo di validazione

Quindi la chiave per la validazione è rispettare la convenzione dei nomi:

  • validateActionName è il metodo per la validazione, che resitituisce true o false. E' il primo metodo cercato quando viene richiesta l'azione ActionName. Se non esiste, l'azione viene chiamata direttamente.
  • handleErrorActionName è il metodo chiamato quando la validazione fallisce. Se non esiste, viene visualizzata la template Error.
  • executeActionName è il metodo dell'azione. Deve esistere per ogni azione.

Il Listato 6-29 mostra un esempio di azione con la validazione. Sia che la validazione abbia successo o fallisca, in questo esempio viene sempre visualizzata la template myActionSuccess.php, ma con parametri diversi.

Listato 6-29 - Esempio di metodo per la validazione

class mymoduleActions extends sfActions
{
  public function validateMyAction()
  {
    return ($this->getRequestParameter('id') > 0);
  }
 
  public function handleErrorMyAction()
  {
    $this->message = "Invalid parameters";
 
    return sfView::SUCCESS;
  }
 
  public function executeMyAction()
  {
    $this->message = "The parameters are correct";
  }
}

Puoi inserire il codice che preferisci nel metodo validate(). Devi essere semplicemente sicuro che esso restituisca o true o false. Come i metodi della classe sfActions, ha accesso agli oggetti sfRequest e sfUser, che possono essere molto utili per la validazione dell'input e del contesto.

Potresti utilizzare questo meccanismo per la validazione delle form (che altro non è se non il controllo dei dati inseriti prima di processarli), ma quello è proprio il task repetitivo e noiso per il quale symfony ha meccanismi automatici, come mostrato nel Capitolo 10.

Filtri

Il processo di sicurezza può essere interpretato come un filtro tramite il quale tutte le richieste devono passare prima di poter eseguire un'azione. A seconda di alcuni test eseguiti nel filtro, il processo della richiesta viene modificato: ad esempio azione da eseguire (nel caso di filtro di sicurezza default/secure invece dell'azione richiesta). Symfony estende quest'idea per filtrare le classi. Puoi specificare un qualsiasi numero di (classi) filtri da eseguire prima dell'esecuzione di un'azione o prima del rendering di una risposta, e farlo per ogni richiesta. Puoi vedere i filtri come un metodo per pacchettizzare codice, simile a preExecute() e postExecute(), ma ad un livello più alto (per l'intera applicazione invece che per l'intero modulo).

La catena dei filtri

Effettivamente symfony vede il processo di una richiesta come una catena di filtri. Quando il framework riceve una richiesta, esegue per primo sempre il filtro sfRenderingFilter). Ad un certo punto, esegue il secondo filtro, poi il seguente e così via. Quando viene eseguito l'ultimo filtro (che è sempre sfExecutionFilter), il filtro precedente può terminare, e così via fino al primo. La Figura 6-3 illustra tale concetto con un diagramma di sequenza, utilizzando una catena di filtri artificiale (quella reale ne contiene di più).

Figura 6-3 - Esempio di catena di filtri

Questo processo giustifica la struttura delle classi di filtri. Esse estendono tutte la classe sfFilter, e contengono un metodo execute(), che si aspetta come parametro un oggetto $filterChain. Da qualche parte in questo metodo, il filtro passa a quello successivo chiamando $filterChain->execute(). Il Listato 6-30 ne mostra un esempio. Quindi, fondamentalmente, i filtri sono divisi in due parti:

  • Il codice prima della chiamata $filterChain->execute() viene eseguito prima dell'azione.
  • Il codice dopo la chiamata $filterChain->execute() viene eseguito dopo l'azione ma prima del rendering.

Listato 6-30 - Struttura di una classe filtro.

class myFilter extends sfFilter
{
  public function execute ($filterChain)
  {
    // Codice da eseguire prima dell'azione
    ...
 
    // Esegue il filtro successivo nella catena
    $filterChain->execute();
 
    // Codice da eseguire dopo l'azione ma prima del rendering
    ...
  }
}

La catena di filtri di default è definita in un file di configurazione dell'applicazione chiamato filters.yml, ed è mostrato nel Listato 6-31. Questo file contiene un elenco dei filtri che devono essere eseguiti per ogni richiesta.

Listato 6-31 - Catena di filtri di default, in myapp/config/filters.yml

rendering: ~
web_debug: ~
security:  ~

# Generalmente puoi inserire i tuoi file qui

cache:     ~
common:    ~
flash:     ~
execution: ~

Queste dichiarazioni non hanno parametri (in YAML, la tilde, ~, significa "null"), perchè ereditano i parametri definiti nel core di symfony. All'interno del proprio core, infatti, symfony definisce settaggi class e param per ognuno di questi filtri. Ad esempio, il Listato 6-32 mostra i parametri di default per il filtro rendering.

Listato 6-32 - Parametri di default per il filtro rendering, in $sf_symfony_data_dir/config/filters.yml

rendering:
  class: sfRenderingFilter   # Classe
  param:                     # Parametri
    type: rendering

Lasciando un valore vuoto (~) nel file filters.yml dell'applicazione, dici a symfony di utilizzare i filtri con i parametri di default definiti nel core.

Puoi personalizzare la catena di filtri in diversi modi:

  • Disabilita alcuni filtri dalla catena aggiungendo un parametro enabled: off. Ad esempio, per disabilitare il filtro debug:
    web_debug:
      enabled: off
    
    
  • Non eliminare una riga da filters.yml per disabilitare un filtro; symfony in questo caso genererebbe un'eccezione.
  • Aggiungi le tue dichiarazioni da qualche parte nella catena (di solito dopo il filtro security) per inserire un filtro personalizzato (come discusso nella prossima sezione). Fai attenzione a che il filtro rendering sia sempre il primo ed il filtro execute sempre l'ultimo della catena.
  • Fai un override della classe e dei parametri di default del filtro di default (specialmente per modificare il sistema di sicurezza ed usare i tuoi filtri).

Il parametro enabled: off funziona bene per disabilitare i tuoi filtri, ma puoi disattivare i filtri di default tramite il file settings.yml, modificando i valori di web_debug, use_security, cache, e use_flash. Questo perchè ognuno dei filtri di default possiede un parametro condition che controlla i valori di tali impostazioni.

Costruire filtri personalizzati

E' abbastanza semplice costruire un filtro. Crea una classe simile a quella del Listato 6-30, e posizionala in una delle cartelle lib/ del progetto per approfittare della funzionalità di autoloading.

Dato che un'azione può fare un redirect od un forward ad un'altra azione e di conseguenza rilanciare tutta la catena di filtri, potresti voler restringere l'esecuzione dei tuoi filtri alla sola prima chiamata della richiesta. Il metodo isFirstCall() della classe sfFilter restituisce un booleano a questo scopo. Questa chiamata ha senso esclusivamente prima dell'azione execution.

Questi concetti sono più chiari con un esempio. Il Listato 6-33 mostra un filtro utilizzato per autenticare automaticamente utenti che possiedono uno specifico cookie MyWebSite?, che presumibilmente viene creato dall'azione di login. E' un modo rudimentale ma funzionante di implementare la funzionalità "ricordati di me" offerta spesso nelle form di login.

Listato 6-33 - Esempio di filtro, in apps/myapp/lib/rememberFilter.class.php

class rememberFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Esegue questo filtro solo una volta
    if ($this->isFirstCall())
    {
      // I filtri non hanno accesso diretto alle richieste ed agli oggetti utente
      // Per arrivarci devi passare dal contesto
      $request = $this->getContext()->getRequest();
      $user    = $this->getContext()->getUser();
 
      if ($request->getCookie('MyWebSite'))
      {
        // sign in
        $user->setAuthenticated(true);
      }
    }
 
    // Esegui un nuovo filtro
    $filterChain->execute();
  }
}

In qualche caso, invece di continuare l'esecuzione della catena di filtri, alla fine di un filtro avrai bisogno di fare un forward ad una data azione. sfFilter non possiede un metodo forward(), ma sfController si, per cui puoi fare semplicemente:

return $this->getController()->forward('mymodule', 'myAction');

La classe sfFilter possiede un metodo initialize(), eseguito quando l'oggetto filtro viene creato. Puoi fare un override di tale metodo nel tuo filtro personalizzato se hai bisogno di gestire parametri (definiti in filters.yml, come descritto in seguito).

Attivazione dei filtri e parametri

Creare un filtro non significa attivarlo. Devi aggiungere il tuo filtro alla catena, e per farlo devi dichiararlo nel file filters.yml, situato nella cartella config/ dell'applicazione o del modulo, come mostrato nel Listato 6-34.

Listato-34 - Esempio di attivazione di un filtro, in in apps/myapp/config/filters.yml

rendering: ~
web_debug: ~
security:  ~

remember:                 # I filtri necessitano di un nome univoco
  class: rememberFilter
  param:
    cookie_name: MyWebSite
    condition:   %APP_ENABLE_REMEMBER_ME%

cache:     ~
common:    ~
flash:     ~
execution: ~

Una volta attivato, il filtro viene eseguito ad ogni richiesta. Il file di configurazione dei filtri può contenere una o più definizioni di parametri sotto la chiave param. La classe ha la possibilità di ricevere i valori di tali parametri tramite il metodo getParameter(). Il Listato 6-35 ne mostra un esempio.

Listato 6-35 - Ricevere i parametri, in apps/myapp/lib/rememberFilter.class.php

class rememberFilter extends sfFilter
{
  public function execute ($filterChain)
  {
      ...
      if ($request->getCookie($this->getParameter('cookie_name')))
      ...
  }
}

Il parametro condition viene testato dalla catena di filtri per verificare se il filtro debba essere eseguito o meno. Quindi la dichiarazione del tuo filtro si potrebbe basare sulla configurazione di un'applicazione, come nel Listato 6-34. Il filtro remember verrà eseguito esclusivamente se nel tuo app.yml è presente la seguente impostazione:

all:
  enable_remember_me: on

Esempi di filtri

La funzionalità di filtro è utile nella ripetizione di codice per ogni azione. Ad esempio, nell'utilizzo di un sistema di tracking esterno, probabilmente avrai bisogno di inserire un pezzo di codice in ogni pagina dell'applicazione. Potresti metterlo nel layout globale, ma così sarebbe disponibile per tutte le applicazioni. Oppure, potresti metterlo in un filtro, come mostrato nel Listato 6-36, ed attivarlo a seconda del modulo.

Listato 6-36 - Filtro Google Analytics

class sfGoogleAnalyticsFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Prima dell'azione non c'è niente da eseguire
    $filterChain->execute();
 
    // Decora la risposta con il codice del tracker
    $googleCode = '
<script src="http://www.google-analytics.com/urchin.js"  type="text/javascript">
</script>
<script type="text/javascript">
  _uacct="UA-'.$this->getParameter('google_id').'";urchinTracker();
</script>';
    $response = $this->getContext()->getResponse();
    $response->setContent(str_ireplace('</body>', $googleCode.'</body>',$response->getContent()));
   }
}

Fai attenzione al fatto che il filtro nell'esempio non è perfetto, in quanto non dovrebbe aggiungere il tracker in risposte che non siano HTML.

Un altro esempio potrebbe essere un filtro che commuta la risposta in SSL, se non lo è già, come mostrato nel Listato 6-37.

Listato 6-37 - Filtro per comunicazione sicura

class sfSecureFilter extends sfFilter
{
  public function execute($filterChain)
  {
    $context = $this->getContext();
    $request = $context->getRequest();
    if (!$request->isSecure())
    {
      $secure_url = str_replace('http', 'https', $request->getUri());
      return $context->getController()->redirect($secure_url);
      // Non continuiamo con la catena dei filtri
    }
    else
    {
      // La richiesta è già sicura, proseguiamo
      $filterChain->execute();
    }
  }
}

Configurazione del modulo

Pochi comportamenti dei moduli si basano su configurazione. Per modificarli, devi creare un file chiamato module.yml nella cartella config/ del modulo, e definire le impoostazioni per ogni ambiente (o sotto l'header all: per tutti gli ambienti). Il Listato 6-38 ne mostra un esempio.

Listato 6-38 - Configurazione del modulo, in apps/myapp/modules/mymodule/config/module.yml

all:                 # Per tutti gli ambienti
  enabled:     true
  is_internal: false
  view_name:   sfPhpView

Il parametro enabled ti permette di disabilitare tutte le azioni di un modulo. Tutte le azioni vengono redirezionate a module_disabled_module/module_disabled_action (come impostato in settings.yml).

Il parametro is_internal ti permette di restringere a chiamate interne tutte le azioni di un modulo. Ad esempio, questo torna utile per tutte le azioni che spediscono e-mail, le quali dovrebbero essere disponibili alle altre azioni ma non direttamente dall'esterno.

Il parametro view_name definisce la classe di vista. Deve ereditare da sfView. Facendo l'override di questo parametro puoi utilizzare altri sistemi per le template, come ad esempio Smarty.

Sommario

In symfony, il controller è diviso in due parti: il front controller, unico punto di accesso all'applicazione per un dato ambiente, e le azioni, che contengono la logica delle pagine. Un'azione ha la capacità di determinare come sarà eseguita la propria vista, restituendo una delle costanti sfView. All'interno di un'azione, puoi manipolari diversi elementi del contesto, compresa la richiesta (sfRequest) e le sessioni utente (sfUser).

Unendo la potenze delle azioni, delle sessioni e della configurazione di sicurezza si ottiene un sistema completamente sicuro, con gestione di restrizioni e credenziali. I metodi special validate() e handleError() nelle azioni permettono la gestione della validazione. Ed i metodi perExecute() e postExecute() sono pensati per la riusabilità del codice in un modulo, mentre i filtri autorizzano la stessa riusabilità per tutte le applicazioni eseguendo il codice del controller ad ogni richiesta.