Development

Documentation/it_IT/book/1.0/06-Inside-the-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 richiama 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]
<?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 del 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

Batch Files

Potresti voler eseguire uno script da linea di comando (o tramite il cron) con pieno accesso a tutte le classi e funzionalità 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]
<?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');

// add code here

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.

TIP 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.

Actions

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

[php]
class mymoduleActions extends sfActions
{
  public function executeIndex()
  {

  }
}

CAUTION 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

[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.

SIDEBAR Standard di codifica di symfony

Avrai notato negli esempi di questo libro che le parentesi graffe di apertura e chiusura ({ e }) occupano ognuna sempre una riga. Questa convenzione rende il codice piu' facile da leggere.

Fra gli altri standard di codifica del framework, l'indentazione e' sempre costituita da due spazi bianchi, non vengono utilizzate tabulazioni. Questo e' dovuto al fatto che le tabulazioni hanno valori diversi in termini di spazi bianchi a seconda dell'editor utilizzato, inoltre codice composto sia da spazi bianchi che da tabulazioni risulta impossibile da leggere.

Il core e i file generati da symfony non finiscono con il classico tag di chiusura ?>. Questo perche' cio' non e' realmente necessario, inoltre possono insorgere problemi nell'ouput se ad esempio ci fossero spazi bianchi dopo questo tag.

E se presti bene attenzione, noterai che nessuna linea finisce con uno spazio bianco in symfony. Questo per una ragione piu' banale questa volta: linee che finiscono con spazi bianchi si vedono molto male nell'editor di Fabien.

Sintassi alternativa per le classi Action

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

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

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

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

Recuperare informazioni dalla Action

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

[php]
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

  }
}

SIDEBAR Il contesto singleton

Hai già visto nel front controller, una chiamata a sfContext::getInstance(). In una azione, il metodo getContext() restituisce lo stesso singleton. E' un oggetto molto utile che contiene un riferimento a tutti gli oggetti di core di symfony relativi ad una data richiesta ed offre un modo per accedervi:

sfController: il controller (->getController()) sfRequest: la richiesta (->getRequest()) sfResponse: la risposta (->getResponse()) sfUser: la sessione utente (->getUser()) sfDatabaseConnection: la connessione al database (->getDatabaseConnection()) sfLogger: l'oggetto logger (->getLogger()) sfI18N: l'oggetto per l'internazionalizzazione (->getI18N())

Puoi richiamare il singleton sfContext::getInstance() da qualsiasi punto del codice.

Termine di una Action

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:

[php]
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 richiamano i template indexSuccess.php e listSuccess.php

[php]
public function executeIndex()
{
  return sfView::SUCCESS;
}

public function executeList()
{
}

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

[php]
return sfView::ERROR;

Symfony cercherà un template chiamato actionNameError.php.

Per una vista personalizzata, utilizza questa terminazione:

[php]
return 'MyResult';

Symfony cercherà un template chiamato 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:

[php]
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

[php]
public function executeIndex()
{
  echo "<html><body>Hello, World!</body></html>";

  return sfView::NONE;
}

// Is equivalent to
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

[php]
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():

[php]
$this->setTemplate('myCustomTemplate');

Passare ad un'altra Action

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:

    [php]
    $this->forward('otherModule', 'index');
    
  • Se l'azione come risultato deve fare una redirezione web:

    [php]
    $this->redirect('otherModule/index');
    $this->redirect('http://www.google.com/');
    

NOTE 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' delicata. 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()

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

TIP 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 aggiungendo 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 ha 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()

[php]
// This action is equivalent to the one shown in Listing 6-12
public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
  $this->forward404If(!$article);
}

// So is this one
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.

TIP 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 Action 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

[php]
class mymoduleActions extends sfActions
{
  public function preExecute()
  {
    // The code inserted here is executed at the beginning of each action call
    ...
  }

  public function executeIndex()
  {
    ...
  }

  public function executeList()
  {
    ...
    $this->myCustomMethod();  // Methods of the action class are accessible
  }

  public function postExecute()
  {
    // The code inserted here is executed at the end of each action call
    ...
  }

  protected function myCustomMethod()
  {
    // You can also add your own methods, as long as they don't start with "execute"
    // In that case, it's better to declare them as protected or private
    ...
  }
}

Accedere alla richiesta (Request)

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 all'oggetto della 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

Nome | Funzione | Output d'esempio -------------------------------- | -------------------------------------- | ----------------------------------------------------------------------- Request Information | | getMethod() | Metodo della richiesta | Restituisce le constanti di 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 Request Parameters | | hasParameter('foo') | Parametro presente nella richiesta? | true getParameter('foo') | Valore di un dato parametro | 'bar' getParameterHolder()->getAll() | Array di tutti i parametri richiesta | URI-Related Information | | getUri() | URI completo | 'http://localhost/myapp_dev.php/mymodule/myaction' getPathInfo() | Path info | '/mymodule/myaction' getReferer() | Referrer | 'http://localhost/myapp_dev.php/' getHost() | Host name | 'localhost' getScriptName() | Path e nome del Front controller | 'myapp_dev.php' Client Browser Information | | getLanguages() | Array delle lingue accettate | Array( [0] => fr [1] => fr_FR [2] => en_US [3] => en ) getCharsets() | Array dei charset accettati | Array( [0] => ISO-8859-1 [1] => UTF-8 [2] => * ) getAcceptableContentTypes() | Array dei content types accettati | Array( [0] => text/xml [1] => text/html

  • Funziona solo con Prototype

Alcune volte bloccato dai 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 dell'oggetto sfRequest da un'azione

[php]
class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    $hasFoo = $this->getRequest()->hasParameter('foo');
    $hasFoo = $this->hasRequestParameter('foo');  // Shorter version
    $foo    = $this->getRequest()->getParameter('foo');
    $foo    = $this->getRequestParameter('foo');  // Shorter version
  }
}

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 i files allegati

[php]
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

[php]
class mymoduleActions extends sfActions
{
  public function executeFirstPage()
  {
    $nickname = $this->getRequestParameter('nickname');

    // Store data in the user session
    $this->getUser()->setAttribute('nickname', $nickname);
  }

  public function executeSecondPage()
  {
    // Retrieve data from the user session with a default value
    $nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward');
  }
}

CAUTION 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

[php]
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

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

NOTE 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 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':

[php]
$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:

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

Dopodichè potrai dimenticarlo, 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]
<?php if ($sf_flash->has('attrib')): ?>
  <?php echo $sf_flash->get('attrib') ?>
<?php endif; ?>

oppure:

[php]
<?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 meccanismo 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

TIP 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 nel 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      # Name of the table storing the sessions
      database: DATABASE_CONNECTION     # Name of the database connection to use

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           # Session lifetime in seconds

Sicurezza nelle Action

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.

Restrizioni d'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       # All users can request the read action

update:
  is_secure:   on        # The update action is only for authenticated users

delete:
  is_secure:   on        # Only for authenticated users
  credentials: admin     # With the admin credential

all:
  is_secure:  off        # off is the default value anyway

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 il file security.yml esiste, 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 di default per azioni protette Figure 6-1 - The default secure action page

Pagina di default per azioni protette

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

Garantire l'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

[php]
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 nell'Action

[php]
class myAccountActions extends sfActions
{
  public function executeDoThingsWithCredentials()
  {
    $user = $this->getUser();

    // Add one or more credentials
    $user->addCredential('foo');
    $user->addCredentials('foo', 'bar');

    // Check if the user has a credential
    echo $user->hasCredential('foo');                      =>   true

    // Check if the user has both credentials
    echo $user->hasCredential(array('foo', 'bar'));        =>   true

    // Check if the user has one of the credentials
    echo $user->hasCredential(array('foo', 'bar'), false); =>   true

    // Remove a credential
    $user->removeCredential('foo');
    echo $user->hasCredential('foo');                      =>   false

    // Remove all credentials (useful in the logout process)
    $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 utente in un template

[php]
<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.

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

Credeziali 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

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

[php]
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 un task ripetitivo e noiso per il quale symfony ha meccanismi automatici, come mostrato nel Capitolo 10.

Filters

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, l'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 il primo filtro {che è sempre 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

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

[php]
class myFilter extends sfFilter
{
  public function execute ($filterChain)
  {
    // Code to execute before the action execution
    ...

    // Execute next filter in the chain
    $filterChain->execute();

    // Code to execute after the action execution, before the 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:  ~

# Generally, you will want to insert your own filters here

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   # Filter class
  param:                     # Filter parameters
    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:

  • Disabilitando alcuni filtri dalla catena aggiungendo il 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).

TIP 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.

Creare 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/ approfittando 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

[php]
class rememberFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Execute this filter only once
    if ($this->isFirstCall())
    {
      // Filters don't have direct access to the request and user objects.
      // You will need to use the context object to get them
      $request = $this->getContext()->getRequest();
      $user    = $this->getContext()->getUser();

      if ($request->getCookie('MyWebSite'))
      {
        // sign in
        $user->setAuthenticated(true);
      }
    }

    // Execute next filter
    $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:

[php]
return $this->getContext()->getController()->forward('mymodule', 'myAction');

NOTE La classe sfFilter possiede un metodo initialize(), eseguito quando viene creato l'oggetto filtro. 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 apps/myapp/config/filters.yml

rendering: ~
web_debug: ~
security:  ~

remember:                 # Filters need a unique name
  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

[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

Filtri d'esempio

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

[php]
class sfGoogleAnalyticsFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Nothing to do before the action
    $filterChain->execute();

    // Decorate the response with the tracker code
    $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

[php]
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);
      // We don't continue the filter chain
    }
    else
    {
      // The request is already secure, so we can continue
      $filterChain->execute();
    }
  }
}

I filtri sono utilizzati abbondantemente nei , dato che ti permettono di estendere le funzionalità di una applicazione globalmente. Controlla il Capitolo 17 per conoscere meglio i plug-ins, e controlla il wiki online (http://www.symfony-project.com/trac/wiki) per altri diltri d'esempio.

Configurazione del modulo

Pochi comportamenti dei moduli dipendono dalla 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:                 # For all environments
  enabled:     true
  is_internal: false
  view_name:   sfPHP

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 delle vista. Deve ereditare da sfView. Facendo l'override di questo parametro puoi utilizzare altri sistemi per le template, come ad esempio Smarty.

Riepilogo

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 l'oggetto richiesta (sfRequest) e l'oggetto sessioni utente (sfUser).

Unendo la potenza dell'oggetto sessioni, dell'oggetto azioni, e delle configurazioni di sicurezza si ottiene un sistema completamente sicuro, con gestione di restrizioni e credenziali. I metodi speciali validate() e handleError() delle 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.