Sei sulla pagina 1di 708

Documentazione Symfony

Release 2.0

Fabien Potencier

02 April 2012

Indice

Giro rapido 1.1 Giro rapido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Libro 2.1 Libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 1 25 25

Ricettario 271 3.1 Ricettario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 Componenti 471 4.1 I componenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471 Documenti di riferimento 513 5.1 Documenti di riferimento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513 Bundle 637 6.1 Bundle di Symfony SE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637 Contributi 683 7.1 Contribuire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 683 701

Indice

ii

CAPITOLO 1

Giro rapido

Iniziare subito con il giro rapido di Symfony2:

1.1 Giro rapido


1.1.1 Un quadro generale
Volete provare Symfony2 avendo a disposizione solo dieci minuti? Questa prima parte di questa guida stata scritta appositamente: spiega come partire subito con Symfony2, mostrando la struttura di un semplice progetto gi pronto. Chi ha gi usato un framework per il web si trover come a casa con Symfony2. Suggerimento: Si vuole imparare perch e quando si ha bisogno di un framework? Si legga il documento Symfony in 5 minuti (http://symfony.com/symfony-in-ve-minutes).

Scaricare Symfony2 Prima di tutto, vericare di avere almeno PHP 5.3.2 (o successivo) installato e congurato correttamente per funzionare con un server web, come Apache. Pronti? Iniziamo scaricando Symfony2 Standard Edition (http://symfony.com/download), una distribuzione di Symfony precongurata per gli usi pi comuni e che contiene anche del codice che dimostra come usare Symfony2 (con larchivio che include i venditori, si parte ancora pi velocemente). Scaricare larchivio e scompattarlo nella cartella radice del web. Si dovrebbe ora avere una cartella Symfony/, come la seguente:
www/ <- cartella radice del web Symfony/ <- archivio scompattato app/ cache/ config/ logs/ Resources/ bin/ src/

Documentazione Symfony, Release 2.0

Acme/ DemoBundle/ Controller/ Resources/ ... vendor/ symfony/ doctrine/ ... web/ app.php ...

Nota: Se stata scaricata la Standard Edition senza venditori, basta eseguire il comando seguente per scaricare tutte le librerie dei venditori:
php bin/vendors install

Verica della congurazione Per evitare mal di testa successivamente, Symfony2 dispone di uno strumento per testare la congurazione, per vericare congurazioni errate di PHP o del server web. Usare il seguente URL per avviare la diagnosi sulla propria macchina:
http://localhost/Symfony/web/config.php

Se ci sono dei problemi, correggerli. Si potrebbe anche voler modicare la propria congurazione, seguendo le raccomandazioni fornite. Quando tutto a posto, cliccare su Bypass conguration and go to the Welcome page per richiedere la prima vera pagina di Symfony2:
http://localhost/Symfony/web/app_dev.php/

Symfony2 dovrebbe congratularsi per il duro lavoro svolto nora!

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

Capire i fondamenti Uno degli obiettivi principali di un framework quello di assicurare la Separazione degli ambiti (http://en.wikipedia.org/wiki/Separation_of_concerns). Ci mantiene il proprio codice organizzato e consente alla propria applicazione di evolvere facilmente nel tempo, evitando il miscuglio di chiamate al database, tag HTML e logica di business nello stesso script. Per raggiungere questo obiettivo con Symfony, occorre prima imparare alcuni termini e concetti fondamentali. Suggerimento: Chi volesse la prova che usare un framework sia meglio che mescolare tutto nello stesso script, legga il capitolo Symfony2 contro PHP puro del libro. La distribuzione offre alcuni esempi di codice, che possono essere usati per capire meglio i concetti fondamentali di Symfony. Si vada al seguente URL per essere salutati da Symfony2 (sostituire Fabien col proprio nome):
http://localhost/Symfony/web/app_dev.php/demo/hello/Fabien

1.1. Giro rapido

Documentazione Symfony, Release 2.0

Cosa sta accadendo? Dissezioniamo lURL: app_dev.php: un front controller. lunico punto di ingresso dellapplicazione e risponde a ogni richiesta dellutente; /demo/hello/Fabien: il percorso virtuale alla risorsa a cui lutente vuole accedere . responsabilit dello sviluppatore scrivere il codice che mappa la richiesta dellutente (/demo/hello/Fabien) alla risorsa a essa associata (la pagina HTML Hello Fabien!).
Rotte

Symfony2 dirige la richiesta al codice che la gestisce, cercando la corrispondenza tra lURL richiesto e alcuni schemi congurati. Per impostazione predenita, questi schemi (chiamate rotte) sono denite nel le di congurazione app/config/routing.yml. Se si nellambiente dev, indicato dal front controller app_**dev**.php, viene caricato il le di congurazione app/config/routing_dev.yml. Nella Standard Edition, le rotte delle pagine di demo sono in quel le:
# app/config/routing_dev.yml _welcome: pattern: / defaults: { _controller: AcmeDemoBundle:Welcome:index } _demo: resource: "@AcmeDemoBundle/Controller/DemoController.php" type: annotation prefix: /demo # ...

Le prime righe (dopo il commento) deniscono quale codice richiamare quanto lutente richiede la risorsa / (come la pagina di benvenuto vista prima). Quando richiesto, il controllore AcmeDemoBundle:Welcome:index sar eseguito. Nella prossima sezione, si imparer esattamente quello che signica.

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

Suggerimento: La Standard Edition usa YAML (http://www.yaml.org/) per i suoi le di congurazione, ma Symfony2 supporta nativamente anche XML, PHP e le annotazioni. I diversi formati sono compatibili e possono essere usati alternativamente in unapplicazione. Inoltre, le prestazioni dellapplicazione non dipendono dal formato scelto, perch tutto viene messo in cache alla prima richiesta.

Controllori

Il controllore una funzione o un metodo PHP che gestisce le richieste in entrata e restituisce delle risposte (spesso codice HTML). Invece di usare variabili e funzioni globali di PHP (come $_GET o header()) per gestire questi messaggi HTTP, Symfony usa degli oggetti: Symfony\Component\HttpFoundation\Request e Symfony\Component\HttpFoundation\Response. Il controllore pi semplice possibile potrebbe creare la risposta a mano, basandosi sulla richiesta:
use Symfony\Component\HttpFoundation\Response; $name = $request->query->get(name); return new Response(Hello .$name, 200, array(Content-Type => text/plain));

Nota: Symfony2 abbraccia le speciche HTTP, che sono delle regole che governano tutte le comunicazioni sul web. Si legga il capitolo Symfony2 e fondamenti di HTTP del libro per sapere di pi sullargomento e sulle sue potenzialit. Symfony2 sceglie il controllore basandosi sul valore _controller della congurazione delle rotte: AcmeDemoBundle:Welcome:index. Questa stringa il nome logico del controllore e fa riferimento al metodo indexAction della classe Acme\DemoBundle\Controller\WelcomeController:
// src/Acme/DemoBundle/Controller/WelcomeController.php namespace Acme\DemoBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class WelcomeController extends Controller { public function indexAction() { return $this->render(AcmeDemoBundle:Welcome:index.html.twig); } }

Suggerimento: Si sarebbero potuti usare i nomi completi di classe e metodi, Acme\DemoBundle\Controller\WelcomeController::indexAction, per il valore di _controller. Ma se si seguono alcune semplici convenzioni, il nome logico pi breve e consente maggiore essibilit. La classe WelcomeController estende la classe predenita Controller, che fornisce alcuni utili metodi scorciatoia, come il metodo :method:Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::render, che carica e rende un template (AcmeDemoBundle:Welcome:index.html.twig). Il valore restituito un oggetto risposta, popolato con il contenuto resto. Quindi, se ci sono nuove necessit, loggetto risposta pu essere manipolato prima di essere inviato al browser:
public function indexAction() {

1.1. Giro rapido

Documentazione Symfony, Release 2.0

$response = $this->render(AcmeDemoBundle:Welcome:index.txt.twig); $response->headers->set(Content-Type, text/plain); return $response; }

Indipendentemente da come lo si raggiunge, lo scopo nale del proprio controllore sempre quello di restituire loggetto Response da inviare allutente. Questo oggetto Response pu essere popolato con codice HTML, rappresentare un rinvio del client o anche restituire il contenuto di unimmagine JPG, con un header Content-Type del valore image/jpg. Suggerimento: Estendere la classe base Controller facoltativo. Di fatto, un controllore pu essere una semplice funzione PHP, o anche una funzione anonima PHP. Il capitolo Il controllore del libro dice tutto sui controllori di Symfony2. Il nome del template, AcmeDemoBundle:Welcome:index.html.twig, il nome logico del template e fa riferimento al le Resources/views/Welcome/index.html.twig dentro AcmeDemoBundle (localizzato in src/Acme/DemoBundle). La sezione successiva sui bundle ne spiega lutilit. Diamo ora un altro sguardo al le di congurazione delle rotte e cerchiamo la voce _demo:
# app/config/routing_dev.yml _demo: resource: "@AcmeDemoBundle/Controller/DemoController.php" type: annotation prefix: /demo

Symfony2 pu leggere e importare informazioni sulle rotte da diversi le, scritti in YAML, XML, PHP o anche inseriti in annotazioni PHP. Qui, il nome logico del le @AcmeDemoBundle/Controller/DemoController.php e si riferisce al le src/Acme/DemoBundle/Controller/DemoController.php. In questo le, le rotte sono denite come annotazioni sui metodi delle azioni:
// src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; class DemoController extends Controller { /** * @Route("/hello/{name}", name="_demo_hello") * @Template() */ public function helloAction($name) { return array(name => $name); } // ... }

Lannotazione @Route() denisce una nuova rotta con uno schema /hello/{name}, che esegue il metodo helloAction quando trovato. Una stringa racchiusa tra parentesi graffe, come {name}, chiamata segnaposto. Come si pu vedere, il suo valore pu essere recuperato tramite il parametro $name del metodo. Nota: Anche se le annotazioni sono sono supportate nativamente da PHP, possono essere usate in Symfony2 come mezzo conveniente per congurare i comportamenti del framework e mantenere la congurazione accanto al codice.

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

Dando unocchiata pi attenta al codice del controllore, si pu vedere che invece di rendere un template e restituire un oggetto Response come prima, esso restituisce solo un array di parametri. Lannotazione @Template() dice a Symfony di rendere il template al posto nostro, passando ogni variabili dellarray al template. Il nome del template resto segue il nome del controllore. Quindi, nel nostro esempio, viene reso il template AcmeDemoBundle:Demo:hello.html.twig (localizzato in src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig). Suggerimento: Le annotazioni @Route() e @Template() sono pi potenti dei semplici esempi mostrati in questa guida. Si pu approfondire largomento annotazioni nei controllori nella documentazione ufciale.

Template

Il controllore rende il template src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig (oppure AcmeDemoBundle:Demo:hello.html.twig, se si usa il nome logico):


{# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} {% extends "AcmeDemoBundle::layout.html.twig" %} {% block title "Hello " ~ name %} {% block content %} <h1>Hello {{ name }}!</h1> {% endblock %}

Per impostazione predenita, Symfony2 usa Twig (http://twig.sensiolabs.org/) come sistema di template, ma si possono anche usare i tradizionali template PHP, se si preferisce. Il prossimo capitolo introdurr il modo in cui funzionano i template in in Symfony2.
Bundle

Forse vi siete chiesti perch il termine bundle viene usato cos tante volte nora. Tutto il codice che si scrive per la propria applicazione organizzato in bundle. Nel linguaggio di Symfony2, un bundle un insieme strutturato di le (le PHP, fogli di stile, JavaScript, immagini, ...) che implementano una singola caratteristica (un blog, un forum, ...) e che pu essere condivisa facilmente con altri sviluppatori. Finora abbiamo manipolato un solo bundle, AcmeDemoBundle. Impareremo di pi sui bundle nellultimo capitolo di questa guida. Lavorare con gli ambienti Ora che si possiede una migliore comprensione di come funziona Symfony2, ora di dare unocchiata pi da vicino al fondo della pagina: si noter una piccola barra con il logo di Symfony2. Questa barra chiamata barra di debug del web ed il miglior amico dello sviluppatore.

1.1. Giro rapido

Documentazione Symfony, Release 2.0

Ma quello che si vede allinizio solo la punta delliceberg: cliccando sullo strano numero esadecimale, riveler un altro strumento di debug veramente utile di Symfony2: il prolatore.

Ovviamente, questo strumento non deve essere mostrato quando si rilascia lapplicazione su un server di produzione. Per questo motivo, si trover un altro front controller (app.php) nella cartella web/, ottimizzato per lambiente di produzione:
http://localhost/Symfony/web/app.php/demo/hello/Fabien

Se si usa Apache con mod_rewrite abilitato, si pu anche omettere la parte app.php dellURL:
http://localhost/Symfony/web/demo/hello/Fabien

Inne, ma non meno importante, sui server di produzione si dovrebbe far puntare la cartella radice del web alla cartella web/,per rendere linstallazione sicura e avere URL pi allettanti:

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

http://localhost/demo/hello/Fabien

Nota: Si noti che i tre URL qui forniti sono solo esempi di come un URL potrebbe apparire in produzione usando un front controller (con o senza mod_rewrite). Se li si prova effettivamente in uninstallazione base della Standard Edition di Symfony, si otterr un errore 404, perch AcmeDemoBundle abilitato solo in ambiente dev e le sue rotte importate in app/cong/routing_dev.yml. Per rendere lambiente di produzione pi veloce possibile, Symfony2 mantiene una cache sotto la cartella app/cache/. Quando si fanno delle modiche al codice o alla congurazione, occorre rimuovere a mano i le in cache. Per questo si dovrebbe sempre usare il front controller di sviluppo (app_dev.php) mentre si lavora al progetto. Diversi ambienti di una stessa applicazione differiscono solo nella loro congurazione. In effetti, una congurazione pu ereditare da unaltra:
# app/config/config_dev.yml imports: - { resource: config.yml } web_profiler: toolbar: true intercept_redirects: false

Lambiente dev (che carica il le di congurazione config_dev.yml) importa il le globale config.yml e lo modica, in questo caso, abilitando la barra di debug del web. Considerazioni nali Congratulazioni! Avete avuto il vostro primo assaggio di codice di Symfony2. Non era cos difcile, vero? C ancora molto da esplorare, ma dovreste gi vedere come Symfony2 rende veramente facile implementare siti web in modo migliore e pi veloce. Se siete ansiosi di saperne di pi, andate alla prossima sezione: la vista.

1.1.2 La vista
Dopo aver letto la prima parte di questa guida, avete deciso che Symfony2 vale altri dieci minuti. Bene! In questa seconda parte, si imparer di pi sul sistema dei template di Symfony2, Twig (http://twig.sensiolabs.org/). Twig un sistema di template veloce, essibile e sicuro per PHP. Rende i propri template pi leggibili e concisi e anche pi amichevoli per i designer. Nota: Invece di Twig, si pu anche usare PHP per i proprio template. Entrambi i sistemi di template sono supportati da Symfony2.

Familiarizzare con Twig

Suggerimento: Se si vuole imparare Twig, suggeriamo caldamente di leggere la sua documentazione (http://twig.sensiolabs.org/documentation) ufciale. Questa sezione solo un rapido sguardo ai concetti principali. Un template Twig un le di test che pu generare ogni tipo di contenuto (HTML, XML, CSV, LaTeX, ...). Twig denisce due tipi di delimitatori:

1.1. Giro rapido

Documentazione Symfony, Release 2.0

{{ ... {% ... esempio.

}}: Stampa una variabile o il risultato di unespressione; %}: Controlla la logica del template; usato per eseguire dei cicli for e delle istruzioni if, per

Segue un template minimale, che illustra alcune caratteristiche di base, usando due variabili, page_title e navigation, che dovrebbero essere passate al template:
<!DOCTYPE html> <html> <head> <title>La mia pagina web</title> </head> <body> <h1>{{ page_title }}</h1> <ul id="navigation"> {% for item in navigation %} <li><a href="{{ item.href }}">{{ item.caption }}</a></li> {% endfor %} </ul> </body> </html>

Suggerimento: Si possono inserire commenti nei template, usando i delimitatori {# ...

#}.

Per rendere un template in Symfony, usare il metodo render dal controllore e passargli qualsiasi variabile necessaria al template:
$this->render(AcmeDemoBundle:Demo:hello.html.twig, array( name => $name, ));

Le variabili passate a un template possono essere stringhe, array o anche oggetti. Twig astrae le differenze tra essi e consente di accedere agli attributi di una variabie con la notazione del punto (.):
{# array(name => Fabien) #} {{ name }} {# array(user => array(name => Fabien)) #} {{ user.name }} {# forza la ricerca nellarray #} {{ user[name] }} {# array(user => new User(Fabien)) #} {{ user.name }} {{ user.getName }} {# forza la ricerca del nome del metodo #} {{ user.name() }} {{ user.getName() }} {# passa parametri a un metodo #} {{ user.date(Y-m-d) }}

Nota: importante sapere che le parentesi graffe non sono parte della variabile, ma istruzioni di stampa. Se si accede alle variabili dentro ai tag, non inserire le parentesi graffe. 10 Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

Decorare i template Molto spesso, i template in un progetto condividono alcuni elementi comuni, come i ben noti header e footer. In Symfony2, il problema affrontato in modo diverso: un template pu essere decorato da un altro template. Funziona esattamente come nelle classi di PHP: lereditariet dei template consente di costruire un template di base layout, che contiene tutti gli elementi comuni del proprio sito e denisce dei blocchi, che i template gli possono sovrascrivere. Il template hello.html.twig eredita da layout.html.twig, grazie al tag extends:
{# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} {% extends "AcmeDemoBundle::layout.html.twig" %} {% block title "Hello " ~ name %} {% block content %} <h1>Hello {{ name }}!</h1> {% endblock %}

La notazione AcmeDemoBundle::layout.html.twig suona familiare, non vero? la stessa notazione usata per riferirsi a un template. La parte :: vuol dire semplicemente che lelemento controllore vuoto, quindi il le corrispondente si trova direttamente sotto la cartella Resources/views/. Diamo ora unocchiata a una versione semplicata di layout.html.twig:
{# src/Acme/DemoBundle/Resources/views/layout.html.twig #} <div class="symfony-content"> {% block content %} {% endblock %} </div>

I tag {% block %} deniscono blocchi che i template gli possono riempire. Tutto ci che fa un tag di blocco dire al sistema di template che un template glio pu sovrascrivere quelle porzioni di template. In questo esempio, il template hello.html.twig sovrascrive il blocco content, quindi il testo Hello Fabien viene reso allinterno dellelemento div.symfony-content. Usare tag, ltri e funzioni Una delle migliori caratteristiche di Twig la sua estensibilit tramite tag, ltri e funzioni. Symfony2 ha dei bundle con molti di questi, per facilitare il lavoro dei designer. Includere altri template Il modo migliore per condividere una parte di codice di un template quello di denire un template che possa essere incluso in altri template. Creare un template embedded.html.twig:
{# src/Acme/DemoBundle/Resources/views/Demo/embedded.html.twig #} Hello {{ name }}

E cambiare il template index.html.twig per includerlo:

1.1. Giro rapido

11

Documentazione Symfony, Release 2.0

{# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #} {% extends "AcmeDemoBundle::layout.html.twig" %} {# override the body block from embedded.html.twig #} {% block content %} {% include "AcmeDemoBundle:Demo:embedded.html.twig" %} {% endblock %}

Inserire altri controllori Cosa fare se si vuole inserire il risultato di un altro controllore in un template? Pu essere molto utile quando si lavora con Ajax o quando il template incluso necessita di alcune variabili, non disponibili nel template principale. Se si crea unazione fancy e la si vuole includere nel template index, basta usare il tag render:
{# src/Acme/DemoBundle/Resources/views/Demo/index.html.twig #} {% render "AcmeDemoBundle:Demo:fancy" with { name: name, color: verde } %}

Qui la stringa AcmeDemoBundle:Demo:fancy si riferisce allazione fancy del controllore Demo. I parametri (name e color) si comportano come variabili di richiesta simulate (come se fancyAction stesse gestendo una richiesta del tutto nuova) e sono rese disponibili al controllore:
// src/Acme/DemoBundle/Controller/DemoController.php class DemoController extends Controller { public function fancyAction($name, $color) { // creare un oggetto, in base alla variabile $color $object = ...;

return $this->render(AcmeDemoBundle:Demo:fancy.html.twig, array(name => $name, object = } // ... }

Creare collegamenti tra le pagine

Parlando di applicazioni web, i collegamenti tra pagine sono una parte essenziale. Invece di inserire a mano gli URL nei template, la funzione path sa come generare URL in base alla congurazione delle rotte. In questo modo, tutti gli URL saranno facilmente aggiornati al cambiare della congurazione:
<a href="{{ path(_demo_hello, { name: Thomas }) }}">Ciao Thomas!</a>

La funzione path() accetta come parametri un nome di rotta e un array di parametri. Il nome della rotta la chiave principale sotto cui le rotte sono elencate e i parametri sono i valori dei segnaposto deniti nello schema della rotta:
// src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; /** * @Route("/hello/{name}", name="_demo_hello") * @Template() */

12

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

public function helloAction($name) { return array(name => $name); }

Suggerimento: La funzione url genera URL assoluti: Thomas }) }}.

{{ url(_demo_hello, { name:

Includere risorse: immagini, JavaScript e fogli di stile

Cosa sarebbe Internet senza immagini, JavaScript e fogli di stile? Symfony2 fornisce la funzione asset per gestirli facilmente.
<link href="{{ asset(css/blog.css) }}" rel="stylesheet" type="text/css" /> <img src="{{ asset(images/logo.png) }}" />

Lo scopo principale della funzione asset quello di rendere le applicazioni maggiormente portabili. Grazie a questa funzione, si pu spostare la cartella radice dellapplicazione ovunque, sotto la propria cartella radice del web, senza cambiare nulla nel codice dei template. Escape delle variabili Twig congurato in modo predenito per lescape automatico di ogni output. Si legga la documentazione (http://twig.sensiolabs.org/documentation) di Twig per sapere di pi sullescape delloutput e sullestensione Escaper. Considerazioni nali Twig semplice ma potente. Grazie a layout, blocchi, template e inclusioni di azioni, molto facile organizzare i propri template in un modo logico ed estensibile. Tuttavia, chi non si trova a proprio agio con Twig pu sempre usare i template PHP in Symfony, senza problemi. Stiamo lavorando con Symfony2 da soli venti minuti e gi siamo in grado di fare cose incredibili. Questo il potere di Symfony2. Imparare le basi facile e si imparer presto che questa facilit nascosta sotto unarchitettura molto essibile. Ma non corriamo troppo. Prima occorre imparare di pi sul controllore e questo esattamente largomento della prossima parte di questa guida. Pronti per altri dieci minuti di Symfony2?

1.1.3 Il controllore
Ancora qui, dopo le prime due parti? State diventano dei Symfony2-dipendenti! Senza ulteriori indugi, scopriamo cosa sono in grado di fare i controllori. Usare i formati Oggigiorno, unapplicazione web dovrebbe essere in grado di servire pi che semplici pagine HTML. Da XML per i feed RSS o per web service, a JSON per le richieste Ajax, ci sono molti formati diversi tra cui scegliere. Il supporto di tali formati in Symfony2 semplice. Modicare il le routing.yml e aggiungere un formato _format, con valore xml:

1.1. Giro rapido

13

Documentazione Symfony, Release 2.0

// src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; /** * @Route("/hello/{name}", defaults={"_format"="xml"}, name="_demo_hello") * @Template() */ public function helloAction($name) { return array(name => $name); }

Usando il formato di richiesta (come denito nel valore _format), Symfony2 sceglie automaticamente il template giusto, in questo caso hello.xml.twig:
<!-- src/Acme/DemoBundle/Resources/views/Demo/hello.xml.twig --> <hello> <name>{{ name }}</name> </hello>

tutto. Per i formati standard, Symfony2 sceglier anche lheader Content-Type migliore per la risposta. Se si vogliono supportare diversi formati per una singola azione, usare invece il segnaposto {_format} nello schema della rotta:
// src/Acme/DemoBundle/Controller/DemoController.php use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

/** * @Route("/hello/{name}.{_format}", defaults={"_format"="html"}, requirements={"_format"="html|xml|j * @Template() */ public function helloAction($name) { return array(name => $name); }

Ora il controller sar richiamato per URL come /demo/hello/Fabien.xml o /demo/hello/Fabien.json. La voce requirements denisce delle espressioni regolari che i segnaposto devono soddisfare. In questo esempio, se si prova a richiedere la risorsa /demo/hello/Fabien.js, si otterr un errore 404, poich essa non corrisponde al requisito di _format. Rinvii e rimandi Se si vuole rinviare lutente a unaltra pagina, usare il metodo redirect():
return $this->redirect($this->generateUrl(_demo_hello, array(name => Lucas)));

Il metodo generateUrl() lo stesso della funzione path() che abbiamo usato nei template. Accetta come parametri il nome della rotta e un array di parametri e restituisce lURL amichevole associato. Si pu anche facilmente rimandare lazione a unaltra, col metodo forward(). Internamente, Symfony effettua una sotto-richiesta e restituisce un oggetto Response da tale sotto-richiesta:

$response = $this->forward(AcmeDemoBundle:Hello:fancy, array(name => $name, color => green)); // fare qualcosa con la risposta o restituirla direttamente

14

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

Ottenere informazioni dalla richiesta Oltre ai valori dei segnaposto delle rotte, il controllore ha anche accesso alloggetto Request:
$request = $this->getRequest(); $request->isXmlHttpRequest(); // una richiesta Ajax? $request->getPreferredLanguage(array(en, fr)); $request->query->get(page); // prende un parametro $_GET $request->request->get(page); // prende un parametro $_POST

In un template, si pu anche avere accesso alloggetto Request tramite la variabile app.request:


{{ app.request.query.get(page) }} {{ app.request.parameter(page) }}

Persistere i dati nella sessione Anche se il protocollo HTTP non ha stato, Symfony2 fornisce un belloggetto sessione, che rappresenta il client (sia esso una persona che usa un browser, un bot o un servizio web). Tra due richieste, Symfony2 memorizza gli attributi in un cookie, usando le sessioni native di PHP. Si possono memorizzare e recuperare informazioni dalla sessione in modo facile, da un qualsiasi controllore:
$session = $this->getRequest()->getSession(); // memorizza un attributo per riusarlo pi avanti durante una richiesta utente $session->set(foo, bar); // in un altro controllore per unaltra richiesta $foo = $session->get(foo); // imposta la localizzazione dellutente $session->setLocale(fr);

Si possono anche memorizzare piccoli messaggi che saranno disponibili solo per la richiesta successiva:
// memorizza un messaggio per la richiesta successiva (in un controllore) $session->setFlash(notice, Congratulazioni, azione eseguita con successo!); // mostra il messaggio nella richiesta successiva (in un template) {{ app.session.flash(notice) }}

Ci risulta utile quando occorre impostare un messaggio di successo, prima di rinviare lutente a unaltra pagina (la quale mostrer il messaggio). Proteggere le risorse La Standard Edition di Symfony possiede una semplice congurazione di sicurezza, che soddisfa i bisogni pi comuni:
# app/config/security.yml security: encoders:

1.1. Giro rapido

15

Documentazione Symfony, Release 2.0

Symfony\Component\Security\Core\User\User: plaintext role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] providers: in_memory: users: user: { password: userpass, roles: [ ROLE_USER ] } admin: { password: adminpass, roles: [ ROLE_ADMIN ] } firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false login: pattern: ^/demo/secured/login$ security: false secured_area: pattern: ^/demo/secured/ form_login: check_path: /demo/secured/login_check login_path: /demo/secured/login logout: path: /demo/secured/logout target: /demo/

Questa congurazione richiede agli utenti di effettuare login per ogni URL che inizi per /demo/secured/ e denisce due utenti validi: user e admin. Inoltre, lutente admin ha il ruolo ROLE_ADMIN, che include il ruolo ROLE_USER (si veda limpostazione role_hierarchy). Suggerimento: Per leggibilit, le password sono memorizzate in chiaro in questa semplice congurazione, ma si pu usare un qualsiasi algoritmo di hash, modicando la sezione encoders. Andando allURL http://localhost/Symfony/web/app_dev.php/demo/secured/hello, si verr automaticamente rinviati al form di login, perch questa risorsa protetta da un firewall. Si pu anche forzare lazione a richiedere un dato ruolo, usando lannotazione @Secure nel controllore:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use JMS\SecurityExtraBundle\Annotation\Secure; /** * @Route("/hello/admin/{name}", name="_demo_secured_hello_admin") * @Secure(roles="ROLE_ADMIN") * @Template() */ public function helloAdminAction($name) { return array(name => $name); }

Ora, si entri come utente user (che non ha il ruolo ROLE_ADMIN) e, dalla pagina sicura hello, si clicchi sul collegamento Hello resource secured. Symfony2 dovrebbe restituire un codice di stato HTTP 403 (forbidden), 16 Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

indicando che lutente non autorizzato ad accedere a tale risorsa. Nota: Il livello di sicurezza di Symfony2 molto essibile e fornisce diversi provider per gli utenti (come quello per lORM Doctrine) e provider di autenticazione (come HTTP basic, HTTP digest o certicati X509). Si legga il capitolo Sicurezza del libro per maggiori informazioni su come usarli e congurarli.

Mettere in cache le risorse Non appena il proprio sito inizia a generare pi trafco, si vorr evitare di dover generare la stessa risorsa pi volte. Symfony2 usa gli header di cache HTTP per gestire la cache delle risorse. Per semplici strategie di cache, si pu usare lannotazione @Cache():
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; /** * @Route("/hello/{name}", name="_demo_hello") * @Template() * @Cache(maxage="86400") */ public function helloAction($name) { return array(name => $name); }

In questo esempio, la risorsa sar in cache per un giorno. Ma si pu anche usare la validazione invece della scadenza o una combinazione di entrambe, se questo soddisfa meglio le proprie esigenze. La cache delle risorse gestita dal reverse proxy predenito di Symfony2. Ma poich la cache gestita usando i normali header di cache di HTTP, possibile rimpiazzare il reverse proxy predenito con Varnish o Squid e scalare facilmente la propria applicazione. Nota: E se non si volesse mettere in cache lintera pagina? Symfony2 ha una soluzione, tramite Edge Side Includes (ESI), supportate nativamente. Si possono avere maggiori informazioni nel capitolo Cache HTTP del libro.

Considerazioni nali tutto, e forse non abbiamo nemmeno speso tutti e dieci i minuti previsti. Nella prima parte abbiamo introdotto brevemente i bundle e tutte le caratteristiche apprese nora fanno parte del bundle del nucleo del framework. Ma, grazie ai bundle, ogni cosa in Symfony2 pu essere estesa o sostituita. Questo largomento della prossima parte di questa guida.

1.1.4 Larchitettura
Sei il mio eroe! Chi avrebbe pensato che tu fossi ancora qui dopo le prime tre parti? I tuoi sforzi saranno presto ricompensati. Le prime tre parti non danno uno sguardo approfondito allarchitettura del framework. Poich essa rende unico Symfony2 nel panorama dei framework, vediamo in cosa consiste.

1.1. Giro rapido

17

Documentazione Symfony, Release 2.0

Capire la struttura delle cartelle La struttura delle cartelle di unapplicazione Symfony2 alquanto essibile, ma la struttura delle cartelle della distribuzione Standard Edition riette la struttura tipica e raccomandata di unapplicazione Symfony2: app/: La congurazione dellapplicazione; src/: Il codice PHP del progetto; vendor/: Le dipendenze di terze parti; web/: La cartella radice del web.
La cartella web/

La cartella radice del web la casa di tutti i le pubblici e statici, come immagini, fogli di stile, le JavaScript. anche il posto in cui stanno i front controller:
// web/app.php require_once __DIR__./../app/bootstrap.php.cache; require_once __DIR__./../app/AppKernel.php; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel(prod, false); $kernel->loadClassCache(); $kernel->handle(Request::createFromGlobals())->send();

Il kernel inizialmente richiede il le bootstrap.php.cache, che lancia lapplicazione e registra lautoloader (vedi sotto). Come ogni front controller, app.php usa una classe Kernel, AppKernel, per inizializzare lapplicazione.
La cartella app/

La classe AppKernel il punto di ingresso principale della congurazione dellapplicazione e quindi memorizzata nella cartella app/. Questa classe deve implementare due metodi: registerBundles() deve restituire un array di tutti i bundle necessari per eseguire lapplicazione; registerContainerConfiguration() carica la congurazione dellapplicazione (approfondito pi avanti); Il caricamento automatico di PHP pu essere congurato tramite app/autoload.php:
// app/autoload.php use Symfony\Component\ClassLoader\UniversalClassLoader; $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( Symfony => array(__DIR__./../vendor/symfony/src, __DIR__./../vendor/bundles), Sensio => __DIR__./../vendor/bundles, JMS => __DIR__./../vendor/bundles, Doctrine\\Common => __DIR__./../vendor/doctrine-common/lib, Doctrine\\DBAL => __DIR__./../vendor/doctrine-dbal/lib, Doctrine => __DIR__./../vendor/doctrine/lib, Monolog => __DIR__./../vendor/monolog/src,

18

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

Assetic Metadata

=> __DIR__./../vendor/assetic/src, => __DIR__./../vendor/metadata/src,

)); $loader->registerPrefixes(array( Twig_Extensions_ => __DIR__./../vendor/twig-extensions/lib, Twig_ => __DIR__./../vendor/twig/lib, )); // ... $loader->registerNamespaceFallbacks(array( __DIR__./../src, )); $loader->register();

La classe Symfony\Component\ClassLoader\UniversalClassLoader di Symfony2 usata per auto-caricare i le che rispettano gli standard (http://groups.google.com/group/php-standards/web/psr-0-nal-proposal) di interoperabilit per gli spazi dei nomi di PHP 5.3 oppure la convenzione (http://pear.php.net/) dei nomi di PEAR per le classi. Come si pu vedere, tutte le dipendenze sono sotto la cartella vendor/, ma questa solo una convenzione. Si possono inserire in qualsiasi posto, globalmente sul proprio server o localmente nei propri progetti. Nota: Se si vuole approfondire largomento essibilit dellautoloader di Symfony2, si pu leggere il capitolo Il componente ClassLoader.

Capire il sistema dei bundle Questa sezione unintroduzione a una delle pi grandi e potenti caratteristiche di Symfony2, il sistema dei bundle. Un bundle molto simile a un plugin in un altro software. Ma perch allora si chiama bundle e non plugin? Perch ogni cosa un bundle in Symfony2, dalle caratteristiche del nucleo del framework al codice scritto per la propria applicazione. I bundle sono cittadini di prima classe in Symfony2. Essi forniscono la essibilit di usare delle caratteristiche pre-costruite impacchettate in bundle di terze parti o di distribuire i propri bundle. Questo rende molto facile scegliere quali caratteristiche abilitare nella propria applicazione e ottimizzarle nel modo preferito. A ne giornata, il codice della propria applicazione importante quanto il nucleo stesso del framework.
Registrare un bundle

Unapplicazione composta di bundle, come denito nel metodo registerBundles() della classe AppKernel . Ogni bundle una cartella che contiene una singola classe Bundle che la descrive:
// app/AppKernel.php public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(), );

1.1. Giro rapido

19

Documentazione Symfony, Release 2.0

if (in_array($this->getEnvironment(), array(dev, test))) { $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); } return $bundles; }

Oltre a AcmeDemoBundle, di cui abbiamo gi parlato, si noti che il kernel abilita anche FrameworkBundle, DoctrineBundle, SwiftmailerBundle e AsseticBundle. Fanno tutti parte del nucleo del framework.
Congurare un bundle

Ogni bundle pu essere personalizzato tramite le di congurazione scritti in YAML, XML o PHP. Si veda la congurazione predenita:
# app/config/config.yml imports: - { resource: parameters.ini } - { resource: security.yml } framework: secret: %secret% charset: UTF-8 router: { resource: "%kernel.root_dir%/config/routing.yml" } form: true csrf_protection: true validation: { enable_annotations: true } templating: { engines: [twig] } #assets_version: SomeVersionScheme session: default_locale: %locale% auto_start: true # Configurazione di Twig twig: debug: %kernel.debug% strict_variables: %kernel.debug% # Configurazione di Assetic assetic: debug: %kernel.debug% use_controller: false filters: cssrewrite: ~ # closure: # jar: %kernel.root_dir%/java/compiler.jar # yui_css: # jar: %kernel.root_dir%/java/yuicompressor-2.4.2.jar # Configurazione di Doctrine doctrine: dbal: driver: %database_driver% host: %database_host% dbname: %database_name%

20

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

user: %database_user% password: %database_password% charset: UTF8 orm: auto_generate_proxy_classes: %kernel.debug% auto_mapping: true # Configurazione di Swiftmailer swiftmailer: transport: %mailer_transport% host: %mailer_host% username: %mailer_user% password: %mailer_password% jms_security_extra: secure_controllers: true secure_all_services: false

Ogni voce come framework denisce la congurazione per uno specico bundle. Per esempio, framework congura FrameworkBundle, mentre swiftmailer congura SwiftmailerBundle. Ogni ambiente pu sovrascrivere la congurazione predenita, fornendo un le di congurazione specico. Per esempio, lambiente dev carica il le config_dev.yml, che carica la congurazione principale (cio config.yml) e quindi la modica per aggiungere alcuni strumenti di debug:
# app/config/config_dev.yml imports: - { resource: config.yml } framework: router: { resource: "%kernel.root_dir%/config/routing_dev.yml" } profiler: { only_exceptions: false } web_profiler: toolbar: true intercept_redirects: false monolog: handlers: main: type: path: level: firephp: type: level:

stream %kernel.logs_dir%/%kernel.environment%.log debug firephp info

assetic: use_controller: true

Estendere un bundle

Oltre a essere un modo carino per organizzare e congurare il proprio codice, un bundle pu estendere un altro bundle. Lereditariet dei bundle consente di sovrascrivere un bundle esistente, per poter personalizzare i suoi controllori, i template o qualsiasi altro suo le. Qui sono daiuto i nomi logici (come

1.1. Giro rapido

21

Documentazione Symfony, Release 2.0

@AcmeDemoBundle/Controller/SecuredController.php), che astraggono i posti in cui le risorse sono effettivamente memorizzate. Nomi logici di le Quando si vuole fare riferimento a un le da un bundle, usare questa notazione: @NOME_BUNDLE/percorso/del/file; Symfony2 risolver @NOME_BUNDLE nel percorso reale del bundle. Per esempio, il percorso logico @AcmeDemoBundle/Controller/DemoController.php verrebbe convertito in src/Acme/DemoBundle/Controller/DemoController.php, perch Symfony conosce la locazione di AcmeDemoBundle. Nomi logici di controllori Per i controllori, occorre fare riferimento ai nomi dei metodi usando il formato NOME_BUNDLE:NOME_CONTROLLORE:NOME_AZIONE. Per esempio, AcmeDemoBundle:Welcome:index mappa il metodo indexAction della classe Acme\DemoBundle\Controller\WelcomeController. Nomi logici di template Per i template, il nome logico AcmeDemoBundle:Welcome:index.html.twig convertito al percorso del le src/Acme/DemoBundle/Resources/views/Welcome/index.html.twig. I template diventano ancora pi interessanti quando si realizza che i le non hanno bisogno di essere memorizzati su lesystem. Si possono facilmente memorizzare, per esempio, in una tabella di database. Estendere i bundle Se si seguono queste convenzioni, si pu usare lereditariet dei bundle per sovrascrivere le, controllori o template. Per esempio, se un nuovo bundle chiamato AcmeNewBundle estende AcmeDemoBundle, Symfony prover a caricare prima il controllore AcmeDemoBundle:Welcome:index da AcmeNewBundle e poi cercher il secondo AcmeDemoBundle. Questo vuol dire che un bundle pu sovrascrivere quasi ogni parte di un altro bundle! Capite ora perch Symfony2 cos essibile? Condividere i propri bundle tra le applicazioni, memorizzarli localmente o globalmente, a propria scelta. Usare i venditori Probabilmente la propria applicazione dipender da librerie di terze parti. Queste ultime dovrebbero essere memorizzate nella cartella vendor/. Tale cartella contiene gi le librerie di Symfony2, SwiftMailer, lORM Doctrine, il sistema di template Twig e alcune altre librerie e bundle di terze parti. Capire la cache e i log Symfony2 forse uno dei framework completi pi veloci in circolazione. Ma come pu essere cos veloce, se analizza e interpreta decine di le YAML e XML a ogni richiesta? In parte, per il suo sistema di cache. La congurazione dellapplicazione analizzata solo per la prima richiesta e poi compilata in semplice le PHP, memorizzato nella cartella app/cache/ dellapplicazione. Nellambiente di sviluppo, Symfony2 abbastanza intelligente da pulire la cache quando cambiano dei le. In produzione, invece, occorre pulire la cache manualmente quando si aggiorna il codice o si modica la congurazione. Sviluppando unapplicazione web, le cose possono andar male in diversi modi. I le di log nella cartella app/logs/ dicono tutto a proposito delle richieste e aiutano a risolvere il problema in breve tempo. Usare linterfaccia a linea di comando Ogni applicazione ha uno strumento di interfaccia a linea di comando (app/console), che aiuta nella manutenzione dellapplicazione. La console fornisce dei comandi che incrementano la produttivit, automatizzando dei compiti noiosi e ripetitivi.

22

Capitolo 1. Giro rapido

Documentazione Symfony, Release 2.0

Richiamandola senza parametri, si pu sapere di pi sulle sue capacit:


php app/console

Lopzione --help aiuta a scoprire lutilizzo di un comando:


php app/console router:debug --help

Considerazioni nali Dopo aver letto questa parte, si dovrebbe essere in grado di muoversi facilmente dentro Symfony2 e farlo funzionare. Ogni cosa in Symfony2 fatta per rispondere alle varie esigenze. Quindi, si possono rinominare e spostare le varie cartelle, nch non si raggiunge il risultato voluto. E questo tutto per il giro veloce. Dai test allinvio di email, occorre ancora imparare diverse cose per padroneggiare Symfony2. Pronti per approfondire questi temi? Senza indugi, basta andare nella pagine del libro e scegliere un argomento a piacere. Un quadro generale > La vista > Il controllore > Larchitettura

1.1. Giro rapido

23

Documentazione Symfony, Release 2.0

24

Capitolo 1. Giro rapido

CAPITOLO 2

Libro

Approfondire Symfony2 con le guide per argomento:

2.1 Libro
2.1.1 Symfony2 e fondamenti di HTTP
Congratulazioni! Imparando Symfony2, si tende a essere sviluppatori web pi produttivi, versatili e popolari (in realt, per questultimo dovete sbrigarvela da soli). Symfony2 costruito per tornare alle basi: per sviluppare strumenti che consentono di sviluppare pi velocemente e costruire applicazioni pi robuste, anche andando fuori strada. Symfony costruito sulle migliori idee prese da diverse tecnologie: gli strumenti e i concetti che si stanno per apprendere rappresentano lo sforzo di centinaia di persone, in molti anni. In altre parole, non si sta semplicemente imparando Symfony, si stanno imparando i fondamenti del web, le pratiche migliori per lo sviluppo e come usare tante incredibili librerie PHP, allinterno o dipendenti da Symfony2. Tenetevi pronti. Fedele alla losoa di Symfony2, questo capitolo inizia spiegando il concetto fondamentale comune allo sviluppo web: HTTP. Indipendentemente dalla propria storia o dal linguaggio di programmazione preferito, questo capitolo andrebbe letto da tutti. HTTP semplice HTTP (Hypertext Transfer Protocol) un linguaggio testuale che consente a due macchine di comunicare tra loro. Tutto qui! Per esempio, quando controllate lultima vignetta di xkcd (http://xkcd.com/), ha luogo la seguente conversazione (approssimata):

25

Documentazione Symfony, Release 2.0

E mentre il linguaggio veramente usato un po pi formale, ancora assolutamente semplice. HTTP il termine usato per descrivere tale semplice linguaggio testuale. Non importa in quale linguaggio si sviluppi sul web, lo scopo del proprio server sempre quello di interpretare semplici richieste testuali e restituire semplici risposte testuali. Symfony2 costruito n dalle basi attorno a questa realt. Che lo si comprenda o meno, HTTP qualcosa che si usa ogni giorno. Con Symfony2, si imparer come padroneggiarlo.
Passo 1: il client invia una richiesta

Ogni conversazione sul web inizia con una richiesta. La richiesta un messaggio testuale creato da un client (per esempio un browser, unapplicazione mobile, ecc.) in uno speciale formato noto come HTTP. Il client invia la richiesta a un server e quindi attende una risposta. Diamo uno sguardo alla prima parte dellinterazione (la richiesta) tra un browser e il server web di xkcd:

Nel gergo di HTTP, questa richiesta apparirebbe in realt in questo modo:


GET / HTTP/1.1 Host: xkcd.com Accept: text/html User-Agent: Mozilla/5.0 (Macintosh)

26

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Questo semplice messaggio comunica ogni cosa necessaria su quale risorsa esattamente il client sta richiedendo. La prima riga di ogni richiesta HTTP la pi importante e contiene due cose: lURI e il metodo HTTP. LURI (p.e. /, /contact, ecc.) lindirizzo univoco o la locazione che identica la risorsa che il client vuole. Il metodo HTTP (p.e. GET) denisce cosa si vuole fare con la risorsa. I metodi HTTP sono verbi della richiesta e deniscono i pochi modi comuni in cui si pu agire sulla risorsa: GET POST PUT DELETE Recupera la risorsa dal server Crea una risorsa sul server Aggiorna la risorsa sul server Elimina la risorsa dal server

Tenendo questo a mente, si pu immaginare come potrebbe apparire una richiesta HTTP per cancellare una specica voce di un blog, per esempio:
DELETE /blog/15 HTTP/1.1

Nota: Ci sono in realt nove metodi HTTP deniti dalla specica HTTP, ma molti di essi non sono molto usati o supportati. In realt, molti browser moderni non supportano nemmeno i metodi PUT e DELETE. In aggiunta alla prima linea, una richiesta HTTP contiene sempre altre linee di informazioni, chiamate header. Gli header possono fornire un ampio raggio di informazioni, come lHost richiesto, i formati di risposta accettati dal client (Accept) e lapplicazione usata dal client per eseguire la richiesta (User-Agent). Esistono molti altri header, che possono essere trovati nella pagina di Wikipedia Lista di header HTTP (http://en.wikipedia.org/wiki/List_of_HTTP_header_elds).
Passo 2: Il server restituisce una risposta

Una volta che il server ha ricevuto la richiesta, sa esattamente la risorsa di cui il client ha bisogno (tramite lURI) e cosa vuole fare il client con tale risorsa (tramite il metodo). Per esempio, nel caso di una richiesta GET, il server prepara la risorsa e la restituisce in una risposta HTTP. Consideriamo la risposta del server web di xkcd:

Tradotto in HTTP, la risposta rimandata al browser assomiglier a questa:


HTTP/1.1 200 OK Date: Sat, 02 Apr 2011 21:05:05 GMT Server: lighttpd/1.4.19 Content-Type: text/html

2.1. Libro

27

Documentazione Symfony, Release 2.0

<html> <!-- HTML for the xkcd comic --> </html>

La risposta HTTP contiene la risorsa richiesta (il contenuto HTML, in questo caso). oltre che altre informazioni sulla risposta. La prima riga particolarmente importante e contiene il codice di stato della risposta HTTP (200, in questo caso). Il codice di stato comunica il risultato globale della richiesta al client. La richiesta andata a buon ne? C stato un errore? Diversi codici di stato indicano successo, errore o che il client deve fare qualcosa (p.e. rimandare a unaltra pagina). Una lista completa pu essere trovata nella pagina di Wikipedia Elenco dei codici di stato HTTP (http://it.wikipedia.org/wiki/Elenco_dei_codici_di_stato_HTTP). Come la richiesta, una risposta HTTP contiene parti aggiuntive di informazioni, note come header. Per esempio, un importante header di risposta HTTP Content-Type. Il corpo della risorsa stessa potrebbe essere restituito in molti formati diversi, inclusi HTML, XML o JSON, mentre lheader Content-Type usa i tipi di media di Internet, come text/html, per dire al client quale formato restituito. Ua lista di tipi di media comuni si pu trovare sulla voce di Wikipedia Lista di tipi di media comuni (http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types). Esistono molti altri header, alcuni dei quali molto potenti. Per esempio, alcuni header possono essere usati per creare un potente sistema di cache.
Richieste, risposte e sviluppo web

Questa conversazione richiesta-risposta il processo fondamentale che guida tutta la comunicazione sul web. Questo processo tanto importante e potente, quanto inevitabilmente semplice. Laspetto pi importante questo: indipendentemente dal linguaggio usato, il tipo di applicazione costruita (web, mobile, API JSON) o la losoa di sviluppo seguita, lo scopo nale di unapplicazione sempre quello di capire ogni richiesta e creare e restituire unappropriata risposta. Larchitettura di Symfony strutturata per corrispondere a questa realt. Suggerimento: Per saperne di pi sulla specica HTTP, si pu leggere la RFC HTTP 1.1 (http://www.w3.org/Protocols/rfc2616/rfc2616.html) originale o la HTTP Bis (http://datatracker.ietf.org/wg/httpbis/), che uno sforzo attivo di chiarire la specica originale. Un importante strumento per vericare sia gli header di richiesta che quelli di risposta durante la navigazione lestensione Live HTTP Headers (https://addons.mozilla.org/en-US/refox/addon/3829/) di Firefox.

Richieste e risposte in PHP Dunque, come interagire con la richiesta e creare una risposta quando si usa PHP? In realt, PHP astrae un po lintero processo:
<?php $uri = $_SERVER[REQUEST_URI]; $pippo = $_GET[pippo]; header(Content-type: text/html); echo L\URI richiesto : .$uri; echo Il valore del parametro "pippo" : .$pippo;

Per quanto possa sembrare strano, questa piccola applicazione di fatto prende informazioni dalla richiesta HTTP e le usa per creare una risposta HTTP. Invece di analizzare il messaggio di richiesta HTTP grezzo, PHP prepara della variabili superglobali, come $_SERVER e $_GET, che contengono tutte le informazioni dalla richiesta. Similmente, 28 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

inece di restituire un testo di risposta formattato come da HTTP, si pu usare la funzione header() per creare header di risposta e stampare semplicemente il contenuto, che sar la parte di contenuto del messaggio di risposta. PHP creer una vera risposta HTTP e la restituir al client:
HTTP/1.1 200 OK Date: Sat, 03 Apr 2011 02:14:33 GMT Server: Apache/2.2.17 (Unix) Content-Type: text/html LURI richiesto : /testing?pippo=symfony Il valore del parametro "pippo" : symfony

Richieste e risposte in Symfony Symfony fornisce unalternativa allapproccio grezzo di PHP, tramite due classi che consentono di interagire con richiesta e risposta HTTP in modo pi facile. La classe Symfony\Component\HttpFoundation\Request una semplice rappresentazione orientata agli oggetti del messaggio di richiesta HTTP. Con essa, si hanno a portata di mano tutte le informazioni sulla richiesta:
use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); // lURI richiesto (p.e. /about) tranne ogni parametro $request->getPathInfo(); // recupera rispettivamente le variabili GET e POST $request->query->get(pippo); $request->request->get(pluto); // recupera le variabili SERVER $request->server->get(HTTP_HOST); // recupera unistanza di UploadedFile identificata da pippo $request->files->get(pippo); // recupera il valore di un COOKIE $request->cookies->get(PHPSESSID); // recupera un header di risposta HTTP, con chiavi normalizzate e minuscole $request->headers->get(host); $request->headers->get(content_type); $request->getMethod(); $request->getLanguages(); // GET, POST, PUT, DELETE, HEAD // un array di lingue accettate dal client

Come bonus, la classe Request fa un sacco di lavoro in sottofondo, di cui non ci si dovr mai preoccupare. Per esempio, il metodo isSecure() verica tre diversi valori in PHP che possono indicare se lutente si stia connettendo o meno tramite una connessione sicura (cio https).

2.1. Libro

29

Documentazione Symfony, Release 2.0

ParameterBags e attributi di Request

Come visto in precedenza, le variabili $_GET e $_POST sono accessibili rispettivamente tramite le propriet pubbliche query e request. Entrambi questi oggetti sono oggetti della classe Symfony\Component\HttpFoundation\ParameterBag, che ha metodi come :method:Symfony\\Component\\HttpFoundation\\ParameterBag::get, :method:Symfony\\Component\\HttpFoundation\\ParameterBag::has, :method:Symfony\\Component\\HttpFoundation\\Par e altri. In effetti, ogni propriet pubblica usata nellesempio precedente unistanza di ParameterBag. La classe Request ha anche una propriet pubblica attributes, che contiene dati speciali relativi a come lapplicazione funziona internamente. Per il framework Symfony2, attributes contiene valori restituiti dalla rotta corrispondente, come _controller, id (se si ha un parametro {id}), e anche il nome della rotta stessa (_route). La propriet attributes pensata apposta per essere un posto in cui preparare e memorizzare informazioni sulla richiesta relative al contesto. Symfony fornisce anche una classe Response: una semplice rappresentazione PHP di un messaggio di risposta HTTP. Questo consente alla propria applicazione di usare uninterfaccia orientata agli oggetti per costruire la risposta che occorre restituire al client:
use Symfony\Component\HttpFoundation\Response; $response = new Response(); $response->setContent(<html><body><h1>Ciao mondo!</h1></body></html>); $response->setStatusCode(200); $response->headers->set(Content-Type, text/html); // stampa gli header HTTP seguiti dal contenuto $response->send();

Se Symfony offrisse solo questo, si avrebbe gi a disposizione un kit di strumenti per accedere facilmente alle informazioni di richiesta e uninterfaccia orientata agli oggetti per creare la risposta. Anche imparando le molte potenti caratteristiche di Symfony, si tenga a mente che lo scopo della propria applicazione sempre quello di interpretare una richiesta e creare lappropriata risposta, basata sulla logica dellapplicazione. Suggerimento: Le classi Request e Response fanno parte di un componente a s stante incluso con Symfony, chiamato HttpFoundation. Questo componente pu essere usato in modo completamente indipendente da Symfony e fornisce anche classi per gestire sessioni e caricamenti di le.

Il viaggio dalla richiesta alla risposta Come lo stesso HTTP, gli oggetti Request e Response sono molto semplici. La parte difcile nella costruzione di unapplicazione la scrittura di quello che sta in mezzo. In altre parole, il vero lavoro consiste nello scrivere il codice che interpreta linformazione della richiesta e crea la risposta. La propria applicazione probabilmente fa molte cose, come inviare email, gestire invii di form, salvare dati in un database, rendere pagine HTML e proteggere contenuti. Come si pu gestire tutto questo e mantenere al contempo il proprio codice organizzato e mantenibile? Symfony stato creato per risolvere questi problemi.
Il front controller

Le applicazioni erano tradizionalmente costruite in modo che ogni pagina di un sito fosse un le sico:

30

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

index.php contact.php blog.php

Ci sono molti problemi con questo approccio, inclusa la essibilit degli URL (che succede se si vuole cambiare blog.php con news.php senza rompere tutti i collegamenti?) e il fatto che ogni le deve includere manualmente alcuni le necessari, in modo che la sicurezza, le connessioni al database e laspetto del sito possano rimanere coerenti. Una soluzione molto migliore usare un front controller: un unico le PHP che gestisce ogni richiesta che arriva alla propria applicazione. Per esempio: /index.php /index.php/contact /index.php/blog esegue index.php esegue index.php esegue index.php

Suggerimento: Usando il modulo mod_rewrite di Apache (o moduli equivalenti di altri server), gli URL possono essere facilmente puliti per essere semplicemente /, /contact e /blog. Ora ogni richiesta gestita esattamente nello stesso modo. Invece di singoli URL che eseguono diversi le PHP, sempre eseguito il front controller, e il dirottamento di URL diversi sulle diverse parti della propria applicazione gestito internamente. Questo risolve entrambi i problemi dellapproccio originario. Quasi tutte le applicazioni web moderne fanno in questo modo, incluse applicazioni come WordPress.
Restare organizzati

Ma allinterno del nostro front controller, come possiamo sapere quale pagina debba essere resa e come poterla renderla in modo facile? In un modo o nellaltro, occorre vericare lURI in entrata ed eseguire parti diverse di codice, a seconda di tale valore. Le cose possono peggiorare rapidamente:
// index.php $request = Request::createFromGlobals(); $path = $request->getPathInfo(); // lURL richiesto if (in_array($path, array(, /)) { $response = new Response(Benvenuto nella homepage.); } elseif ($path == /contact) { $response = new Response(Contattaci); } else { $response = new Response(Pagina non trovata., 404); } $response->send();

La soluzione a questo problema pu essere difcile. Fortunatamente, esattamente quello che Symfony studiato per fare.
Il usso di unapplicazione Symfony

Quando si lascia a Symfony la gestione di ogni richiesta, la vita molto pi facile. Symfony segue lo stesso semplice schema per ogni richiesta: Ogni pagina del proprio sito denita in un le di congurazione delle rotte, che mappa diversi URL su diverse funzioni PHP. Il compito di ogni funzione PHP, chiamata controllore, di usare linformazione della richiesta, insieme a molti altri strumenti resi disponibili da Symfony, per creare e restituire un oggetto Response. In altre parole, il controllore il posto in cui va il proprio codice: dove si interpreta la richiesta e si crea la risposta.

2.1. Libro

31

Documentazione Symfony, Release 2.0

Figura 2.1: Le richieste in entrata sono interpretate dal routing e passate alle funzioni del controllore, che restituisce oggetti Response. cos facile! Rivediamolo: Ogni richiesta esegue un le front controller; Il sistema delle rotte determina quale funzione PHP deve essere eseguita, in base allinformazione proveniente dalla richiesta e alla congurazione delle rotte creata; La giusta funzione PHP eseguita, con il proprio codice che crea e restituisce loggetto Response appropriato.
Un richiesta Symfony in azione

Senza entrare troppo in dettaglio, vediamo questo processo in azione. Supponiamo di voler aggiungere una pagina /contact alla nostra applicazione Symfony. Primo, iniziamo aggiungendo una voce per /contact nel le di congurazione delle rotte:
contact: pattern: /contact defaults: { _controller: AcmeDemoBundle:Main:contact }

Nota: Lesempio usa YAML per denire la congurazione delle rotte. La congurazione delle rotte pu essere scritta anche in altri formati, come XML o PHP. Quando qualcuno vista la pagina /contact, questa rotta viene corrisposta e il controllore specicato eseguito. Come si imparer nel capitolo delle rotte, la stringa AcmeDemoBundle:Main:contact una sintassi breve che punta a uno specico metodo PHP contactAction in una classe chiamata MainController:
class MainController { public function contactAction() { return new Response(<h1>Contattaci!</h1>); } }

32

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

In questo semplice esempio, il controllore semplicemente crea un oggetto Response con il codice HTML <h1>Contattaci!</h1>. Nel capitolo sul controllore, si imparer come un controllore possa rendere dei template, consentendo al proprio codice di presentazione (cio a qualsiasi cosa che scrive effettivamente HTML) di vivere in un le template separato. Questo consente al controllore di preoccuparsi solo delle cose difcili: interagire col database, gestire linvio di dati o linvio di messaggi email. Symfony2: costruire la propria applicazione, non i propri strumenti. Sappiamo dunque che lo scopo di unapplicazione interpretare ogni richiesta in entrata e creare unappropriata risposta. Al crescere di unapplicazione, diventa sempre pi difcile mantenere il proprio codice organizzato e mantenibile. Invariabilmente, gli stessi complessi compiti continuano a presentarsi: persistere nella base dati, rendere e riusare template, gestire invii di form, inviare email, validare i dati degli utenti e gestire la sicurezza. La buona notizia che nessuno di questi problemi unico. Symfony fornisce un framework pieno di strumenti che consentono di costruire unapplicazione, non di costruire degli strumenti. Con Symfony2, nulla viene imposto: si liberi di usare lintero framework oppure un solo pezzo di Symfony.
Strumenti isolati: i componenti di Symfony2

Cos dunque Symfony2? Primo, un insieme di oltre venti librerie indipendenti, che possono essere usate in qualsiasi progetto PHP. Queste librerie, chiamate componenti di Symfony2, contengono qualcosa di utile per quasi ogni situazione, comunque sia sviluppato il proprio progetto. Solo per nominarne alcuni: HttpFoundation (https://github.com/symfony/HttpFoundation) - Contiene le classi Request e Response, insieme ad altre classi per gestire sessioni e caricamenti di le; Routing (https://github.com/symfony/Routing) - Sistema di rotte potente e veloce, che consente di mappare uno specico URI (p.e. /contact) ad alcune informazioni su come tale richiesta andrebbe gestita (p.e. eseguendo il metodo contactAction()); Form (https://github.com/symfony/Form) - Un framework completo e essibile per creare form e gestire invii di dati; Validator (https://github.com/symfony/Validator) Un sistema per creare regole sui dati e quindi validarli, sia che i dati inviati dallutente seguano o meno tali regole; ClassLoader (https://github.com/symfony/ClassLoader) Una libreria di autoloading che consente luso di classi PHP senza bisogno di usare manualmente require sui le che contengono tali classi; Templating (https://github.com/symfony/Templating) Un insieme di strumenti per rendere template, gestire lereditariet dei template (p.e. un template decorato con un layout) ed eseguire altri compiti comuni sui template; Security (https://github.com/symfony/Security) - Una potente libreria per gestire tutti i tipi di sicurezza allinterno di unapplicazione; Translation (https://github.com/symfony/Translation) Un framework per tradurre stringhe nella propria applicazione. Tutti questi componenti sono disaccoppiati e possono essere usati in qualsiasi progetto PHP, indipendentemente dalluso del framework Symfony2. Ogni parte di essi stata realizzata per essere usata se necessario e sostituita in caso contrario.
La soluzione completa il framework Symfony2

Cos quindi il framework Symfony2? Il framework Symfony2 una libreria PHP che esegue due compiti distinti:

2.1. Libro

33

Documentazione Symfony, Release 2.0

1. Fornisce una selezione di componenti (cio i componenti di Symfony2) e librerie di terze parti (p.e. Swiftmailer per linvio di email); 2. Fornisce una pratica congurazione e una libreria collante, che lega insieme tutti i pezzi. Lo scopo del framework integrare molti strumenti indipendenti, per fornire unesperienza coerente allo sviluppatore. Anche il framework stesso un bundle (cio un plugin) che pu essere congurato o sostituito interamente. Symfony2 fornisce un potente insieme di strumenti per sviluppare rapidamente applicazioni web, senza imposizioni sulla propria applicazione. Gli utenti normali possono iniziare velocemente a sviluppare usando una distribuzione di Symfony2, che fornisce uno scheletro di progetto con congurazioni predenite ragionevoli. Gli utenti avanzati hanno il cielo come limite.

2.1.2 Symfony2 contro PHP puro


Perch Symfony2 meglio che aprire un le e scrivere PHP puro? Questo capitolo per chi non ha mai usato un framework PHP, non ha familiarit con la losoa MVC, oppure semplicemente si chiede il motivo di tutto il clamore su Symfony2. Invece di raccontare che Symfony2 consente di sviluppare software pi rapidamente e in modo migliore che con PHP puro, ve lo faremo vedere. In questo capitolo, scriveremo una semplice applicazione in PHP puro e poi la rifattorizzeremo per essere pi organizzata. Viaggeremo nel tempo, guardando le decisioni che stanno dietro ai motivi per cui lo sviluppo web si evoluto durante gli ultimi anni per diventare quello che ora. Alla ne, vedremo come Symfony2 possa salvarci da compiti banali e consentirci di riprendere il controllo del nostro codice. Un semplice blog in PHP puro In questo capitolo, costruiremo unapplicazione blog usando solo PHP puro. Per iniziare, creiamo una singola pagina che mostra le voci del blog, che sono state memorizzate nel database. La scrittura in puro PHP sporca e veloce:
<?php // index.php $link = mysql_connect(localhost, mioutente, miapassword); mysql_select_db(blog_db, $link); $result = mysql_query(SELECT id, title FROM post, $link); ?> <html> <head> <title>Lista dei post</title> </head> <body> <h1>Lista dei post</h1> <ul> <?php while ($row = mysql_fetch_assoc($result)): ?> <li> <a href="/show.php?id=<?php echo $row[id] ?>"> <?php echo $row[title] ?> </a> </li> <?php endwhile; ?> </ul> </body>

34

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

</html> <?php mysql_close($link);

Veloce da scrivere, rapido da eseguire e, al crescere dellapplicazione, impossibile da mantenere. Ci sono diversi problemi che occorre considerare: Niente verica degli errori: Che succede se la connessione al database fallisce? Scarsa organizzazione: Se lapplicazione cresce, questo singolo le diventer sempre pi immantenibile. Dove inserire il codice per gestire la compilazione di un form? Come validare i dati? Dove mettere il codice per inviare delle email? Difcolt nel riusare il codice: Essendo tutto in un solo le, non c modo di riusare alcuna parte dellapplicazione per altre pagine del blog. Nota: Un altro problema non menzionato il fatto che il database legato a MySQL. Sebbene non affrontato qui, Symfony2 integra in pieno Doctrine (http://www.doctrine-project.org), una libreria dedicata allastrazione e alla mappatura del database. Cerchiamo di metterci al lavoro per risolvere questi e altri problemi.
Isolare la presentazione

Il codice pu beneciare immediatamente dalla separazione della logica dellapplicazione dal codice che prepara la presentazione in HTML:
<?php // index.php $link = mysql_connect(localhost, mioutente, miapassword); mysql_select_db(blog_db, $link); $result = mysql_query(SELECT id, title FROM post, $link); $posts = array(); while ($row = mysql_fetch_assoc($result)) { $posts[] = $row; } mysql_close($link); // include il codice HTML di presentazione require templates/list.php;

Il codice HTML ora in un le separato (templates/list.php), che essenzialmente un le HTML che usa una sintassi PHP per template:
<html> <head> <title>Lista dei post</title> </head> <body> <h1>Lista dei post</h1> <ul> <?php foreach ($posts as $post): ?>

2.1. Libro

35

Documentazione Symfony, Release 2.0

<li> <a href="/read?id=<?php echo $post[id] ?>"> <?php echo $post[title] ?> </a> </li> <?php endforeach; ?> </ul> </body> </html>

Per convenzione, il le che contiene tutta la logica dellapplicazione, cio index.php, noto come controllore. Il termine controllore una parola che ricorrer spesso, quale che sia il linguaggio o il framework scelto. Si riferisce semplicemente alla parte del proprio codice che processa linput proveniente dallutente e prepara la risposta. In questo caso, il nostro controllore prepara i dati estratti dal database e quindi include un template, per presentare tali dati. Con il controllore isolato, possibile cambiare facilmente solo il le template necessario per rendere le voci del blog in un qualche altro formato (p.e. list.json.php per il formato JSON).
Isolare la logica dellapplicazione (il dominio)

Finora lapplicazione contiene una singola pagina. Ma se una seconda pagina avesse bisogno di usare la stessa connessione al database, o anche lo stesso array di post del blog? Rifattorizziamo il codice in modo che il comportamento centrale e le funzioni di accesso ai dati dellapplicazioni siano isolati in un nuovo le, chiamato model.php:
<?php // model.php function open_database_connection() { $link = mysql_connect(localhost, mioutente, miapassword); mysql_select_db(blog_db, $link); return $link; } function close_database_connection($link) { mysql_close($link); } function get_all_posts() { $link = open_database_connection(); $result = mysql_query(SELECT id, title FROM post, $link); $posts = array(); while ($row = mysql_fetch_assoc($result)) { $posts[] = $row; } close_database_connection($link); return $posts; }

Suggerimento: Il nome model.php usato perch la logica e laccesso ai dati di unapplicazione sono tradizionalmente noti come il livello del modello. In unapplicazione ben organizzata, la maggior parte del codice che rappre-

36

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

senta la logica di business dovrebbe stare nel modello (invece che stare in un controllore). Diversamente da questo esempio, solo una parte (o niente) del modello riguarda effettivamente laccesso a un database. Il controllore (index.php) ora molto semplice:
<?php require_once model.php; $posts = get_all_posts(); require templates/list.php;

Ora, lunico compito del controllore prendere i dati dal livello del modello dellapplicazione (il modello) e richiamare un template per rendere tali dati. Questo un esempio molto semplice del pattern model-view-controller.
Isolare il layout

A questo punto, lapplicazione stata rifattorizzata in tre parti distinte, offrendo diversi vantaggi e lopportunit di riusare quasi tutto su pagine diverse. Lunica parte del codice che non pu essere riusata il layout. Sistemiamo questo aspetto, creando un nuovo le layout.php:
<!-- templates/layout.php --> <html> <head> <title><?php echo $title ?></title> </head> <body> <?php echo $content ?> </body> </html>

Il template (templates/list.php) ora pu essere semplicato, per estendere il layout:


<?php $title = Lista dei post ?> <?php ob_start() ?> <h1>Lista dei post</h1> <ul> <?php foreach ($posts as $post): ?> <li> <a href="/read?id=<?php echo $post[id] ?>"> <?php echo $post[title] ?> </a> </li> <?php endforeach; ?> </ul> <?php $content = ob_get_clean() ?> <?php include layout.php ?>

Qui abbiamo introdotto una metodologia che consente il riuso del layout. Sfortunatamente, per poterlo fare, si costretti a usare alcune brutte funzioni PHP (ob_start(), ob_get_clean()) nel template. Symfony2 usa un componente Templating, che consente di poter fare ci in modo pulito e facile. Lo vedremo in azione tra poco.

2.1. Libro

37

Documentazione Symfony, Release 2.0

Aggiungere al blog una pagina show La pagina elenco del blog stata ora rifattorizzata in modo che il codice sia meglio organizzato e riusabile. Per provarlo, aggiungiamo al blog una pagina mostra, che mostra un singolo post del blog identicato dal parametro id. Per iniziare, creiamo nel le model.php una nuova funzione, che recupera un singolo risultato del blog a partire da un id dato:
// model.php function get_post_by_id($id) { $link = open_database_connection(); $id = mysql_real_escape_string($id); $query = SELECT date, title, body FROM post WHERE id = .$id; $result = mysql_query($query); $row = mysql_fetch_assoc($result); close_database_connection($link); return $row; }

Quindi, creiamo un le chiamato show.php, il controllore per questa nuova pagina:


<?php require_once model.php; $post = get_post_by_id($_GET[id]); require templates/show.php;

Inne, creiamo un nuovo le template, templates/show.php, per rendere il singolo post del blog:
<?php $title = $post[title] ?> <?php ob_start() ?> <h1><?php echo $post[title] ?></h1> <div class="date"><?php echo $post[date] ?></div> <div class="body"> <?php echo $post[body] ?> </div> <?php $content = ob_get_clean() ?> <?php include layout.php ?>

La creazione della seconda pagina stata molto facile e non ha implicato alcuna duplicazione di codice. Tuttavia, questa pagina introduce alcuni altri problemi, che un framework pu risolvere. Per esempio, un parametro id mancante o non valido causer un errore nella pagina. Sarebbe meglio se facesse rendere una pagina 404, ma non possiamo ancora farlo in modo facile. Inoltre, avendo dimenticato di pulire il parametro id con la funzione mysql_real_escape_string(), il database a rischio di attacchi di tipo SQL injection. Un altro grosso problema che ogni singolo controllore deve includere il le model.php. Che fare se poi occorresse includere un secondo le o eseguire un altro compito globale (p.e. garantire la sicurezza)? Nella situazione attuale, tale codice dovrebbe essere aggiunto a ogni singolo le. Se lo si dimentica in un le, speriamo che non sia qualcosa legato alla sicurezza.

38

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Un front controller alla riscossa La soluzione usare un front controller: un singolo le PHP attraverso il quale tutte le richieste sono processate. Con un front controller, gli URI dellapplicazione cambiano un poco, ma iniziano a diventare pi essibili:
Senza un front controller /index.php => Pagina della lista dei post (index.php eseguito) /show.php => Pagina che mostra il singolo post (show.php eseguito) Con index.php come front controller /index.php => Pagina della lista dei post (index.php eseguito) /index.php/show => Pagina che mostra il singolo post (index.php eseguito)

Suggerimento: La parte dellURI index.php pu essere rimossa se si usano le regole di riscrittura di Apache (o equivalente). In questo caso, lURI risultante della pagina che mostra il post sarebbe semplicemente /show. Usando un front controller, un singolo le PHP (index.php in questo caso) rende ogni richiesta. Per la pagina che mostra il post, /index.php/show eseguir in effetti il le index.php, che ora responsabile per gestire internamente le richieste, in base allURI. Come vedremo, un front controller uno strumento molto potente.
Creazione del front controller

Stiamo per fare un grosso passo avanti con lapplicazione. Con un solo le a gestire tutte le richieste, possiamo centralizzare cose come gestione della sicurezza, caricamento della congurazione, rotte. In questa applicazione, index.php deve essere abbastanza intelligente da rendere la lista dei post oppure il singolo post, in base allURI richiesto:
<?php // index.php // carica e inizializza le librerie globali require_once model.php; require_once controllers.php; // dirotta internamente la richiesta $uri = $_SERVER[REQUEST_URI]; if ($uri == /index.php) { list_action(); } elseif ($uri == /index.php/show && isset($_GET[id])) { show_action($_GET[id]); } else { header(Status: 404 Not Found); echo <html><body><h1>Pagina non trovata</h1></body></html>; }

Per una migliore organizzazione, entrambi i controllori (precedentemente index.php e show.php) sono ora funzioni PHP, entrambe spostate in un le separato, controllers.php:
function list_action() { $posts = get_all_posts(); require templates/list.php; } function show_action($id) {

2.1. Libro

39

Documentazione Symfony, Release 2.0

$post = get_post_by_id($id); require templates/show.php; }

Come front controller, index.php ha assunto un nuovo ruolo, che include il caricamento delle librerie principali e la gestione delle rotte dellapplicazione, in modo che sia richiamato uno dei due controllori (le funzioni list_action() e show_action()). In realt. il front controller inizia ad assomigliare molto al meccanismo con cui Symfony2 gestisce le richieste. Suggerimento: Un altro vantaggio di un front controller sono gli URL essibili. Si noti che lURL della pagina del singolo post pu essere cambiato da /show a /read solo cambiando un unico punto del codice. Prima, occorreva rinominare un le. In Symfony2, gli URL sono ancora pi essibili. Finora, lapplicazione si evoluta da un singolo le PHP a una struttura organizzata e che consente il riuso del codice. Dovremmo essere contenti, ma non ancora soddisfatti. Per esempio, il sistema delle rotte instabile e non riconosce che la pagina della lista (/index.php) dovrebbe essere accessibile anche tramite / (con le regole di riscrittura di Apache). Inoltre, invece di sviluppare il blog, abbiamo speso diverso tempo sullarchitettura del codice (p.e. rotte, richiamo dei controllori, template, ecc.). Ulteriore tempo sarebbe necessario per gestire linvio di form, la validazione dellinput, i log e la sicurezza. Perch dovremmo reinventare soluzioni a tutti questi problemi comuni?
Aggiungere un tocco di Symfony2

Symfony2 alla riscossa! Prima di usare effettivamente Symfony2, occorre accertarsi che PHP sappia come trovare le classi di Symfony2. Possiamo farlo grazie allautoloader fornito da Symfony. Un autoloader uno strumento che rende possibile lutilizzo di classi PHP senza includere esplicitamente il le che contiene la classe. Primo, scaricare symfony (http://symfony.com/download) e metterlo in una cartella vendor/symfony/. Poi, creare un le app/bootstrap.php. Usarlo per il require dei due le dellapplicazione e per congurare lautoloader:
<?php // bootstrap.php require_once model.php; require_once controllers.php; require_once vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php; $loader = new Symfony\Component\ClassLoader\UniversalClassLoader(); $loader->registerNamespaces(array( Symfony => __DIR__./../vendor/symfony/src, )); $loader->register();

Questo dice allautoloader dove sono le classi Symfony. In questo modo, si pu iniziare a usare le classi di Symfony senza usare listruzione require per i le che le contengono. Una delle idee principali della losoa di Symfony che il compito principale di unapplicazione sia quello di interpretare ogni richiesta e restituire una risposta. A tal ne, Symfony2 fornice sia una classe Symfony\Component\HttpFoundation\Request che una classe Symfony\Component\HttpFoundation\Response. Queste classi sono rappresentazioni orientate agli oggetti delle richieste grezze HTTP processate e delle risposte HTTP restituite. Usiamole per migliorare il nostro blog:
<?php // index.php require_once app/bootstrap.php;

40

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $request = Request::createFromGlobals(); $uri = $request->getPathInfo(); if ($uri == /) { $response = list_action(); } elseif ($uri == /show && $request->query->has(id)) { $response = show_action($request->query->get(id)); } else { $html = <html><body><h1>Pagina non trovata</h1></body></html>; $response = new Response($html, 404); } // mostra gli header e invia la risposta $response->send();

I controllori sono ora responsabili di restituire un oggetto Response. Per rendere le cose pi facili, si pu aggiungere una nuova funzione render_template(), che si comporta un po come il sistema di template di Symfony2:
// controllers.php use Symfony\Component\HttpFoundation\Response; function list_action() { $posts = get_all_posts(); $html = render_template(templates/list.php, array(posts => $posts)); return new Response($html); } function show_action($id) { $post = get_post_by_id($id); $html = render_template(templates/show.php, array(post => $post)); return new Response($html); } // funzione helper per rendere i template function render_template($path, array $args) { extract($args); ob_start(); require $path; $html = ob_get_clean(); return $html; }

Prendendo una piccola parte di Symfony2, lapplicazione diventata pi essibile e pi afdabile. La classe Request fornisce un modo di accedere alle informazioni sulla richiesta HTTP. Nello specico, il metodo getPathInfo() restituisce un URI pi pulito (restituisce sempre /show e mai /index.php/show). In questo modo, anche se lutente va su /index.php/show, lapplicazione abbastanza intelligente per dirottare la richiesta a show_action(). Loggetto Response d essibilit durante la costruzione della risposta HTTP, consentendo di aggiungere header e

2.1. Libro

41

Documentazione Symfony, Release 2.0

contenuti HTTP tramite uninterfaccia orientata agli oggetti. Mentre in questa applicazione le risposte molto semplici, tale essibilit ripagher quando lapplicazione cresce.
Lapplicazione di esempio in Symfony2

Il blog ha fatto molta strada, ma contiene ancora troppo codice per unapplicazione cos semplice. Durante il cammino, abbiamo anche inventato un semplice sistema di rotte e un metodo che usa ob_start() e ob_get_clean() per rendere i template. Se, per qualche ragione, si avesse bisogno di continuare a costruire questo framework da zero, si potrebbero almeno utilizzare i componenti Routing (https://github.com/symfony/Routing) e Templating (https://github.com/symfony/Templating), che gi risolvono questi problemi. Invece di risolvere nuovamente problemi comuni, si pu lasciare a Symfony2 il compito di occuparsene. Ecco la stessa applicazione di esempio, ora costruita in Symfony2:
<?php // src/Acme/BlogBundle/Controller/BlogController.php namespace Acme\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function listAction() { $posts = $this->get(doctrine)->getEntityManager() ->createQuery(SELECT p FROM AcmeBlogBundle:Post p) ->execute(); return $this->render(AcmeBlogBundle:Blog:list.html.php, array(posts => $posts)); } public function showAction($id) { $post = $this->get(doctrine) ->getEntityManager() ->getRepository(AcmeBlogBundle:Post) ->find($id); if (!$post) { // cause the 404 page not found to be displayed throw $this->createNotFoundException(); } return $this->render(AcmeBlogBundle:Blog:show.html.php, array(post => $post)); } }

I due controllori sono ancora leggeri. Ognuno usa la libreria ORM Doctrine per recuperare oggetti dal database e il componente Templating per rendere un template e restituire un oggetto Response. Il template della lista ora un po pi semplice:
<!-- src/Acme/BlogBundle/Resources/views/Blog/list.html.php --> <?php $view->extend(::layout.html.php) ?> <?php $view[slots]->set(title, List of Posts) ?> <h1>Lista dei post</h1>

42

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<ul> <?php foreach ($posts as $post): ?> <li> <a href="<?php echo $view[router]->generate(blog_show, array(id => $post->getId())) ?>" <?php echo $post->getTitle() ?> </a> </li> <?php endforeach; ?> </ul>

Il layout quasi identico:


<!-- app/Resources/views/layout.html.php --> <html> <head> <title><?php echo $view[slots]->output(title, Titolo predefinito) ?></title> </head> <body> <?php echo $view[slots]->output(_content) ?> </body> </html>

Nota: Lasciamo il template di show come esercizio, visto che dovrebbe essere banale crearlo basandosi sul template della lista. Quando il motore di Symfony2 (chiamato Kernel) parte, ha bisogno di una mappa che gli consenta di sapere quali controllori eseguire, in base alle informazioni della richiesta. Una congurazione delle rotte fornisce tali informazioni in un formato leggibile:
# app/config/routing.yml blog_list: pattern: /blog defaults: { _controller: AcmeBlogBundle:Blog:list } blog_show: pattern: /blog/show/{id} defaults: { _controller: AcmeBlogBundle:Blog:show }

Ora che Symfony2 gestisce tutti i compiti pi comuni, il front controller semplicissimo. E siccome fa cos poco, non si avr mai bisogno di modicarlo una volta creato (e se si usa una distribuzione di Symfony2, non servir nemmeno crearlo!):
<?php // web/app.php require_once __DIR__./../app/bootstrap.php; require_once __DIR__./../app/AppKernel.php; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel(prod, false); $kernel->handle(Request::createFromGlobals())->send();

Lunico compito del front controller inizializzare il motore di Symfony2 (il Kernel) e passargli un oggetto Request da gestire. Il nucleo di Symfony2 quindi usa la mappa delle rotte per determinare quale controllore richiamare. Proprio come prima, il metodo controllore responsabile di restituire loggetto Response nale. Non resta molto altro da fare.

2.1. Libro

43

Documentazione Symfony, Release 2.0

Per una rappresentazione visuale di come Symfony2 gestisca ogni richiesta, si veda il diagramma di usso della richiesta.
Dove consegna Symfony2

Nei capitoli successivi, impareremo di pi su come funziona ogni pezzo di Symfony e sullorganizzazione raccomandata di un progetto. Per ora, vediamo come migrare il blog da PHP puro a Symfony2 ci abbia migliorato la vita: Lapplicazione ora ha un codice organizzato chiaramente e coerentemente (sebbene Symfony non obblighi a farlo). Questo promuove la riusabilit e consente a nuovi sviluppatori di essere produttivi nel progetto in modo pi rapido. Il 100% del codice che si scrive per la propria applicazione. Non occorre sviluppare o mantenere utilit a basso livello, come autoloading, routing o rendere i controllori. Symfony2 d accesso a strumenti open source, come Doctrine e i componenti Templating, Security, Form, Validation e Translation (solo per nominarne alcuni). Lapplicazione ora gode di URL pienamente essibili, grazie al componente Routing. Larchitettura HTTP-centrica di Symfony2 d accesso a strumenti potenti, come la cache HTTP fornita dalla cache HTTP interna di Symfony2 o a strumenti ancora pi potenti, come Varnish (http://www.varnish-cache.org). Questi aspetti sono coperti in un capitolo successivo, tutto dedicato alla cache. Ma forse la parte migliore nellusare Symfony2 laccesso allintero insieme di strumenti open source di alta qualit sviluppati dalla comunit di Symfony2! Si possono trovare dei buoni bundle su KnpBundles.com (http://knpbundles.com/) Template migliori Se lo si vuole usare, Symfony2 ha un motore di template predenito, chiamato Twig (http://twig.sensiolabs.org), che rende i template pi veloci da scrivere e pi facili da leggere. Questo vuol dire che lapplicazione di esempio pu contenere ancora meno codice! Prendiamo per esempio il template della lista, scritto in Twig:
{# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #} {% extends "::layout.html.twig" %} {% block title %}Lista dei post{% endblock %} {% block body %} <h1>Lista dei post</h1> <ul> {% for post in posts %} <li> <a href="{{ path(blog_show, { id: post.id }) }}"> {{ post.title }} </a> </li> {% endfor %} </ul> {% endblock %}

Il template corrispondente layout.html.twig anche pi facile da scrivere:


{# app/Resources/views/layout.html.twig #} <html>

44

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<head> <title>{% block title %}Titolo predefinito{% endblock %}</title> </head> <body> {% block body %}{% endblock %} </body> </html>

Twig ben supportato in Symfony2. Pur essendo sempre supportati i template PHP, continueremo a discutere dei molti vantaggi offerti da Twig. Per ulteriori informazioni, vedere il capitolo dei template. Imparare di pi con le ricette Come usare PHP al posto di Twig nei template Denire i controllori come servizi

2.1.3 Installare e congurare Symfony


Lo scopo di questo capitolo quello di ottenere unapplicazione funzionante basata su Symfony. Fortunatamente, Symfony offre delle distribuzioni, che sono progetti Symfony di partenza funzionanti, che possono essere scaricati per iniziare immediatamente a sviluppare. Suggerimento: Se si stanno cercando le istruzioni per creare un nuovo progetto e memorizzarlo con un sistema di versionamento, si veda Usare un controllo di sorgenti.

Scaricare una distribuzione Symfony2

Suggerimento: Vericare innanzitutto di avere un server web (come Apache) installato e funzionante con PHP 5.3.2 o successivi. Per ulteriori informazioni sui requisiti di Symfony2, si veda il riferimento sui requisiti. Symfony2 ha dei pacchetti con delle distribuzioni, che sono applicazioni funzionanti che includono le librerie del nucleo di Symfony2, una selezione di bundle utili e alcune congurazioni predenite. Scaricando una distribuzione di Symfony2, si ottiene uno scheletro di unapplicazione funzionante, che pu essere subito usata per sviluppare la propria applicazione. Si pu iniziare visitando la pagina di scaricamento di Symfony2, http://symfony.com/download. Su questa pagina, si vedr la Symfony Standard Edition, che la distribuzione principale di Symfony2. Si possono fare due scelte: Scaricare larchivio, .tgz o .zip sono equivalenti, si pu scegliere quello che si preferisce; Scaricare la distribuzione con o senza venditori. Se si ha Git (http://git-scm.com/) installato sulla propria macchina, si dovrebbe scaricare Symfony2 senza venditori, perch aggiunge pi essibilit nellinclusione delle librerie di terze parti. Si scarichi uno degli archivi e lo si scompatti da qualche parte sotto la cartella radice del web del proprio server. Da una linea di comando UNIX, si pu farlo con uno dei seguenti comandi (sostituire ### con il vero nome del le):
# per il file .tgz tar zxvf Symfony_Standard_Vendors_2.0.###.tgz # per il file .zip unzip Symfony_Standard_Vendors_2.0.###.zip

2.1. Libro

45

Documentazione Symfony, Release 2.0

Finito il procedimento, si dovrebbe avere una cartella Symfony/, che assomiglia a questa:
www/ <- la propria cartella radice del web Symfony/ <- larchivio scompattato app/ cache/ config/ logs/ src/ ... vendor/ ... web/ app.php ...

Aggiornare i venditori

Alla ne, se la scelta caduta sullarchivio senza venditori, installare i venditori eseguendo dalla linea di comando la seguente istruzione:
php bin/vendors install

Questo comando scarica tutte le librerie dei venditori necessarie, incluso Symfony stesso, nella cartella vendor/. Per ulteriori informazioni sulla gestione delle librerie di venditori di terze parti in Symfony2, si veda Gestire le librerie dei venditori con bin/vendors e deps.
Congurazione

A questo punto, tutte le librerie di terze parti che ci occorrono sono nella cartella vendor/. Abbiamo anche una congurazione predenita dellapplicazione in app/ e un po di codice di esempio in src/. Symfony2 dispone di uno strumento visuale per la verica della congurazione del server, per assicurarsi che il server web e PHP siano congurati per usare Symfony2. Usare il seguente URL per la verica della congurazione:
http://localhost/Symfony/web/config.php

Se ci sono problemi, correggerli prima di proseguire.

46

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Impostare i permessi Un problema comune che le cartelle app/cache e app/logs devono essere scrivibili sia dal server web che dallutente della linea di comando. Su sistemi UNIX, se lutente del server web diverso da quello della linea di comando, si possono eseguire i seguenti comandi una sola volta sul proprio progetto, per assicurarsi che i permessi siano impostati correttamente. Cambiare www-data con lutente del server web e tuonome con lutente della linea di comando: 1. Usare ACL su un sistema che supporta chmod +a Molti sistemi consento di usare il comando chmod +a. Provare prima questo e, in caso di errore, provare il metodo successivo:
rm -rf app/cache/* rm -rf app/logs/*

sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs sudo chmod +a "whoami allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs

2. Usare ACL su un sistema che non supporta chmod +a Alcuni sistemi non supportano chmod +a, ma supportano un altro programma chiamato setfacl. Si potrebbe aver bisogno di abilitare il supporto ACL (https://help.ubuntu.com/community/FilePermissions#ACLs) sulla propria partizione e installare setfacl prima di usarlo (come nel caso di Ubuntu), in questo modo:
sudo setfacl -R -m u:www-data:rwx -m u:tuonome:rwx app/cache app/logs sudo setfacl -dR -m u:www-data:rwx -m u:tuonome:rwx app/cache app/logs

3. Senza usare ACL Se non possibile modicare lACL delle cartelle, occorrer modicare lumask in modo che le cartelle cache e log siano scrivibili dal gruppo o da tutti (a seconda che gli utenti di server web e linea di comando siano o meno nello stesso gruppo). Per poterlo fare, inserire la riga seguente allinizio dei le app/console, web/app.php e web/app_dev.php:
umask(0002); // Imposta i permessi a 0775 // oppure umask(0000); // Imposta i permessi a 0777

Si noti che luso di ACL raccomandato quando si ha accesso al server, perch la modica di umask non thread-safe. Quando tutto a posto, cliccare su Go to the Welcome page per accedere alla prima vera pagina di Symfony2:
http://localhost/Symfony/web/app_dev.php/

Symfony2 dovrebbe dare il suo benvenuto e congratularsi per il lavoro svolto nora!

2.1. Libro

47

Documentazione Symfony, Release 2.0

Iniziare lo sviluppo Ora che si dispone di unapplicazione Symfony2 pienamente funzionante, si pu iniziare lo sviluppo. La distribuzione potrebbe contenere del codice di esempio, vericare il le README.rst incluso nella distribuzione (aprendolo come le di testo) per sapere quale codice di esempio incluso nella distribuzione scelta e come poterlo rimuovere in un secondo momento. Per chi nuovo in Symfony, in Creare pagine in Symfony2 si pu imparare come creare pagine, cambiare congurazioni e tutte le altre cose di cui si avr bisogno nella nuova applicazione. Usare un controllo di sorgenti Se si usa un sistema di controllo di versioni, come Git o Subversion, lo si pu impostare e iniziare a fare commit nel proprio progetto, come si fa normalmente. Symfony Standard edition il punto di partenza per il nuovo progetto. Per istruzioni speciche su come impostare al meglio il proprio progetto per essere memorizzato in git, si veda Come creare e memorizzare un progetto Symfony2 in git.
Ignorare la cartella vendor/

Chi ha scelto di scaricare larchivio senza venditori pu tranquillamente ignorare lintera cartella vendor/ e non inviarla in commit al controllo di sorgenti. Con Git, lo si pu fare aggiungendo al le .gitignore la seguente riga:
vendor/

Ora la cartella dei venditori non sar inviata in commit al controllo di sorgenti. Questo bene (anzi, benissimo!) perch quando qualcun altro cloner o far checkout del progetto, potr semplicemente eseguire lo script php bin/vendors install per scaricare tutte le librerie dei venditori necessarie. 48 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

2.1.4 Creare pagine in Symfony2


La creazione di una nuova pagina in Symfony2 un semplice processo in due passi: Creare una rotta: Una rotta denisce lURL (p.e. /about) verso la pagina e specica un controllore (che una funzione PHP) che Symfony2 dovrebbe eseguire quando lURL della richiesta in arrivo corrisponde allo schema della rotta; Creare un controllore: Un controllore una funzione PHP che prende la richiesta in entrata e la trasforma in un oggetto Response di Symfony2, che viene poi restituito allutente. Questo semplice approccio molto bello, perch corrisponde al modo in cui funziona il web. Ogni interazione sul web inizia con una richiesta HTTP. Il lavoro della propria applicazione semplicemente quello di interpretare la richiesta e restituire lappropriata risposta HTTP. Symfony2 segue questa losoa e fornisce strumenti e convenzioni per mantenere la propria applicazione organizzata, man mano che cresce in utenti e in complessit. Sembra abbastanza semplice? Approfondiamo! La pagina Ciao Symfony! Iniziamo con una variazione della classica applicazione Ciao mondo!. Quando avremo nito, lutente sar in grado di ottenere un saluto personale (come Ciao Symfony) andando al seguente URL:
http://localhost/app_dev.php/hello/Symfony

In realt, si potr sostituire Symfony con qualsiasi altro nome da salutare. Per creare la pagina, seguiamo il semplice processo in due passi. Nota: La guida presume che Symfony2 sia stato gi scaricato e il server web congurato. LURL precedente presume che localhost punti alla cartella web del proprio nuovo progetto Symfony2. Per informazioni dettagliate su questo processo, si veda Installare Symfony2.

Prima di iniziare: creare il bundle

Prima di iniziare, occorrer creare un bundle. In Symfony2, un bundle come un plugin, tranne per il fatto che tutto il codice nella propria applicazione star dentro a un bundle. Un bundle non nulla di pi di una cartella che ospita ogni cosa correlata a una specica caratteristica, incluse classi PHP, congurazioni e anche fogli di stile e le JavaScript (si veda Il sistema dei bundle). Per creare un bundle chiamato AcmeHelloBundle (un bundle creato appositamente in questo capitolo), eseguire il seguente comando e seguire le istruzioni su schermo (usando tutte le opzioni predenite):
php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml

Dietro le quinte, viene creata una cartella per il bundle in src/Acme/HelloBundle. Inoltre viene aggiunta automaticamente una riga al le app/AppKernel.php, in modo che il bundle sia registrato nel kernel:
// app/AppKernel.php public function registerBundles() { $bundles = array( // ... new Acme\HelloBundle\AcmeHelloBundle(), );

2.1. Libro

49

Documentazione Symfony, Release 2.0

// ... return $bundles; }

Ora che si impostato il bundle, si pu iniziare a costruire la propria applicazione, dentro il bundle stesso.
Passo 1: creare la rotta

Per impostazione predenita, il le di congurazione delle rotte in unapplicazione Symfony2 si trova in app/config/routing.yml. Come ogni congurazione in Symfony2, si pu anche scegliere di usare XML o PHP per congurare le rotte. Se si guarda il le principale delle rotte, si vedr che Symfony ha gi aggiunto una voce, quando stato generato AcmeHelloBundle: YAML
# app/config/routing.yml AcmeHelloBundle: resource: "@AcmeHelloBundle/Resources/config/routing.yml" prefix: /

XML
<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <import resource="@AcmeHelloBundle/Resources/config/routing.xml" prefix="/" /> </routes>

PHP
// app/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->addCollection( $loader->import(@AcmeHelloBundle/Resources/config/routing.php), /, ); return $collection;

Questa voce molto basica: dice a Symfony2 di caricare la congurazione delle rotte dal le Resources/config/routing.yml, che si trova dentro AcmeHelloBundle. Questo vuol dire che si mette la congurazione delle rotte direttamente in app/config/routing.yml o si organizzano le proprie rotte attraverso la propria applicazione, e le si importano da qui. Ora che il le routing.yml del bundle stato importato, aggiungere la nuova rotta, che denisce lURL della pagina che stiamo per creare: YAML

50

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

# src/Acme/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index }

XML
<!-- src/Acme/HelloBundle/Resources/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="hello" pattern="/hello/{name}"> <default key="_controller">AcmeHelloBundle:Hello:index</default> </route> </routes>

PHP
// src/Acme/HelloBundle/Resources/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(hello, new Route(/hello/{name}, array( _controller => AcmeHelloBundle:Hello:index, ))); return $collection;

Il routing consiste di due pezzi di base: lo schema (pattern), che lURL a cui la rotta corrisponder, e un array defaults, che specica il controllore che sar eseguito. La sintassi dei segnaposto nello schema ({name}) un jolly. Vuol dire che /hello/Ryan, /hello/Fabien o ogni altro URL simile corrisponderanno a questa rotta. Il parametro del segnaposto {name} sar anche passato al controllore, in modo da poter usare il suo valore per salutare personalmente lutente. Nota: Il sistema delle rotte ha molte altre importanti caratteristiche per creare strutture di URL essibili e potenti nella propria applicazioni. Per maggiori dettagli, si veda il capitolo dedicato alle Rotte.

Passo 2: creare il controllore

Quando un URL come /hello/Ryan viene gestita dallapplicazione, la rotta hello viene corrisposta e il controllore AcmeHelloBundle:Hello:index eseguito dal framework. Il secondo passo del processo di creazione della pagina quello di creare tale controllore. Il controllore ha il nome logico AcmeHelloBundle:Hello:index ed mappato sul metodo indexAction di una classe PHP chiamata Acme\HelloBundle\Controller\Hello. Iniziamo creando questo le dentro il nostro AcmeHelloBundle:
// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Component\HttpFoundation\Response;

2.1. Libro

51

Documentazione Symfony, Release 2.0

class HelloController { }

In realt il controllore non nulla di pi di un metodo PHP, che va creato e che Symfony eseguir. qui che il proprio codice usa linformazione dalla richiesta per costruire e preparare la risorsa che stata richiesta. Tranne per alcuni casi avanzati, il prodotto nale di un controllore sempre lo stesso: un oggetto Response di Symfony2. Creare il metodo indexAction, che Symfony2 eseguir quando la rotta hello sar corrisposta:
// src/Acme/HelloBundle/Controller/HelloController.php // ... class HelloController { public function indexAction($name) { return new Response(<html><body>Ciao .$name.!</body></html>); } }

Il controllore semplice: esso crea un nuovo oggetto Response, il cui primo parametro il contenuto che sar usato dalla risposta (in questo esempio, una piccola pagina HTML). Congratulazioni! Dopo aver creato solo una rotta e un controllore, abbiamo gi una pagina pienamente funzionante! Se si impostato tutto correttamente, la propria applicazione dovrebbe salutare:
http://localhost/app_dev.php/hello/Ryan

Suggerimento: Si pu anche vedere lapplicazione nellambiente prod, visitando:


http://localhost/app.php/hello/Ryan

Se si ottiene un errore, probabilmente perch occorre pulire la cache, eseguendo:


php app/console cache:clear --env=prod --no-debug

Un terzo passo, facoltativo ma comune, del processo quello di creare un template. Nota: I controllori sono il punto principale di ingresso del proprio codice e un ingrediente chiave della creazione di pagine. Si possono trovare molte pi informazioni nel Capitolo sul controllore.

Passo 3 (facoltativo): creare il template

I template consentono di spostare tutta la presentazione (p.e. il codice HTML) in un le separato e riusare diverse porzioni del layout della pagina. Invece di scrivere il codice HTML dentro al controllore, meglio rendere un template:
1 2 3 4 5 6 7 8

// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller { public function indexAction($name)

52

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

9 10 11 12 13 14 15

{ return $this->render(AcmeHelloBundle:Hello:index.html.twig, array(name => $name)); // render a PHP template instead // return $this->render(AcmeHelloBundle:Hello:index.html.php, array(name => $name)); } }

Nota: Per poter usare il metodo render(), il controllore deve estendere la classe Symfony\Bundle\FrameworkBundle\Controller\Controller (documentazione API: Symfony\Bundle\FrameworkBundle\Controller\Controller), che aggiunge scorciatoie per compiti comuni nei controllori. Ci viene fatto nellesempio precedente aggiungendo listruzione use alla riga 4 ed estendendo Controller alla riga 6. Il metodo render() crea un oggetto Response riempito con il contenuto del template dato. Come ogni altro controllore, alla ne loggetto Response viene restituito. Si noti che ci sono due diversi esempi su come rendere il template. Per impostazione predenita, Symfony2 supporta due diversi linguaggi di template: i classici template PHP e i template, concisi ma potenti, Twig (http://twig.sensiolabs.org). Non ci si allarmi, si liberi di scegliere tra i due, o anche tutti e due nello stesso progetto. Il controllore rende il template AcmeHelloBundle:Hello:index.html.twig, che usa la seguente convenzioni dei nomi: NomeBundle:NomeControllore:NomeTemplate Questo il nome logico del template, che mappato su una locazione sica, usando la seguente convenzione: /percorso/di/NomeBundle/Resources/views/NomeControllore/NomeTemplate In questo caso, AcmeHelloBundle il nome del bundle, Hello il controllore e index.html.twig il template: Twig
1 2 3 4 5 6

{# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #} {% extends ::base.html.twig %} {% block body %} Ciao {{ name }}! {% endblock %}

PHP
<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php --> <?php $view->extend(::base.html.php) ?> Ciao <?php echo $view->escape($name) ?>!

Analizziamo il template Twig riga per riga: riga 2: Il token extends denisce un template padre. Il template denisce esplicitamente un le di layout, dentro il quale sar inserito. riga 4: Il token block dice che ogni cosa al suo interno va posta dentro un blocco chiamato body. Come vedremo, responsabilit del template padre (base.html.twig) rendere alla ne il blocco chiamato body. Il template padre, ::base.html.twig, manca delle porzioni NomeBundle e NomeControllore del suo nome (per questo ha il doppio duepunti (::) allinizio). Questo vuol dire che il template risiede fuori dai bundle, nella cartella app:

2.1. Libro

53

Documentazione Symfony, Release 2.0

Twig
{# app/Resources/views/base.html.twig #} <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Benvenuto!{% endblock %}</title> {% block stylesheets %}{% endblock %} <link rel="shortcut icon" href="{{ asset(favicon.ico) }}" /> </head> <body> {% block body %}{% endblock %} {% block javascripts %}{% endblock %} </body> </html>

PHP

<!-- app/Resources/views/base.html.php --> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title><?php $view[slots]->output(title, Benvenuto!) ?></title> <?php $view[slots]->output(stylesheets) ?> <link rel="shortcut icon" href="<?php echo $view[assets]->getUrl(favicon.ico) ?>" /> </head> <body> <?php $view[slots]->output(_content) ?> <?php $view[slots]->output(stylesheets) ?> </body> </html>

Il template di base denisce il layout HTML e rende il blocco body, che era stato denito nel template index.html.twig. Rende anche un blocco title, che si pu scegliere di denire nel template nel template index.html.twig. Poich non stato denito il blocco title nel template glio, il suo valore predenito Benvenuto!. I template sono un modo potente per rendere e organizzare il contenuto della propria pagina. Un template pu rendere qualsiasi cosa, dal codice HTML al CSS, o ogni altra cosa che il controllore abbia bisogno di restituire. Nel ciclo di vita della gestione di una richiesta, il motore dei template solo uno strumento opzionale. Si ricordi che lo scopo di ogni controllore quello di restituire un oggetto Response. I template sono uno strumento potente, ma facoltativo, per creare il contenuto per un oggetto Response. Struttura delle cartelle Dopo solo poche sezioni, si inizia gi a capire la losoa che sta dietro alla creazione e alla resa delle pagine in Symfony2. Abbiamo anche gi iniziato a vedere come i progetti Symfony2 siano strutturati e organizzati. Alla ne di questa sezione, sapremo dove cercare e inserire i vari tipi di le, e perch. Sebbene interamente essibili, per impostazione predenita, ogni application Symfony ha la stessa struttura di cartelle raccomandata: app/: Questa cartella contiene la congurazione dellapplicazione; src/: Tutto il codice PHP del progetto sta allinterno di questa cartella; vendor/: Ogni libreria dei venditori inserita qui, per convenzione; 54 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

web/: Questa la cartella radice del web e contiene ogni le accessibile pubblicamente;
La cartella web

La cartella radice del web la casa di tutti i le pubblici e statici, inclusi immagini, fogli di stile, le JavaScript. anche li posto in cui stanno tutti i front controller:
// web/app.php require_once __DIR__./../app/bootstrap.php.cache; require_once __DIR__./../app/AppKernel.php; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel(prod, false); $kernel->loadClassCache(); $kernel->handle(Request::createFromGlobals())->send();

Il le del front controller (app.php in questo esempio) il le PHP che viene eseguito quando si usa unapplicazione Symfony2 e il suo compito quello di usare una classe kernel, AppKernel, per inizializzare lapplicazione. Suggerimento: Aver un front controller vuol dire avere URL diverse e pi essibili rispetto a una tipica applicazione in puro PHP. Quando si usa un front controller, gli URL sono formattati nel modo seguente:
http://localhost/app.php/hello/Ryan

Il front controller, app.php, viene eseguito e lURL interno /hello/Ryan dirottato internamente, usando la congurazione delle rotte. Usando mod_rewrite di Apache, si pu forzare lesecuzione del le app.php senza bisogno di specicarlo nellURL:
http://localhost/hello/Ryan

Sebbene i front controller siano essenziali nella gestione di ogni richiesta, raramente si avr bisogno di modicarli o anche di pensarci. Saranno brevemente menzionati ancora nella sezione Ambienti.
La cartella dellapplicazione (app)

Come visto nel front controller, la classe AppKernel il punto di ingresso principale dellapplicazione ed responsabile di tutta la congurazione. Per questo memorizzata nella cartella app/. Questa classe deve implementare due metodi, che deniscono tutto ci di cui Symfony ha bisogno di sapere sulla propria applicazione. Non ci si deve preoccupare di questi metodi allinizio, Symfony li riempe al posto nostro con delle impostazioni predenite. registerBundles(): Restituisce un array di tutti bundle necessari per eseguire lapplicazione (vedere Il sistema dei bundle); registerContainerConfiguration(): Carica il le della congurazione principale dellapplicazione (vedere la sezione Congurazione dellapplicazione). Nello sviluppo quotidiano, per lo pi si user la cartella app/ per modicare i le di congurazione e delle rotte nella cartella app/config/ (vedere Congurazione dellapplicazione). Essa contiene anche la cartella della cache dellapplicazione (app/cache), la cartella dei log (app/logs) e la cartella dei le risorsa a livello di applicazione, come i template (app/Resources). Ognuna di queste cartella sar approfondita nei capitoli successivi.

2.1. Libro

55

Documentazione Symfony, Release 2.0

Autoload Quando Symfony si carica, un le speciale chiamato app/autoload.php viene incluso. Questo le responsabile di congurare lautoloader, che auto-caricher i le dellapplicazione dalla cartella src/ e le librerie di terze parti dalla cartella vendor/. Grazie allautoloader, non si avr mai bisogno di usare le istruzioni include o require. Al posto loro, Symfony2 usa lo spazio dei nomi di una classe per determinare la sua posizione e includere automaticamente il le al posto nostro, nel momento in cui la classe necessaria. Lautoloader gi congurato per cercare nella cartella src/ tutte le proprie classi PHP. Per poterlo far funzionare, il nome della classe e quello del le devono seguire lo stesso schema:
Nome della classe: Acme\HelloBundle\Controller\HelloController Percorso: src/Acme/HelloBundle/Controller/HelloController.php

Tipicamente, lunica volta in cui si avr bisogno di preoccuparsi del le app/autoload.php sar al momento di includere nuove librerie di terze parti nella cartella vendor/. Per maggiori informazioni sullautoload, vedere Come auto-caricare le classi.

La cartella dei sorgenti (src)

Detto semplicemente, la cartella src/ contiene tutto il codice (codice PHP, template, le di congurazione, fogli di stile, ecc.) che guida la propria applicazione. Quando si sviluppa, la gran parte del proprio lavoro sar svolto dentro uno o pi bundle creati in questa cartella. Ma cos esattamente un bundle? Il sistema dei bundle Un bundle simile a un plugin in altri software, ma anche meglio. La differenza fondamentale che tutto un bundle in Symfony2, incluse le funzionalit fondamentali del framework o il codice scritto per la propria applicazione. I bundle sono cittadini di prima classe in Symfony2. Questo fornisce la essibilit di usare caratteristiche gi pronte impacchettate in bundle di terze parti o di distribuire i propri bundle. Rende facile scegliere quali caratteristiche abilitare nella propria applicazione per ottimizzarla nel modo preferito. Nota: Pur trovando qui i fondamentali, unintera ricetta dedicata allorganizzazione e alle pratiche migliori in bundle. Un bundle semplicemente un insieme strutturato di le dentro una cartella, che implementa una singola caratteristica. Si potrebbe creare un BlogBundle, un ForumBundle o un bundle per la gestione degli utenti (molti di questi gi esistono come bundle open source). Ogni cartella contiene tutto ci che relativo a quella caratteristica, inclusi le PHP, template, fogli di stile, JavaScript, test e tutto il resto. Ogni aspetto di una caratteristica esiste in un bundle e ogni caratteristica risiede in un bundle. Unapplicazione composta di bundle, come denito nel metodo registerBundles() della classe AppKernel:
// app/AppKernel.php public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(),

56

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

new new new new new new new );

Symfony\Bundle\TwigBundle\TwigBundle(), Symfony\Bundle\MonologBundle\MonologBundle(), Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), Symfony\Bundle\DoctrineBundle\DoctrineBundle(), Symfony\Bundle\AsseticBundle\AsseticBundle(), Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),

if (in_array($this->getEnvironment(), array(dev, test))) { $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); } return $bundles; }

Col metodo registerBundles(), si ha il controllo totale su quali bundle siano usati dalla propria applicazione (inclusi i bundle del nucleo di Symfony). Suggerimento: Un bundle pu stare ovunque, purch possa essere auto-caricato (tramite lautoloader congurato in app/autoload.php).

Creare un bundle

Symfony Standard Edition contiene un task utile per creare un bundle pienamente funzionante. Ma anche creare un bundle a mano molto facile. Per dimostrare quanto semplice il sistema dei bundle, creiamo un nuovo bundle, chiamato AcmeTestBundle, e abilitiamolo. Suggerimento: La parte Acme solo un nome ttizio, che andrebbe sostituito da un nome di venditore che rappresenti la propria organizzazione (p.e. ABCTestBundle per unazienda chiamata ABC). Iniziamo creando una cartella src/Acme/TestBundle/ e aggiungendo un nuovo le chiamato AcmeTestBundle.php:
// src/Acme/TestBundle/AcmeTestBundle.php namespace Acme\TestBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; class AcmeTestBundle extends Bundle { }

Suggerimento: Il nome AcmeTestBundle segue le convenzioni sui nomi dei bundle. Si potrebbe anche scegliere di accorciare il nome del bundle semplicemente a TestBundle, chiamando la classe TestBundle (e chiamando il le TestBundle.php).

2.1. Libro

57

Documentazione Symfony, Release 2.0

Questa classe vuota lunico pezzo necessario a creare un nuovo bundle. Sebbene solitamente vuota, questa classe potente e pu essere usata per personalizzare il comportamento del bundle. Ora che abbiamo creato il bundle, abilitiamolo tramite la classe AppKernel:
// app/AppKernel.php public function registerBundles() { $bundles = array( // ... // register your bundles new Acme\TestBundle\AcmeTestBundle(), ); // ... return $bundles; }

Sebbene non faccia ancora nulla, AcmeTestBundle ora pronto per essere usato. Symfony fornisce anche uninterfaccia a linea di comando per generare uno scheletro di base per un bundle:
php app/console generate:bundle --namespace=Acme/TestBundle

Lo scheletro del bundle generato con controllore, template e rotte, tutti personalizzabili. Approfondiremo pi avanti la linea di comando di Symfony2. Suggerimento: Ogni volta che si crea un nuovo bundle o che si usa un bundle di terze parti, assicurarsi sempre che il bundle sia abilitato in registerBundles(). Se si usa il comando generate:bundle, labilitazione automatica.

Struttura delle cartelle dei bundle

La struttura delle cartelle di un bundle semplice e essibile. Per impostazione predenita, il sistema dei bundle segue un insieme di convenzioni, che aiutano a mantenere il codice coerente tra tutti i bundle di Symfony2. Si dia unocchiata a AcmeHelloBundle, perch contiene alcuni degli elementi pi comuni di un bundle: Controller/ contiene i controllori del (p.e. HelloController.php); Resources/config/ ospita la congurazione, routing.yml); Resources/views/ contiene Hello/index.html.twig); i template, compresa la congurazione delle rotte (p.e. organizzati per nome di controllore (p.e.

Resources/public/ contiene le risorse per il web (immagini, fogli di stile, ecc.) ed copiata o collegata simbolicamente alla cartella web/ del progetto, tramite il comando assets:install; Tests/ contiene tutti i test del bundle. Un bundle pu essere grande o piccolo, come la caratteristica che implementa. Contiene solo i le che occorrono e niente altro. Andando avanti nel libro, si imparer come persistere gli oggetti in un database, creare e validare form, creare traduzioni per la propria applicazione, scrivere test e molto altro. Ognuno di questi ha il suo posto e il suo ruolo dentro il bundle.

58

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Congurazione dellapplicazione Unapplicazione composta da un insieme di bundle, che rappresentano tutte le caratteristiche e le capacit dellapplicazione stessa. Ogni bundle pu essere personalizzato tramite le di congurazione, scritti in YAML, XML o PHP. Per impostazione predenita, il le di congurazione principale risiede nella cartella app/config/ si chiama config.yml, config.xml o config.php, a seconda del formato scelto: YAML
# app/config/config.yml imports: - { resource: parameters.ini } - { resource: security.yml } framework: secret: %secret% charset: UTF-8 router: { resource: "%kernel.root_dir%/config/routing.yml" } form: true csrf_protection: true validation: { enable_annotations: true } templating: { engines: [twig] } #assets_version: SomeVersionScheme session: default_locale: %locale% auto_start: true # Configurazione di Twig twig: debug: %kernel.debug% strict_variables: %kernel.debug% # ...

XML
<!-- app/config/config.xml --> <imports> <import resource="parameters.ini" /> <import resource="security.yml" /> </imports> <framework:config charset="UTF-8" secret="%secret%"> <framework:router resource="%kernel.root_dir%/config/routing.xml" /> <framework:form /> <framework:csrf-protection /> <framework:validation annotations="true" /> <framework:templating assets-version="SomeVersionScheme"> <framework:engine id="twig" /> </framework:templating> <framework:session default-locale="%locale%" auto-start="true" /> </framework:config> <!-- Configurazione di Twig --> <twig:config debug="%kernel.debug%" strict-variables="%kernel.debug%" /> <!-- ... -->

PHP

2.1. Libro

59

Documentazione Symfony, Release 2.0

$this->import(parameters.ini); $this->import(security.yml); $container->loadFromExtension(framework, array( secret => %secret%, charset => UTF-8, router => array(resource => %kernel.root_dir%/config/routing.php), form => array(), csrf-protection => array(), validation => array(annotations => true), templating => array( engines => array(twig), #assets_version => "SomeVersionScheme", ), session => array( default_locale => "%locale%", auto_start => true, ), )); // Configurazione di Twig $container->loadFromExtension(twig, array( debug => %kernel.debug%, strict_variables => %kernel.debug%, )); // ...

Nota: Vedremo esattamente come caricare ogni formato di le nella prossima sezione, Ambienti. Ogni voce di primo livello, come framework o twig, denisce la congurazione per un particolare bundle. Per esempio, la voce framework denisce la congurazione per il bundle del nucleo di Symfony FrameworkBundle e include congurazioni per rotte, template e altri sistemi fondamentali. Per ora, non ci preoccupiamo delle opzioni di congurazione speciche di ogni sezione. Il le di congurazione ha delle opzioni predenite impostate. Leggendo ed esplorando ogni parte di Symfony2, le opzioni di congurazione speciche saranno man mano approfondite. Formati di congurazione Nei vari capitoli, tutti gli esempi di congurazione saranno mostrati in tutti e tre i formati (YAML, XML e PHP). Ciascuno ha i suoi vantaggi e svantaggi. La scelta lasciata allo sviluppatore: YAML: Semplice, pulito e leggibile; XML: Pi potente di YAML e supportato nellautocompletamento dagli IDE; PHP: Molto potente, ma meno leggibile dei formati di congurazione standard.

Ambienti Unapplicazione pu girare in vari ambienti. I diversi ambienti condividono lo stesso codice PHP (tranne per il front controller), ma usano differenti congurazioni. Per esempio, un ambiente dev salver nei log gli avvertimenti e gli errori, mentre un ambiente prod solamente gli errori. Alcuni le sono ricostruiti a ogni richiesta nellambiente dev (per facilitare gli sviluppatori=, ma salvati in cache nellambiente prod. Tutti gli ambienti stanno insieme nella stessa macchina e sono eseguiti nella stessa applicazione.

60

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Un progetto Symfony2 generalmente inizia con tre ambienti (dev, test e prod), ma creare nuovi ambienti facile. Si pu vedere la propria applicazione in ambienti diversi, semplicemente cambiando il front controller nel proprio browser. Per vedere lapplicazione in ambiente dev, accedere allapplicazione tramite il front controller di sviluppo:
http://localhost/app_dev.php/hello/Ryan

Se si preferisce vedere come lapplicazione si comporta in ambiente di produzione, richiamare invece il front controller prod:
http://localhost/app.php/hello/Ryan

Essendo lambiente prod ottimizzato per la velocit, la congurazione, le rotte e i template Twig sono compilato in classi in puro PHP e messi in cache. Per vedere delle modiche in ambiente prod, occorrer pulire tali le in cache e consentire che siano ricostruiti:
php app/console cache:clear --env=prod --no-debug

Nota: Se si apre il le web/app.php, si trover che congurato esplicitamente per usare lambiente prod:
$kernel = new AppKernel(prod, false);

Si pu creare un nuovo front controller per un nuovo ambiente, copiando questo le e cambiando prod con un altro valore.

Nota: Lambiente test usato quando si eseguono i test automatici e non pu essere acceduto direttamente tramite il browser. Vedere il capitolo sui test per maggiori dettagli.

Congurazione degli ambienti

La classe AppKernel responsabile del caricare effettivamente i le di conigurazione scelti:


// app/AppKernel.php public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__./config/config_.$this->getEnvironment()..yml); }

Sappiamo gi che lestensione .yml pu essere cambiata in .xml o .php, se si preferisce usare XML o PHP per scrivere la propria congurazione. Si noti anche che ogni ambiente carica i propri le di congurazione. Consideriamo il le di congurazione per lambiente dev. YAML
# app/config/config_dev.yml imports: - { resource: config.yml } framework: router: { resource: "%kernel.root_dir%/config/routing_dev.yml" } profiler: { only_exceptions: false } # ...

XML

2.1. Libro

61

Documentazione Symfony, Release 2.0

<!-- app/config/config_dev.xml --> <imports> <import resource="config.xml" /> </imports> <framework:config> <framework:router resource="%kernel.root_dir%/config/routing_dev.xml" /> <framework:profiler only-exceptions="false" /> </framework:config> <!-- ... -->

PHP
// app/config/config_dev.php $loader->import(config.php); $container->loadFromExtension(framework, array( router => array(resource => %kernel.root_dir%/config/routing_dev.php), profiler => array(only-exceptions => false), )); // ...

La voce imports simile allistruzione include di PHP e garantisce che il le di congurazione principale (config.yml) sia caricato per primo. Il resto del le gestisce la congurazione per aumentare il livello di log, oltre ad altre impostazioni utili allambiente di sviluppo. Sia lambiente prod che quello test seguono lo stesso modello: ogni ambiente importa il le di congurazione di base e quindi modica i suoi le di congurazione per soddisfare le esigenze dello specico ambiente. Questa solo una convenzione, ma consente di riusare la maggior parte della propria congurazione e personalizzare solo le parti diverse tra gli ambienti. Riepilogo Congratulazioni! Ora abbiamo visto ogni aspetto fondamentale di Symfony2 e scoperto quanto possa essere facile e essibile. Pur essendoci ancora moltissime caratteristiche da scoprire, assicuriamoci di tenere a mente alcuni aspetti fondamentali: creare una pagine un processo in tre passi, che coinvolge una rotta, un controllore e (opzionalmente) un template. ogni progetto contienre solo alcune cartelle principali: web/ (risorse web e front controller), app/ (congurazione), src/ (i propri bundle) e vendor/ (codice di terze parti) (c anche la cartella bin/, usata per aiutare nellaggiornamento delle librerire dei venditori); ogni caratteristica in Symfony2 (incluso in nucleo del framework stesso) organizzata in bundle, insiemi strutturati di le relativi a tale caratteristica; la congurazione per ciascun bundle risiede nella cartella app/config e pu essere specicata in YAML, XML o PHP; ogni ambiente accessibile tramite un diverso front controller (p.e. app.php e app_dev.php) e carica un diverso le di congurazione. Da qui in poi, ogni capitolo introdurr strumenti sempre pi potenti e concetti sempre pi avanzati. Pi si imparer su Symfony2, pi si apprezzer la essibilit della sua architettura e la potenza che d nello sviluppo rapido di applicazioni.

62

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

2.1.5 Il controllore
Un controllore una funzione PHP da creare, che prende le informazioni dalla richiesta HTTP e dai costruttori e restituisce una risposta HTTP (come oggetto Response di Symfony2). La risposta potrebbe essere una pagina HTML, un documento XML, un array serializzato JSON, una immagine, un rinvio, un errore 404 o qualsiasi altra cosa possa venire in mente. Il controllore contiene una qualunque logica arbitraria di cui la propria applicazione necessita per rendere il contenuto di una pagina. Per vedere quanto questo semplice, diamo unocchiata a un controllore di Symfony2 in azione. Il seguente controllore renderebbe una pagina che stampa semplicemente Ciao mondo!:
use Symfony\Component\HttpFoundation\Response; public function helloAction() { return new Response(Ciao mondo!); }

Lobiettivo di un controllore sempre lo stesso: creare e restituire un oggetto Response. Lungo il percorso, potrebbe leggere le informazioni dalla richiesta, caricare una risorsa da un database, inviare unemail, o impostare informazioni sulla sessione dellutente. Ma in ogni caso, il controllore alla ne restituir un oggetto Response che verr restituito al client. Non c nessuna magia e nessun altro requisito di cui preoccuparsi! Di seguito alcuni esempi comuni: Il controllore A prepara un oggetto Response che rappresenta il contenuto della homepage di un sito. Il controllore B legge il parametro slug da una richiesta per caricare un blog da un database e creare un oggetto Response che visualizza quel blog. Se lo slug non viene trovato nel database, crea e restituisce un oggetto Response con codice di stato 404. Il controllore C gestisce linvio di un form contatti. Legge le informazioni del form dalla richiesta, salva le informazioni del contatto nella base dati e invia una email con le informazioni del contatto al webmaster. Inne, crea un oggetto Response, che rinvia il browser del client alla pagina di ringraziamento del form contatti. Richieste, controllori, ciclo di vita della risposta Ogni richiesta gestita da un progetto Symfony2 passa attraverso lo stesso semplice ciclo di vita. Il framework si occupa dei compiti ripetitivi ed inne esegue un controllore, che ospita il codice personalizzato dellapplicazione: 1. Ogni richiesta gestita da un singolo le con il controllore principale (ad esempio app.php o app_dev.php) che inizializza lapplicazione; 2. Il Router legge le informazioni dalla richiesta (ad esempio lURI), trova una rotta che corrisponde a tali informazioni e legge il parametro _controller dalla rotta; 3. Viene eseguito il controllore della rotta corrispondente e il codice allinterno del controllore crea e restituisce un oggetto Response; 4. Le intestazioni HTTP e il contenuto delloggetto Response vengono rispedite al client. Creare una pagina facile, basta creare un controllore (#3) e fare una rotta che mappa un URL su un controllore (#2). Nota: Anche se ha un nome simile, il controllore principale (front controller) diverso dagli altri controllori di cui si parla in questo capitolo. Un controllore principale un breve le PHP che presente nella propria cartella web e sul quale sono dirette tutte le richieste. Una tipica applicazione avr un controllore principale di produzione (ad esempio app.php) e un controllore principale per lo sviluppo (ad esempio app_dev.php). Probabilmente non si avr mai bisogno di modicare, visualizzare o preoccuparsi del controllore principale dellapplicazione.

2.1. Libro

63

Documentazione Symfony, Release 2.0

Un semplice controllore Mentre un controllore pu essere un qualsiasi callable PHP (una funzione, un metodo di un oggetto, o una Closure), in Symfony2, un controllore di solito un unico metodo allinterno di un oggetto controllore. I controllori sono anche chiamati azioni.
1 2 3 4 5 6 7 8 9 10 11 12

// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController { public function indexAction($name) { return new Response(<html><body>Ciao .$name.!</body></html>); } }

Suggerimento: Si noti che il controllore il metodo indexAction, che si trova allinterno di una classe controllore (HelloController). Non bisogna confondersi con i nomi: una classe controllore semplicemente un modo comodo per raggruppare insieme vari controllori/azioni. Tipicamente, la classe controllore ospiter diversi controllori/azioni (ad esempio updateAction, deleteAction, ecc). Questo controllore piuttosto semplice, ma vediamo di analizzarlo: linea 3: Symfony2 sfrutta la funzionalit degli spazi dei nomi di PHP 5.3 per utilizzarla nellintera classe dei controllori. La parola chiave use importa la classe Response, che il controllore deve restituire. linea 6: Il nome della classe la concatenazione di un nome per la classe controllore (ad esempio Hello) e la parola Controller. Questa una convenzione che fornisce coerenza ai controllori e permette loro di essere referenziati solo dalla prima parte del nome (ad esempio Hello) nella congurazione delle rotte. linea 8: A ogni azione in una classe controllore viene aggiunto il sufsso Action mentre nella congurazione delle rotte viene utilizzato come riferimento il solo nome dellazione (index). Nella sezione successiva, verr creata una rotta che mappa un URI in questa azione. Si imparer come i segnaposto delle rotte ({name}) diventano parametri del metodo dellazione ($name). linea 10: Il controllore crea e restituisce un oggetto Response. Mappare un URL in un controllore Il nuovo controllore restituisce una semplice pagina HTML. Per visualizzare questa pagina nel browser, necessario creare una rotta che mappa uno specico schema URL nel controllore: YAML
# app/config/routing.yml hello: pattern: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index }

XML
<!-- app/config/routing.xml --> <route id="hello" pattern="/hello/{name}"> <default key="_controller">AcmeHelloBundle:Hello:index</default> </route>

64

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

PHP
// app/config/routing.php $collection->add(hello, new Route(/hello/{name}, array( _controller => AcmeHelloBundle:Hello:index, )));

Andando in /hello/ryan ora viene eseguito il controllore HelloController::indexAction() e viene passato ryan nella variabile $name. Creare una pagina signica semplicemente creare un metodo controllore e associargli una rotta. Si noti la sintassi utilizzata per fare riferimento al controllore: AcmeHelloBundle:Hello:index. Symfony2 utilizza una notazione essibile per le stringhe per fare riferimento a diversi controllori. Questa la sintassi pi comune e dice a Symfony2 di cercare una classe controllore chiamata HelloController dentro un bundle chiamato AcmeHelloBundle. Il metodo indexAction() viene quindi eseguito. Per maggiori dettagli sul formato stringa utilizzato per fare riferimento ai diversi controllori, vedere Schema per il nome dei controllori. Nota: Questo esempio pone la congurazione delle rotte direttamente nella cartella app/config/. Un modo migliore per organizzare le proprie rotte quello di posizionare ogni rotta nel bundle a cui appartiene. Per ulteriori informazioni, si veda Includere risorse esterne per le rotte.

Suggerimento: Si pu imparare molto di pi sul sistema delle rotte leggendo il capitolo sulle rotte.

I parametri delle rotte come parametri del controllore

Si gi appreso che il parametro AcmeHelloBundle:Hello:index di _controller fa riferimento a un metodo HelloController::indexAction() che si trova allinterno di un bundle AcmeHelloBundle. La cosa pi interessante che i parametri vengono passati a tale metodo:
<?php // src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller { public function indexAction($name) { // ... } }

Il controllore ha un solo parametro, $name, che corrisponde al parametro {name} della rotta corrispondente (ryan nel nostro esempio). Infatti, quando viene eseguito il controllore, Symfony2 verica ogni parametro del controllore con un parametro della rotta abbinata. Vedere il seguente esempio: YAML
# app/config/routing.yml hello: pattern: /hello/{first_name}/{last_name} defaults: { _controller: AcmeHelloBundle:Hello:index, color: green }

2.1. Libro

65

Documentazione Symfony, Release 2.0

XML
<!-- app/config/routing.xml --> <route id="hello" pattern="/hello/{first_name}/{last_name}"> <default key="_controller">AcmeHelloBundle:Hello:index</default> <default key="color">green</default> </route>

PHP
// app/config/routing.php $collection->add(hello, new Route(/hello/{first_name}/{last_name}, array( _controller => AcmeHelloBundle:Hello:index, color => green, )));

Per questo il controllore pu richiedere diversi parametri:


public function indexAction($first_name, $last_name, $color) { // ... }

Si noti che entrambe le variabili segnaposto ({first_name}, {last_name}), cos come la variabile predenita color, sono disponibili come parametri nel controllore. Quando una rotta viene abbinata, le variabili segnaposto vengono unite con le impostazioni predefinite per creare un array che disponibile al controllore. La mappatura dei parametri delle rotte nei parametri del controllore semplice e essibile. Tenere in mente le seguenti linee guida mentre si sviluppa. Lordine dei parametri del controllore non ha importanza Symfony in grado di abbinare i nomi dei parametri delle rotte e i nomi delle variabili dei metodi dei controllori. In altre parole, vuol dire che il parametro {last_name} corrisponde al parametro $last_name. I parametri del controllore possono essere totalmente riordinati e continuare a funzionare perfettamente:
public function indexAction($last_name, $color, $first_name) { // .. }

Ogni parametro richiesto del controllore, deve corrispondere a uno dei parametri della rotta Il codice seguente genererebbe un RuntimeException, perch non c nessun parametro foo denito nella rotta:
public function indexAction($first_name, $last_name, $color, $foo) { // .. }

Rendere lparametro facoltativo metterebbe le cose a posto. Il seguente esempio non lancerebbe uneccezione:
public function indexAction($first_name, $last_name, $color, $foo = bar) { // .. }

Non tutti i parametri delle rotte devono essere parametri del controllore

66

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Se, per esempio, last_name non importante per il controllore, si pu ometterlo del tutto:
public function indexAction($first_name, $color) { // .. }

Suggerimento: Ogni rotta ha anche un parametro speciale _route, che equivalente al nome della rotta che stata abbinata (ad esempio hello). Anche se di solito non utile, questa ugualmente disponibile come parametro del controllore.

La Request come parametro del controllore

Per comodit, anche possibile far passare a Symfony loggetto Request come parametro al controllore. particolarmente utile quando si lavora con i form, ad esempio:
use Symfony\Component\HttpFoundation\Request; public function updateAction(Request $request) { $form = $this->createForm(...); $form->bindRequest($request); // ... }

La classe base del controllore Per comodit, Symfony2 ha una classe base Controller che aiuta nelle attivit pi comuni del controllore e d alla classe controllore laccesso a qualsiasi risorsa che potrebbe essere necessaria. Estendendo questa classe Controller, possibile usufruire di numerosi metodi helper. Aggiungere la dichiarazione use sopra alla classe Controller e modicare HelloController per estenderla:
// src/Acme/HelloBundle/Controller/HelloController.php namespace Acme\HelloBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController extends Controller { public function indexAction($name) { return new Response(<html><body>Hello .$name.!</body></html>); } }

Questo in realt non cambia nulla su come lavora il controllore. Nella prossima sezione, si imparer a conoscere i metodi helper che rende disponibili la classe base del controllore. Questi metodi sono solo scorciatoie per usare funzionalit del nucleo di Symfony2 che sono a disposizione con o senza la classe base di Controller. Un ottimo modo per vedere le funzionalit del nucleo in azione quello di guardare nella classe Symfony\Bundle\FrameworkBundle\Controller\Controller stessa.

2.1. Libro

67

Documentazione Symfony, Release 2.0

Suggerimento: Estendere la classe base opzionale in Symfony; essa contiene utili scorciatoie ma niente di obbligatorio. inoltre possibile estendere Symfony\Component\DependencyInjection\ContainerAware. Loggetto service container sar quindi accessibile tramite la propriet container.

Nota: inoltre possibile denire i Controllori come servizi.

Attivit comuni del controllore Anche se un controllore pu fare praticamente qualsiasi cosa, la maggior parte dei controllori eseguiranno gli stessi compiti di base pi volte. Questi compiti, come il rinvio, linoltro, il rendere i template e laccesso ai servizi del nucleo, sono molto semplici da gestire con Symfony2.
Rinvio

Se si vuole rinviare lutente a unaltra pagina, usare il metodo redirect():


public function indexAction() { return $this->redirect($this->generateUrl(homepage)); }

Il metodo generateUrl() solo una funzione di supporto che genera lURL per una determinata rotta. Per maggiori informazioni, vedere il capitolo Rotte. Per impostazione predenita, il metodo redirect() esegue un rinvio 302 (temporaneo). Per eseguire un rinvio 301 (permanente), modicare il secondo parametro:
public function indexAction() { return $this->redirect($this->generateUrl(homepage), 301); }

Suggerimento: Il metodo redirect() semplicemente una scorciatoia che crea un oggetto Response specializzato nel rinviare lutente. equivalente a:
use Symfony\Component\HttpFoundation\RedirectResponse; return new RedirectResponse($this->generateUrl(homepage));

Inoltro

Si pu anche facilmente inoltrare internamente a un altro controllore con il metodo forward(). Invece di redirigere il browser dellutente, fa una sotto richiesta interna e chiama il controllore specicato. Il metodo forward() restituisce loggetto Response che tornato da quel controllore:
public function { $response = name color )); indexAction($name) $this->forward(AcmeHelloBundle:Hello:fancy, array( => $name, => green

68

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

// modifica ulteriormente la risposta o ritorna direttamente return $response; }

Si noti che il metodo forward() utilizza la stessa rappresentazione stringa del controllore utilizzato nella congurazione delle rotte. In questo caso, lobiettivo della classe del controllore sar HelloController allinterno di un qualche AcmeHelloBundle. Larray passato al metodo diventa un insieme di parametri sul controllore risultante. La stessa interfaccia viene utilizzata quando si incorporano controllori nei template (vedere Inserire controllori). Lobiettivo del metodo controllore dovrebbe essere simile al seguente:
public function fancyAction($name, $color) { // ... creare e restituire un oggetto Response }

E proprio come quando si crea un controllore per una rotta, lordine dei parametri di fancyAction non importante. Symfony2 controlla i nomi degli indici chiave (ad esempio name) con i nomi dei parametri del metodo (ad esempio $name). Se si modica lordine dei parametri, Symfony2 continuer a passare il corretto valore di ogni variabile. Suggerimento: Come per gli altri metodi base di Controller, il metodo forward solo una scorciatoia per funzionalit del nucleo di Symfony2. Un inoltro pu essere eseguito direttamente attraverso il servizio http_kernel. Un inoltro restituisce un oggetto Response:
$httpKernel $response = name color )); = $this->container->get(http_kernel); $httpKernel->forward(AcmeHelloBundle:Hello:fancy, array( => $name, => green,

Rendere i template

Sebbene non sia un requisito, la maggior parte dei controllori alla ne rendono un template che responsabile di generare il codice HTML (o un altro formato) per il controllore. Il metodo renderView() rende un template e restituisce il suo contenuto. Il contenuto di un template pu essere usato per creare un oggetto Response:
$content = $this->renderView(AcmeHelloBundle:Hello:index.html.twig, array(name => $name)); return new Response($content);

Questo pu anche essere fatto in un solo passaggio con il metodo render(), che restituisce un oggetto Response contenente il contenuto di un template:
return $this->render(AcmeHelloBundle:Hello:index.html.twig, array(name => $name));

In entrambi i casi, verr reso il template Resources/views/Hello/index.html.twig presente allinterno di AcmeHelloBundle. Il motore per i template di Symfony spiegato in dettaglio nel capitolo Template. Suggerimento: Il metodo renderView una scorciatoia per utilizzare direttamente il servizio templating. Il servizio templating pu anche essere utilizzato in modo diretto:
$templating = $this->get(templating); $content = $templating->render(AcmeHelloBundle:Hello:index.html.twig, array(name => $name));

2.1. Libro

69

Documentazione Symfony, Release 2.0

Accesso ad altri servizi

Quando si estende la classe base del controllore, possibile accedere a qualsiasi servizio di Symfony2 attraverso il metodo get(). Di seguito si elencano alcuni servizi comuni che potrebbero essere utili:
$request = $this->getRequest(); $templating = $this->get(templating); $router = $this->get(router); $mailer = $this->get(mailer);

Ci sono innumerevoli altri servizi disponibili e si incoraggia a denirne di propri. Per elencare tutti i servizi disponibili, utilizzare il comando di console container:debug:
php app/console container:debug

Per maggiori informazioni, vedere il capitolo Contenitore di servizi. Gestire gli errori e le pagine 404 Quando qualcosa non si trova, si dovrebbe utilizzare bene il protocollo HTTP e restituire una risposta 404. Per fare questo, si lancia uno speciale tipo di eccezione. Se si sta estendendo la classe base del controllore, procedere come segue:
public function indexAction() { $product = // recuperare loggetto dal database if (!$product) { throw $this->createNotFoundException(Il prodotto non esiste); } return $this->render(...); }

Il metodo createNotFoundException() crea uno speciale oggetto NotFoundHttpException, che in ultima analisi innesca una risposta HTTP 404 allinterno di Symfony. Naturalmente si liberi di lanciare qualunque classe Exception nel controllore - Symfony2 ritorner automaticamente un codice di risposta HTTP 500.
throw new \Exception(Qualcosa andato storto!);

In ogni caso, allutente nale viene mostrata una pagina di errore predenita e allo sviluppatore viene mostrata una pagina di errore completa di debug (quando si visualizza la pagina in modalit debug). Entrambe le pagine di errore possono essere personalizzate. Per ulteriori informazioni, leggere nel ricettario Come personalizzare le pagine di errore. Gestione della sessione Symfony2 fornisce un oggetto sessione che si pu utilizzare per memorizzare le informazioni sullutente (che sia una persona reale che utilizza un browser, un bot, o un servizio web) attraverso le richieste. Per impostazione predenita, Symfony2 memorizza gli attributi in un cookie utilizzando le sessioni PHP native. 70 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Memorizzare e recuperare informazioni dalla sessione pu essere fatto da qualsiasi controllore:


$session = $this->getRequest()->getSession(); // memorizza un attributo per riutilizzarlo durante una successiva richiesta dellutente $session->set(foo, bar); // in un altro controllore per unaltra richiesta $foo = $session->get(foo); // imposta il locale dellutente $session->setLocale(fr);

Questi attributi rimarranno sullutente per il resto della sessione utente.


Messaggi ash

anche possibile memorizzare messaggi di piccole dimensioni che vengono memorizzati sulla sessione utente solo per una richiesta successiva. Ci utile quando si elabora un form: si desidera rinviare e avere un messaggio speciale mostrato sulla richiesta successiva. Questo tipo di messaggi sono chiamati messaggi ash. Per esempio, immaginiamo che si stia elaborando un form inviato:
public function updateAction() { $form = $this->createForm(...); $form->bindRequest($this->getRequest()); if ($form->isValid()) { // fare una qualche elaborazione $this->get(session)->setFlash(notice, Le modifiche sono state salvate!); return $this->redirect($this->generateUrl(...)); } return $this->render(...); }

Dopo lelaborazione della richiesta, il controllore imposta un messaggio ash notice e poi rinvia. Il nome (notice) non signicativo, solo quello che si utilizza per identicare il tipo del messaggio. Nel template dellazione successiva, il seguente codice pu essere utilizzato per rendere il messaggio notice: Twig
{% if app.session.hasFlash(notice) %} <div class="flash-notice"> {{ app.session.flash(notice) }} </div> {% endif %}

PHP
<?php if ($view[session]->hasFlash(notice)): ?> <div class="flash-notice"> <?php echo $view[session]->getFlash(notice) ?> </div> <?php endif; ?>

2.1. Libro

71

Documentazione Symfony, Release 2.0

Per come sono stati progettati, i messaggi ash sono destinati a vivere esattamente per una richiesta (hanno la durata di un ash). Sono progettati per essere utilizzati in redirect esattamente come stato fatto in questo esempio. Loggetto Response Lunico requisito per un controllore restituire un oggetto Response. La classe Symfony\Component\HttpFoundation\Response una astrazione PHP sulla risposta HTTP - il messaggio testuale che contiene gli header HTTP e il contenuto che viene inviato al client:
// crea una semplice risposta con un codice di stato 200 (il predefinito) $response = new Response(Hello .$name, 200); // crea una risposta JSON con un codice di stato 200 $response = new Response(json_encode(array(name => $name))); $response->headers->set(Content-Type, application/json);

Suggerimento: La propriet headers un oggetto Symfony\Component\HttpFoundation\HeaderBag con alcuni utili metodi per leggere e modicare gli header Response. I nomi degli header sono normalizzati in modo che lutilizzo di Content-Type sia equivalente a content-type o anche a content_type.

Loggetto Request Oltre ai valori dei segnaposto delle rotte, il controllore ha anche accesso alloggetto Request quando si estende la classe base Controller:
$request = $this->getRequest(); $request->isXmlHttpRequest(); // una richiesta Ajax? $request->getPreferredLanguage(array(en, fr)); $request->query->get(page); // recupera un parametro $_GET $request->request->get(page); // recupera un parametro $_POST

Come loggetto Response, le intestazioni della richiesta sono memorizzate in un oggetto HeaderBag e sono facilmente accessibili. Considerazioni nali Ogni volta che si crea una pagina, necessario scrivere del codice che contiene la logica per quella pagina. In Symfony, questo codice si chiama controllore, ed una funzione PHP che pu fare qualsiasi cosa di cui ha bisogno per tornare loggetto nale Response che verr restituito allutente. Per rendere la vita pi facile, si pu scegliere di estendere una classe base Controller, che contiene metodi scorciatoia per molti compiti comuni del controllore. Per esempio, dal momento che non si vuole mettere il codice HTML nel controllore, possibile utilizzare il metodo render() per rendere e restituire il contenuto da un template. In altri capitoli, si vedr come il controllore pu essere usato per persistere e recuperare oggetti da un database, processare i form inviati, gestire la cache e altro ancora.

72

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Imparare di pi dal ricettario Come personalizzare le pagine di errore Denire i controllori come servizi

2.1.6 Le rotte
URL ben realizzati sono una cosa assolutamente da avere per qualsiasi applicazione web seria. Questo signica lasciarsi alle spalle URL del tipo index.php?article_id=57 in favore di qualcosa come /read/intro-to-symfony. Avere essibilit ancora pi importante. Che cosa succede se necessario modicare lURL di una pagina da /blog a /news? Quanti collegamenti bisogna cercare e aggiornare per realizzare la modica? Se si stanno utilizzando le rotte di Symfony la modica semplice. Le rotte di Symfony2 consentono di denire URL creativi che possono essere mappati in differenti aree dellapplicazione. Entro la ne del capitolo, si sar in grado di: Creare rotte complesse che mappano i controllori Generare URL allinterno di template e controllori Caricare le risorse delle rotte dai bundle (o da altre parti) Eseguire il debug delle rotte Le rotte in azione Una rotta una mappatura tra uno schema di URL e un controllore. Per esempio, supponiamo che si voglia gestire un qualsiasi URL tipo /blog/my-post o /blog/all-about-symfony e inviarlo a un controllore che cerchi e visualizzi quel post del blog. La rotta semplice: YAML
# app/config/routing.yml blog_show: pattern: /blog/{slug} defaults: { _controller: AcmeBlogBundle:Blog:show }

XML

<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="blog_show" pattern="/blog/{slug}"> <default key="_controller">AcmeBlogBundle:Blog:show</default> </route> </routes>

PHP
// app/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route;

2.1. Libro

73

Documentazione Symfony, Release 2.0

$collection = new RouteCollection(); $collection->add(blog_show, new Route(/blog/{slug}, array( _controller => AcmeBlogBundle:Blog:show, ))); return $collection;

Lo schema denito dalla rotta blog_show si comporta come /blog/*, dove al carattere jolly viene dato il nome slug. Per lURL /blog/my-blog-post, la variabile slug ottiene il valore my-blog-post, che disponibile per lutilizzo nel controllore (proseguire nella lettura). Il parametro _controller una chiave speciale che dice a Symfony quale controllore dovrebbe essere eseguito quando un URL corrisponde a questa rotta. La stringa _controller detta nome logico. Segue un pattern che punta a un specico classe e metodo PHP:
// src/Acme/BlogBundle/Controller/BlogController.php namespace Acme\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function showAction($slug) { $blog = // usare la variabile $slug per interrogare il database return $this->render(AcmeBlogBundle:Blog:show.html.twig, array( blog => $blog, )); } }

Congratulazioni! Si appena creata la prima rotta, collegandola ad un controllore. Ora, quando si visita /blog/my-post, verr eseguito il controllore showAction e la variabile $slug avr valore my-post. Questo lobiettivo delle rotte di Symfony2: mappare lURL di una richiesta in un controllore. Lungo la strada, si impareranno tutti i trucchi per mappare facilmente anche gli URL pi complessi. Le rotte: funzionamento interno Quando allapplicazione viene fatta una richiesta, questa contiene un indirizzo alla esatta risorsa che il client sta richiedendo. Questo indirizzo chiamato URL, (o URI) e potrebbe essere /contact, /blog/read-me, o qualunque altra cosa. Prendere ad esempio la seguente richiesta HTTP:
GET /blog/my-blog-post

Lobiettivo del sistema delle rotte di Symfony2 quello di analizzare questo URL e determinare quale controller dovrebbe essere eseguito. Lintero processo il seguente: 1. La richiesta gestita dal front controller di Symfony2 (ad esempio app.php); 2. Il nucleo di Symfony2 (ad es. il kernel) chiede al router di ispezionare la richiesta; 3. Il router verica la corrispondenza dellURL in arrivo con una specica rotta e restituisce informazioni sulla rotta, tra le quali il controllore che deve essere eseguito; 4. Il kernel di Symfony2 esegue il controllore, che alla ne restituisce un oggetto Response.

74

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Figura 2.2: Lo strato delle rotte uno strumento che traduce lURL in ingresso in uno specico controllore da eseguire. Creazione delle rotte Symfony carica tutte le rotte per lapplicazione da un singolo le con la congurazione delle rotte. Il le generalmente app/config/routing.yml, ma pu essere congurato per essere qualunque cosa (compreso un le XML o PHP) tramite il le di congurazione dellapplicazione: YAML
# app/config/config.yml framework: # ... router: { resource: "%kernel.root_dir%/config/routing.yml" }

XML
<!-- app/config/config.xml --> <framework:config ...> <!-- ... --> <framework:router resource="%kernel.root_dir%/config/routing.xml" /> </framework:config>

PHP
// app/config/config.php $container->loadFromExtension(framework, array( // ... router => array(resource => %kernel.root_dir%/config/routing.php), ));

Suggerimento: Anche se tutte le rotte sono caricate da un singolo le, una pratica comune includere ulteriori risorse di rotte allinterno del le. Vedere la sezione Includere risorse esterne per le rotte per maggiori informazioni.

Congurazione di base delle rotte

Denire una rotta semplice e una tipica applicazione avr molte rotte. Una rotta di base costituita da due parti: il pattern da confrontare e un array defaults: 2.1. Libro 75

Documentazione Symfony, Release 2.0

YAML
_welcome: pattern: defaults: / { _controller: AcmeDemoBundle:Main:homepage }

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="_welcome" pattern="/"> <default key="_controller">AcmeDemoBundle:Main:homepage</default> </route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(_welcome, new Route(/, array( _controller => AcmeDemoBundle:Main:homepage, ))); return $collection;

Questa rotta corrisponde alla homepage (/) e la mappa nel controllore AcmeDemoBundle:Main:homepage. La stringa _controller tradotta da Symfony2 in una funzione PHP effettiva, ed eseguita. Questo processo verr spiegato a breve nella sezione Schema per il nome dei controllori.
Rotte con segnaposti

Naturalmente il sistema delle rotte supporta rotte molto pi interessanti. Molte rotte conterranno uno o pi segnaposto jolly: YAML
blog_show: pattern: defaults: /blog/{slug} { _controller: AcmeBlogBundle:Blog:show }

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="blog_show" pattern="/blog/{slug}"> <default key="_controller">AcmeBlogBundle:Blog:show</default> </route> </routes>

76

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(blog_show, new Route(/blog/{slug}, array( _controller => AcmeBlogBundle:Blog:show, ))); return $collection;

Lo schema verr soddisfatto da qualsiasi cosa del tipo /blog/*. Meglio ancora, il valore corrispondente il segnaposto {slug} sar disponibile allinterno del controllore. In altre parole, se lURL /blog/hello-world, una variabile $slug, con un valore hello-world, sar disponibile nel controllore. Questo pu essere usato, ad esempio, per caricare il post sul blog che verica questa stringa. Tuttavia lo schema non deve corrispondere semplicemente a /blog. Questo perch, per impostazione predenita, tutti i segnaposto sono obbligatori. Questo comportamento pu essere cambiato aggiungendo un valore segnaposto allarray defaults.
Segnaposto obbligatori e opzionali

Per rendere le cose pi eccitanti, aggiungere una nuova rotta che visualizza un elenco di tutti i post disponibili del blog per questa applicazione immaginaria di blog: YAML
blog: pattern: defaults: /blog { _controller: AcmeBlogBundle:Blog:index }

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="blog" pattern="/blog"> <default key="_controller">AcmeBlogBundle:Blog:index</default> </route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(blog, new Route(/blog, array( _controller => AcmeBlogBundle:Blog:index, ))); return $collection;

2.1. Libro

77

Documentazione Symfony, Release 2.0

Finora, questa rotta il pi semplice possibile: non contiene segnaposto e corrisponde solo allesatto URL /blog. Ma cosa succede se si ha bisogno di questa rotta per supportare limpaginazione, dove /blog/2 visualizza la seconda pagina dellelenco post del blog? Bisogna aggiornare la rotta per avere un nuovo segnaposto {page}: YAML
blog: pattern: defaults: /blog/{page} { _controller: AcmeBlogBundle:Blog:index }

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="blog" pattern="/blog/{page}"> <default key="_controller">AcmeBlogBundle:Blog:index</default> </route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(blog, new Route(/blog/{page}, array( _controller => AcmeBlogBundle:Blog:index, ))); return $collection;

Come il precedente segnaposto {slug}, il valore che verica {page} sar disponibile allinterno del controllore. Il suo valore pu essere usato per determinare quale insieme di post del blog devono essere visualizzati per una data pagina. Un attimo per! Dal momento che i segnaposto per impostazione predenita sono obbligatori, questa rotta non avr pi corrispondenza con il semplice /blog. Invece, per vedere la pagina 1 del blog, si avr bisogno di utilizzare lURL /blog/1! Dal momento che non c soluzione per una complessa applicazione web, modicare la rotta per rendere il parametro {page} opzionale. Questo si fa includendolo nella collezione defaults: YAML
blog: pattern: defaults: /blog/{page} { _controller: AcmeBlogBundle:Blog:index, page: 1 }

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="blog" pattern="/blog/{page}"> <default key="_controller">AcmeBlogBundle:Blog:index</default> <default key="page">1</default>

78

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

</route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(blog, new Route(/blog/{page}, array( _controller => AcmeBlogBundle:Blog:index, page => 1, ))); return $collection;

Aggiungendo page alla chiave defaults, il segnaposto {page} non pi obbligatorio. LURL /blog corrisponder a questa rotta e il valore del parametro page verr impostato a 1. Anche lURL /blog/2 avr corrispondenza, dando al parametro page il valore 2. Perfetto. /blog /blog/1 /blog/2 {page} = 1 {page} = 1 {page} = 2

Aggiungere requisiti

Si dia uno sguardo veloce alle rotte che sono state create nora: YAML
blog: pattern: defaults: blog_show: pattern: defaults: /blog/{page} { _controller: AcmeBlogBundle:Blog:index, page: 1 }

/blog/{slug} { _controller: AcmeBlogBundle:Blog:show }

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="blog" pattern="/blog/{page}"> <default key="_controller">AcmeBlogBundle:Blog:index</default> <default key="page">1</default> </route> <route id="blog_show" pattern="/blog/{slug}"> <default key="_controller">AcmeBlogBundle:Blog:show</default> </route> </routes>

PHP

2.1. Libro

79

Documentazione Symfony, Release 2.0

use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(blog, new Route(/blog/{page}, array( _controller => AcmeBlogBundle:Blog:index, page => 1, ))); $collection->add(blog_show, new Route(/blog/{show}, array( _controller => AcmeBlogBundle:Blog:show, ))); return $collection;

Si riesce a individuare il problema? Notare che entrambe le rotte hanno schemi che vericano URL del tipo /blog/*. Il router di Symfony sceglie sempre la prima rotta corrispondente che trova. In altre parole, la rotta blog_show non sar mai trovata. Invece, un URL del tipo /blog/my-blog-post verr abbinato alla prima rotta (blog) restituendo il valore senza senso my-blog-post per il parametro {page}. URL /blog/2 /blog/my-blog-post rotta blog blog paramettri {page} = 2 {page} = my-blog-post

La risposta al problema aggiungere rotte obbligatorie. Le rotte in questo esempio potrebbero funzionare perfettamente se lo schema /blog/{page} fosse vericato solo per gli URL dove {page} fosse un numero intero. Fortunatamente, i requisiti possono essere scritti tramite espressioni regolari e aggiunti per ogni parametro. Per esempio: YAML
blog: pattern: /blog/{page} defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } requirements: page: \d+

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="blog" pattern="/blog/{page}"> <default key="_controller">AcmeBlogBundle:Blog:index</default> <default key="page">1</default> <requirement key="page">\d+</requirement> </route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(blog, new Route(/blog/{page}, array(

80

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

_controller => AcmeBlogBundle:Blog:index, page => 1, ), array( page => \d+, ))); return $collection;

Il requisito \d+ una espressione regolare che dice che il valore del parametro {page} deve essere una cifra (cio un numero). La rotta blog sar comunque abbinata a un URL del tipo /blog/2 (perch 2 un numero), ma non sar pi abbinata a un URL tipo /blog/my-blog-post (perch my-blog-post non un numero). Come risultato, un URL tipo /blog/my-blog-post ora verr correttamente abbinato alla rotta blog_show. URL /blog/2 /blog/my-blog-post rotta blog blog_show paramettri {page} = 2 {slug} = my-blog-post

Vincono sempre le rotte che compaiono prima Il signicato di tutto questo che lordine delle rotte molto importante. Se la rotta blog_show fosse stata collocata sopra la rotta blog, lURL /blog/2 sarebbe stato abbinato a blog_show invece di blog perch il parametro {slug} di blog_show non ha requisiti. Utilizzando lordinamento appropriato e dei requisiti intelligenti, si pu realizzare qualsiasi cosa. Poich i requisiti dei parametri sono espressioni regolari, la complessit e la essibilit di ogni requisito dipende da come li si scrive. Si supponga che la pagina iniziale dellapplicazione sia disponibile in due diverse lingue, in base allURL: YAML
homepage: pattern: /{culture} defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en } requirements: culture: en|fr

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="homepage" pattern="/{culture}"> <default key="_controller">AcmeDemoBundle:Main:homepage</default> <default key="culture">en</default> <requirement key="culture">en|fr</requirement> </route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(homepage, new Route(/{culture}, array(

2.1. Libro

81

Documentazione Symfony, Release 2.0

_controller => AcmeDemoBundle:Main:homepage, culture => en, ), array( culture => en|fr, ))); return $collection;

Per le richieste in entrata, la porzione {culture} dellURL viene controllata tramite lespressione regolare (en|fr). / /en /fr /es {culture} = en {culture} = en {culture} = fr non si abbina a questa rotta

Aggiungere requisiti al metodo HTTP

In aggiunta agli URL, si pu anche vericare il metodo della richiesta entrante (ad esempio GET, HEAD, POST, PUT, DELETE). Si supponga di avere un form contatti con due controllori: uno per visualizzare il form (su una richiesta GET) e uno per lelaborazione del form dopo che stato inviato (su una richiesta POST). Questo pu essere realizzato con la seguente congurazione per le rotte: YAML
contact: pattern: /contact defaults: { _controller: AcmeDemoBundle:Main:contact } requirements: _method: GET contact_process: pattern: /contact defaults: { _controller: AcmeDemoBundle:Main:contactProcess } requirements: _method: POST

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="contact" pattern="/contact"> <default key="_controller">AcmeDemoBundle:Main:contact</default> <requirement key="_method">GET</requirement> </route> <route id="contact_process" pattern="/contact"> <default key="_controller">AcmeDemoBundle:Main:contactProcess</default> <requirement key="_method">POST</requirement> </route> </routes>

PHP

82

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(contact, new Route(/contact, array( _controller => AcmeDemoBundle:Main:contact, ), array( _method => GET, ))); $collection->add(contact_process, new Route(/contact, array( _controller => AcmeDemoBundle:Main:contactProcess, ), array( _method => POST, ))); return $collection;

Nonostante il fatto che queste due rotte abbiano schemi identici (/contact), la prima rotta corrisponder solo a richieste GET e la seconda rotta corrisponder solo a richieste POST. Questo signica che possibile visualizzare il form e invia e inviarlo utilizzando lo stesso URL ma controllori distinti per le due azioni. Nota: Se non viene specicato nessun requisito _method, la rotta verr abbinata con tutti i metodi. Come avviene per gli altri requisiti, il requisito _method viene analizzato come una espressione regolare. Per abbinare le richieste GET o POST, si pu utilizzare GET|POST.
Esempio di rotte avanzate

A questo punto, si ha tutto il necessario per creare una complessa struttura di rotte in Symfony. Quello che segue un esempio di quanto essibile pu essere il sistema delle rotte: YAML
article_show: pattern: /articles/{culture}/{year}/{title}.{_format} defaults: { _controller: AcmeDemoBundle:Article:show, _format: html } requirements: culture: en|fr _format: html|rss year: \d+

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="article_show" pattern="/articles/{culture}/{year}/{title}.{_format}"> <default key="_controller">AcmeDemoBundle:Article:show</default> <default key="_format">html</default> <requirement key="culture">en|fr</requirement> <requirement key="_format">html|rss</requirement> <requirement key="year">\d+</requirement>

2.1. Libro

83

Documentazione Symfony, Release 2.0

</route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(homepage, new Route(/articles/{culture}/{year}/{title}.{_format}, array( _controller => AcmeDemoBundle:Article:show, _format => html, ), array( culture => en|fr, _format => html|rss, year => \d+, ))); return $collection;

Come si sar visto, questa rotta verr soddisfatta solo quando la porzione {culture} dellURL en o fr e se {year} un numero. Questa rotta mostra anche come sia possibile utilizzare un punto tra i segnaposto al posto di una barra. Gli URL corrispondenti a questa rotta potrebbero essere del tipo: /articles/en/2010/my-post /articles/fr/2010/my-post.rss Il parametro speciale _format per le rotte Questo esempio mette in evidenza lo speciale parametro per le rotte _format. Quando si utilizza questo parametro, il valore cercato diventa il formato della richiesta delloggetto Request. In denitiva, il formato della richiesta usato per cose tipo impostare il Content-Type della risposta (per esempio una richiesta di formato json si traduce in un Content-Type con valore application/json). Pu essere utilizzato anche nel controllore per rendere un template diverso per ciascun valore di _format. Il parametro _format un modo molto potente per rendere lo stesso contenuto in formati diversi.

Parametri speciali per le rotte

Come si visto, ogni parametro della rotta o valore predenito disponibile come parametro nel metodo del controllore. Inoltre, ci sono tre parametri speciali: ciascuno aggiunge una funzionalit allinterno dellapplicazione: _controller: Come si visto, questo parametro viene utilizzato per determinare quale controllore viene eseguito quando viene trovata la rotta; _format: Utilizzato per impostare il formato della richiesta (per saperne di pi); _locale: Utilizzato per impostare il locale sulla sessione (per saperne di pi); Schema per il nome dei controllori Ogni rotta deve avere un parametro _controller, che determina quale controllore dovrebbe essere eseguito quando si accoppia la rotta. Questo parametro utilizza un semplice schema stringa, chiamato nome logico del controllore, che Symfony mappa in uno specico metodo PHP di una certa classe. Lo schema ha tre parti, ciascuna separata da due punti:

84

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

bundle:controllore:azione Per esempio, se _controller ha valore AcmeBlogBundle:Blog:show signica: Bundle AcmeBlogBundle Classe del controllore BlogController Nome del metodo showAction

Il controllore potrebbe essere simile a questo:


// src/Acme/BlogBundle/Controller/BlogController.php namespace Acme\BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class BlogController extends Controller { public function showAction($slug) { // ... } }

Si noti che Symfony aggiunge la stringa Controller al nome della classe (Blog => BlogController) e Action al nome del metodo (show => showAction). Si potrebbe anche fare riferimento a questo controllore con il nome completo di classe e metodo: Acme\BlogBundle\Controller\BlogController::showAction. Ma seguendo alcune semplici convenzioni, il nome logico pi conciso e permette una maggiore essibilit. Nota: Oltre allutilizzo del nome logico o il nome completo della classe, Symfony supporta un terzo modo per fare riferimento a un controllore. Questo metodo utilizza solo un separatore due punti (ad esempio nome_servizio:indexAction) e fa riferimento al controllore come un servizio (vedere Denire i controllori come servizi).

Parametri delle rotte e parametri del controllore I parametri delle rotte (ad esempio {slug}) sono particolarmente importanti perch ciascuno reso disponibile come parametro al metodo del controllore:
public function showAction($slug) { // ... }

In realt, lintera collezione defaults viene unita con i valori del parametro per formare un singolo array. Ogni chiave di questo array disponibile come parametro sul controllore. In altre parole, per ogni parametro del metodo del controllore, Symfony cerca per un parametro della rotta con quel nome e assegna il suo valore a tale parametro. Nellesempio avanzato di cui sopra, qualsiasi combinazioni (in qualsiasi ordine) delle seguenti variabili potrebbe essere usati come parametri per il metodo showAction(): $culture $year $title $_format $_controller 2.1. Libro 85

Documentazione Symfony, Release 2.0

Dal momento che il segnaposto e la collezione defaults vengono uniti insieme, disponibile anche la variabile $_controller. Per una trattazione pi dettagliata, vedere I parametri delle rotte come parametri del controllore. Suggerimento: inoltre possibile utilizzare una variabile speciale $_route, che impostata sul nome della rotta che stata abbinata.

Includere risorse esterne per le rotte Tutte le rotte vengono caricate attraverso un singolo le di congurazione, generalmente app/config/routing.yml (vedere Creazione delle rotte sopra). In genere, per, si desidera caricare le rotte da altri posti, come un le di rotte presente allinterno di un bundle. Questo pu essere fatto importando il le: YAML
# app/config/routing.yml acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml"

XML
<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <import resource="@AcmeHelloBundle/Resources/config/routing.xml" /> </routes>

PHP
// app/config/routing.php use Symfony\Component\Routing\RouteCollection; $collection = new RouteCollection(); $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php")); return $collection;

Nota: Quando si importano le risorse in formato YAML, la chiave (ad esempio acme_hello) non ha senso. Basta essere sicuri che sia unica, in modo che nessunaltra linea la sovrascriva. La chiave resource carica la data risorsa di rotte. In questo esempio la risorsa il percorso completo di un le, dove la sintassi scorciatoia @AcmeHelloBundle viene risolta con il percorso del bundle. Il le importato potrebbe essere tipo questo: YAML
# src/Acme/HelloBundle/Resources/config/routing.yml acme_hello: pattern: /hello/{name} defaults: { _controller: AcmeHelloBundle:Hello:index }

XML

86

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<!-- src/Acme/HelloBundle/Resources/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="acme_hello" pattern="/hello/{name}"> <default key="_controller">AcmeHelloBundle:Hello:index</default> </route> </routes>

PHP
// src/Acme/HelloBundle/Resources/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(acme_hello, new Route(/hello/{name}, array( _controller => AcmeHelloBundle:Hello:index, ))); return $collection;

Le rotte di questo le sono analizzate e caricate nello stesso modo del le principale delle rotte.
Pressare le rotte importate

Si pu anche scegliere di fornire un presso per le rotte importate. Per esempio, si supponga di volere che la rotta acme_hello abbia uno schema nale con /admin/hello/{name} invece di /hello/{name}: YAML
# app/config/routing.yml acme_hello: resource: "@AcmeHelloBundle/Resources/config/routing.yml" prefix: /admin

XML
<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <import resource="@AcmeHelloBundle/Resources/config/routing.xml" prefix="/admin" /> </routes>

PHP
// app/config/routing.php use Symfony\Component\Routing\RouteCollection;

$collection = new RouteCollection(); $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"), /a

2.1. Libro

87

Documentazione Symfony, Release 2.0

return $collection;

La stringa /admin ora verr preposta allo schema di ogni rotta caricata dalla nuova risorsa delle rotte. Visualizzare e fare il debug delle rotte Laggiunta e la personalizzazione di rotte utile, ma lo anche essere in grado di visualizzare e recuperare informazioni dettagliate sulle rotte. Il modo migliore per vedere tutte le rotte dellapplicazione tramite il comando di console router:debug. Eseguire il comando scrivendo il codice seguente dalla cartella radice del progetto
php app/console router:debug

Il comando visualizzer un utile elenco di tutte le rotte congurate nellapplicazione:


homepage contact contact_process article_show blog blog_show ANY GET POST ANY ANY ANY / /contact /contact /articles/{culture}/{year}/{title}.{_format} /blog/{page} /blog/{slug}

Inoltre possibile ottenere informazioni molto speciche su una singola rotta mettendo il nome della rotta dopo il comando:
php app/console router:debug article_show

Generazione degli URL Il sistema delle rotte dovrebbe anche essere usato per generare gli URL. In realt, il routing un sistema bidirezionale: mappa lURL in un controllore + parametri e una rotta + parametri di nuovo in un URL. I metodi :method:Symfony\\Component\\Routing\\Router::match e :method:Symfony\\Component\\Routing\\Router::generate formano questo sistema bidirezionale. Si prenda la rotta dellesempio precedente blog_show:
$params = $router->match(/blog/my-blog-post); // array(slug => my-blog-post, _controller => AcmeBlogBundle:Blog:show) $uri = $router->generate(blog_show, array(slug => my-blog-post)); // /blog/my-blog-post

Per generare un URL, necessario specicare il nome della rotta (ad esempio blog_show) ed eventuali caratteri jolly (ad esempio slug = my-blog-post) usati nello schema per questa rotta. Con queste informazioni, qualsiasi URL pu essere generata facilmente:
class MainController extends Controller { public function showAction($slug) { // ... $url = $this->get(router)->generate(blog_show, array(slug => my-blog-post)); } }

In una sezione successiva, si imparer a generare URL da tempalte interni.

88

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Suggerimento: Se la propria applicazione usa richieste AJAX, si potrebbe voler generare URL in JavaScript, che siano basate sulla propria congurazione delle rotte. Usando FOSJsRoutingBundle (https://github.com/FriendsOfSymfony/FOSJsRoutingBundle), lo si pu fare:
var url = Routing.generate(blog_show, { "slug": my-blog-post});

Per ultetiori informazioni, vedere la documentazione del bundle.

Generare URL assoluti

Per impostazione predenita, il router genera URL relativi (ad esempio /blog). Per generare un URL assoluto, sufciente passare true come terzo parametro del metodo generate():
$router->generate(blog_show, array(slug => my-blog-post), true); // http://www.example.com/blog/my-blog-post

Nota: Lhost che viene usato quando si genera un URL assoluto lhost delloggetto Request corrente. Questo viene rilevato automaticamente in base alle informazioni sul server fornite da PHP. Quando si generano URL assolute per script che devono essere eseguiti da riga di comando, sar necessario impostare manualmente lhost desiderato sulloggetto Request:
$request->headers->set(HOST, www.example.com);

Generare URL con query string

Il metodo generate accetta un array di valori jolly per generare lURI. Ma se si passano quelli extra, saranno aggiunti allURI come query string:
$router->generate(blog, array(page => 2, category => Symfony)); // /blog/2?category=Symfony

Generare URL da un template

Il luogo pi comune per generare un URL allinterno di un template quando si creano i collegamenti tra le varie pagine dellapplicazione. Questo viene fatto esattamente come prima, ma utilizzando una funzione helper per i template: Twig
<a href="{{ path(blog_show, { slug: my-blog-post }) }}"> Read this blog post. </a>

PHP

<a href="<?php echo $view[router]->generate(blog_show, array(slug => my-blog-post)) ?>"> Read this blog post. </a>

Possono anche essere generati gli URL assoluti. Twig

2.1. Libro

89

Documentazione Symfony, Release 2.0

<a href="{{ url(blog_show, { slug: my-blog-post }) }}"> Read this blog post. </a>

PHP

<a href="<?php echo $view[router]->generate(blog_show, array(slug => my-blog-post), true Read this blog post. </a>

Riassunto Il routing un sistema per mappare lURL delle richieste in arrivo in una funzione controllore che dovrebbe essere chiamata a processare la richiesta. Il tutto permette sia di creare URL belle che di mantenere la funzionalit dellapplicazione disaccoppiata da questi URL. Il routing un meccanismo bidirezionale, nel senso che dovrebbe anche essere utilizzato per generare gli URL. Imparare di pi dal ricettario Come forzare le rotte per utilizzare sempre HTTPS

2.1.7 Creare e usare i template


Come noto, il controllore responsabile della gestione di ogni richiesta che arriva a unapplicazione Symfony2. In realt, il controllore delega la maggior parte del lavoro pesante ad altri punti, in modo che il codice possa essere testato e riusato. Quando un controllore ha bisogno di generare HTML, CSS o ogni altro contenuto, passa il lavoro al motore dei template. In questo capitolo si imparer come scrivere potenti template, che possano essere riusati per restituire del contenuto allutente, popolare corpi di email e altro. Si impareranno scorciatoie, modi intelligenti di estendere template e come riusare il codice di un template Template Un template un semplice le testuale che pu generare qualsiasi formato basato sul testo (HTML, XML, CSV, LaTeX ...). Il tipo pi familiare di template un template PHP, un le testuale analizzato da PHP che contiene un misto di testo e codice PHP:
<!DOCTYPE html> <html> <head> <title>Benvenuto in Symfony!</title> </head> <body> <h1><?php echo $page_title ?></h1> <ul id="navigation"> <?php foreach ($navigation as $item): ?> <li> <a href="<?php echo $item->getHref() ?>"> <?php echo $item->getCaption() ?> </a> </li> <?php endforeach; ?> </ul>

90

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

</body> </html>

Ma Symfony2 possiede un linguaggio di template ancora pi potente, chiamato Twig (http://twig.sensiolabs.org). Twig consente di scrivere template concisi e leggibili, pi amichevoli per i graci e, in molti modi, pi potenti dei template PHP:
<!DOCTYPE html> <html> <head> <title>Benvenuto in Symfony!</title> </head> <body> <h1>{{ page_title }}</h1> <ul id="navigation"> {% for item in navigation %} <li><a href="{{ item.href }}">{{ item.caption }}</a></li> {% endfor %} </ul> </body> </html>

Twig denisce due tipi di sintassi speciali: {{ ... }}: Dice qualcosa: stampa una variabile o il risultato di unespressione nel template;

{% ... %}: Fa qualcosa: un tag che controlla la logica del template; usato per eseguire istruzioni, come il ciclo for dellesempio. Nota: C una terza sintassi usata per creare commenti: {# questo un commento #}. Questa sintassi pu essere usata su righe multiple, come il suo equivalente PHP /* commento */. Twig contiene anche dei ltri, che modicano il contenuto prima che sia reso. Lesempio seguente rende la variabile title tutta maiuscola, prima di renderla:
{{ title | upper }}

Twig ha una lunga lista di tag (http://twig.sensiolabs.org/doc/tags/index.html) e ltri (http://twig.sensiolabs.org/doc/lters/index.html), disponibili in maniera predenita. Si possono anche aggiungere le proprie estensioni (http://twig.sensiolabs.org/doc/extensions.html) a Twig, se necessario. Suggerimento: facile registrare unestensione di Twig: basta creare un nuovo servizio e assegnarli il tag twig.extension. Come vedremo nella documentazione, Twig supporta anche le funzioni e si possono aggiungere facilmente nuove funzioni. Per esempio, di seguito viene usato un tag standard for e la funzione cycle per stampare dieci tag div, con classi alternate odd e even:
{% for i in 0..10 %} <div class="{{ cycle([odd, even], i) }}"> <!-- un po di codice HTML --> </div> {% endfor %}

In questo capitolo, gli esempi dei template saranno mostrati sia in Twig che in PHP.

2.1. Libro

91

Documentazione Symfony, Release 2.0

Perch Twig? I template di Twig sono pensati per essere semplici e non considerano i tag PHP. Questo intenzionale: il sistema di template di Twig fatto per esprimere una presentazione, non logica di programmazione. Pi si usa Twig, pi se ne pu apprezzare beneci e distinzione. E, ovviamente, essere amati da tutti i graci del mondo. Twig pu anche far cose che PHP non pu fare, come una vera ereditariet dei template (i template Twig compilano in classi PHP che ereditano tra di loro), controllo degli spazi vuoti, sandbox e inclusione di funzioni e ltri personalizzati, che hanno effetti solo sui template. Twig possiede poche caratteristiche che rendono la scrittura di template pi facile e concisa. Si prenda il seguente esempio, che combina un ciclo con unistruzione logica if:
<ul> {% for user in users %} <li>{{ user.username }}</li> {% else %} <li>Nessun utente trovato</li> {% endfor %} </ul>

Cache di template Twig

Twig veloce. Ogni template Twig compilato in una classe nativa PHP, che viene resa a runtime. Le classi compilate sono situate nella cartella app/cache/{environment}/twig (dove {environment} lambiente, come dev o prod) e in alcuni casi possono essere utili durante il debug. Vedere Ambienti per maggiori informazioni sugli ambienti. Quando si abilita la modalit di debug (tipicamente in ambiente dev), un template Twig viene automaticamente ricompilato a ogni modica subita. Questo vuol dire che durante lo sviluppo si possono tranquillamente effettuare cambiamenti a un template Twig e vedere immediatamente le modiche, senza doversi preoccupare di pulire la cache. Quando la modalit di debug disabilitata (tipicamente in ambiente prod), tuttavia, occorre pulire la cache di Twig, in modo che i template Twig siano rigenerati. Si ricordi di farlo al deploy della propria applicazione. Ereditariet dei template e layout Molto spesso, i template di un progetto condividono elementi comuni, come la testata, il pi di pagina, una barra laterale e altro. In Symfony2, ci piace pensare a questo problema in modo differente: un template pu essere decorato da un altro template. Funziona esattamente come per le classi PHP: lereditariet dei template consente di costruire un template layout di base, che contiene tutti gli elementi comuni del proprio sito, deniti come blocchi (li si pensi come classi PHP con metodi base). Un template glio pu estendere un layout di base e sovrascrivere uno qualsiasi dei suoi blocchi (li si pensi come sottoclassi PHP che sovrascrivono alcuni metodi della classe genitrice). Primo, costruire un le per il layout di base: Twig
{# app/Resources/views/base.html.twig #} <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{% block title %}Applicazione di test{% endblock %}</title> </head> <body>

92

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<div id="sidebar"> {% block sidebar %} <ul> <li><a href="/">Home</a></li> <li><a href="/blog">Blog</a></li> </ul> {% endblock %} </div> <div id="content"> {% block body %}{% endblock %} </div> </body> </html>

PHP
<!-- app/Resources/views/base.html.php --> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title><?php $view[slots]->output(title, Applicazione di test) ?></title> </head> <body> <div id="sidebar"> <?php if ($view[slots]->has(sidebar): ?> <?php $view[slots]->output(sidebar) ?> <?php else: ?> <ul> <li><a href="/">Home</a></li> <li><a href="/blog">Blog</a></li> </ul> <?php endif; ?> </div> <div id="content"> <?php $view[slots]->output(body) ?> </div> </body> </html>

Nota: Sebbene la discussione sullereditariet dei template sia relativa a Twig, la losoa condivisa tra template Twig e template PHP. Questo template denisce lo scheletro del documento HTML di base di una semplice pagina a due colonne. In questo esempio, tre aree {% block %} sono denite (title, sidebar e body). Ciascun blocco pu essere sovrascritto da un template glio o lasciato alla sua implementazione predenita. Questo template potrebbe anche essere reso direttamente. In questo caso, i blocchi title, sidebar e body manterrebbero semplicemente i valori predeniti usati in questo template. Un template glio potrebbe assomigliare a questo: Twig
{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} {% extends ::base.html.twig %}

2.1. Libro

93

Documentazione Symfony, Release 2.0

{% block title %}I post fighi del mio blog{% endblock %} {% block body %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> <p>{{ entry.body }}</p> {% endfor %} {% endblock %}

PHP
<!-- src/Acme/BlogBundle/Resources/views/Blog/index.html.php --> <?php $view->extend(::base.html.php) ?> <?php $view[slots]->set(title, I post fighi del mio blog) ?> <?php $view[slots]->start(body) ?> <?php foreach ($blog_entries as $entry): ?> <h2><?php echo $entry->getTitle() ?></h2> <p><?php echo $entry->getBody() ?></p> <?php endforeach; ?> <?php $view[slots]->stop() ?>

Nota: Il template padre identicato da una speciale sintassi di stringa (::base.html.twig) che indica che il template si trova nella cartella app/Resources/views del progetto. Questa convenzione di nomi spiegata nel dettaglio in Nomi e posizioni dei template. La chiave dellereditariet dei template il tag {% extends %}. Questo dice al motore dei template di valutare prima il template base, che imposta il layout e denisce i vari blocchi. Quindi viene reso il template glio e i blocchi title e body del padre vengono rimpiazzati da quelli del glio. A seconda del valore di blog_entries, loutput potrebbe assomigliare a questo:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>I post fighi del mio blog</title> </head> <body> <div id="sidebar"> <ul> <li><a href="/">Home</a></li> <li><a href="/blog">Blog</a></li> </ul> </div> <div id="content"> <h2>Il mio primo post</h2> <p>Il testo del primo post.</p> <h2>Un altro post</h2> <p>Il testo del secondo post.</p> </div> </body> </html>

94

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Si noti che, siccome il template glio non denisce un blocco sidebar, viene usato al suo posto il valore del template padre. Il contenuto di un tag {% block %} in un template padre sempre usato come valore predenito. Si possono usare tanti livelli di ereditariet quanti se ne desiderano. Nella prossima sezione, sar spiegato un modello comuni a tre livelli di ereditariet, insieme al modo in cui i template sono organizzati in un progetto Symfony2. Quando si lavora con lereditariet dei template, ci sono alcuni concetti da tenere a mente: se si usa {% extends %} in un template, deve essere il primo tag di quel template. Pi tag {% block %} si hanno in un template, meglio . Si ricordi che i template gli non devono denire tutti i blocchi del padre, quindi si possono creare molti blocchi nei template base e dar loro dei valori predeniti adeguati. Pi blocchi si hanno in un template base, pi sar essibile il layout. Se ci si trova ad aver duplicato del contenuto in un certo numero di template, vuol dire che probabilmente si dovrebbe spostare tale contenuto in un {% block %} di un template padre. In alcuni casi, una soluzione migliore potrebbe essere spostare il contenuto in un nuovo template e usare include (vedere Includere altri template). Se occorre prendere il contenuto di un blocco da un template padre, si pu usare la funzione {{ parent() }}. utile quando si vuole aggiungere il contenuto di un template padre, invece di sovrascriverlo completamente:
{% block sidebar %} <h3>Sommario</h3> ... {{ parent() }} {% endblock %}

Nomi e posizioni dei template Per impostazione predenita, i template possono stare in una di queste posizioni: app/Resources/views/: La cartella views di unapplicazione pu contenere template di base a livello di applicazione (p.e. i layout dellapplicazione), ma anche template che sovrascrivono template di bundle (vedere Sovrascrivere template dei bundle); percorso/bundle/Resources/views/: Ogni bundle ha i suoi template, nella sua cartella Resources/views (e nelle sotto-cartelle). La maggior parte dei template dentro a un bundle. Symfony2 usa una sintassi stringa bundle:controllore:template per i template. Questo consente diversi tipi di template, ciascuno in un posto specico: AcmeBlogBundle:Blog:index.html.twig: Questa sintassi usata per specicare un template per una determinata pagina. Le tre parti della stringa, ognuna separata da due-punti (:), hanno il seguente signicato: AcmeBlogBundle: (bundle) src/Acme/BlogBundle); il template dentro AcmeBlogBundle (p.e.

Blog: (controllore) indica che il template nella sotto-cartella Blog di Resources/views; index.html.twig: (template) il nome del le index.html.twig. Ipotizzando che AcmeBlogBundle sia dentro src/Acme/BlogBundle, il percorso nale del layout sarebbe src/Acme/BlogBundle/Resources/views/Blog/index.html.twig. AcmeBlogBundle::layout.html.twig: Questa sintassi si riferisce a un template di base specico di AcmeBlogBundle. Poich la parte centrale, controllore, manca, (p.e. Blog), il template Resources/views/layout.html.twig dentro AcmeBlogBundle.

2.1. Libro

95

Documentazione Symfony, Release 2.0

::base.html.twig: Questa sintassi si riferisce a un template di base o a un layout di applicazione. Si noti che la stringa inizia con un doppio due-punti (::), il che vuol dire che mancano sia la parte del bundle che quella del controllore. Questo signica che il template non in alcun bundle, ma invece nella cartella radice app/Resources/views/. Nella sezione Sovrascrivere template dei bundle si potr trovare come ogni template dentro AcmeBlogBundle, per esempio, possa essere sovrascritto mettendo un template con lo stesso nome nella cartella app/Resources/AcmeBlogBundle/views/. Questo d la possibilit di sovrascrivere template di qualsiasi bundle. Suggerimento: Si spera che la sintassi dei nomi risulti familiare: la stessa convenzione di nomi usata per lo Schema per il nome dei controllori.

Sufssi dei template

Il formato bundle:controllore:template di ogni template specica dove il le del template si trova. Ogni nome di template ha anche due estensioni, che specicano il formato e il motore per quel template. AcmeBlogBundle:Blog:index.html.twig - formato HTML, motore Twig AcmeBlogBundle:Blog:index.html.php - formato HTML, motore PHP AcmeBlogBundle:Blog:index.css.twig - formato CSS, motore Twig Per impostazione predenita, ogni template Symfony2 pu essere scritto in Twig o in PHP, e lultima parte dellestensione (p.e. .twig o .php) specica quale di questi due motori va usata. La prima parte dellestensione, (p.e. .html, .css, ecc.) il formato nale che il template generer. Diversamente dal motore, che determina il modo in cui Symfony2 analizza il template, si tratta di una tattica organizzativa usata nel caso in cui alcune risorse debbano essere rese come HTML (index.html.twig), XML (index.xml.twig) o in altri formati. Per maggiori informazioni, leggere la sezione Debug. Nota: I motori disponibili possono essere congurati e se ne possono aggiungere di nuovi. Vedere Congurazione dei template per maggiori dettagli.

Tag e helper Dopo aver parlato delle basi dei template, di che nomi abbiano e di come si possa usare lereditariet, la parte pi difcile passata. In questa sezione, si potranno conoscere un gran numero di strumenti disponibili per aiutare a compiere i compiti pi comuni sui template, come includere altri template, collegare pagine e inserire immagini. Symfony2 dispone di molti tag di Twig specializzati e di molte funzioni, che facilitano il lavoro del progettista di template. In PHP, il sistema di template fornisce un sistema estensibile di helper, che fornisce utili caratteristiche nel contesto dei template. Abbiamo gi visto i tag predeniti ({% block %} e {% extends %}), cos come un esempio di helper PHP ($view[slots]). Vediamone alcuni altri.
Includere altri template

Spesso si vorranno includere lo stesso template o lo stesso pezzo di codice in pagine diverse. Per esempio, in unapplicazione con nuovi articoli, il codice del template che mostra un articolo potrebbe essere usato sulla pagina dei dettagli dellarticolo, un una pagina che mostra gli articoli pi popolari o in una lista degli articoli pi recenti.

96

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Quando occorre riusare un pezzo di codice PHP, tipicamente si posta il codice in una nuova classe o funzione PHP. Lo stesso vale per i template. Spostando il codice del template da riusare in un template a parte, pu essere incluso in qualsiasi altro template. Primo, creare il template che occorrer riusare. Twig
{# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #} <h2>{{ article.title }}</h2> <h3 class="byline">by {{ article.authorName }}</h3> <p> {{ article.body }} </p>

PHP
<!-- src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.php --> <h2><?php echo $article->getTitle() ?></h2> <h3 class="byline">by <?php echo $article->getAuthorName() ?></h3> <p> <?php echo $article->getBody() ?> </p>

Includere questo template da un altro template semplice: Twig


{# src/Acme/ArticleBundle/Resources/Article/list.html.twig #} {% extends AcmeArticleBundle::layout.html.twig %} {% block body %} <h1>Articoli recenti<h1>

{% for article in articles %} {% include AcmeArticleBundle:Article:articleDetails.html.twig with {article: article {% endfor %} {% endblock %}

PHP
<!-- src/Acme/ArticleBundle/Resources/Article/list.html.php --> <?php $view->extend(AcmeArticleBundle::layout.html.php) ?> <?php $view[slots]->start(body) ?> <h1>Articoli recenti</h1>

<?php foreach ($articles as $article): ?> <?php echo $view->render(AcmeArticleBundle:Article:articleDetails.html.php, array(art <?php endforeach; ?> <?php $view[slots]->stop() ?>

Il template incluso usando il tag {% include %}. Si noti che il nome del template segue le stesse tipiche convenzioni. Il template articleDetails.html.twig usa una variabile article. Questa viene passata nel template list.html.twig usando il comando with. Suggerimento: La sintassi {article: article} la sintassi standard di Twig per gli array associativi (con chiavi non numeriche). Se avessimo avuto bisogno di passare pi elementi, sarebbe stato cos: {pippo: pippo, pluto: pluto}.

2.1. Libro

97

Documentazione Symfony, Release 2.0

Inserire controllori

A volte occorre fare di pi che includere semplici template. Si supponga di avere nel proprio layout una barra laterale, che contiene i tre articoli pi recenti. Recuperare i tre articoli potrebbe implicare una query al database, o lesecuzione di altra logica, che non si pu fare dentro a un template. La soluzione semplicemente linserimento del risultato di un intero controllore dal proprio template. Primo, creare un controllore che rende un certo numero di articoli recenti:
// src/Acme/ArticleBundle/Controller/ArticleController.php class ArticleController extends Controller { public function recentArticlesAction($max = 3) { // chiamare il database o altra logica per ottenere "$max" articoli recenti $articles = ...;

return $this->render(AcmeArticleBundle:Article:recentList.html.twig, array(articles => $a } }

Il template recentList molto semplice: Twig


{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} {% for article in articles %} <a href="/article/{{ article.slug }}"> {{ article.title }} </a> {% endfor %}

PHP
<!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php --> <?php foreach ($articles in $article): ?> <a href="/article/<?php echo $article->getSlug() ?>"> <?php echo $article->getTitle() ?> </a> <?php endforeach; ?>

Nota: Si noti che abbiamo barato e inserito a mano lURL dellarticolo in questo esempio (p.e. /article/*slug*). Questa non una buona pratica. Nella prossima sezione, vedremo come farlo correttamente. Per includere il controllore, occorrer fare riferimento a esso usando la sintassi standard per i controllori (cio bundle:controllore:azione): Twig
{# app/Resources/views/base.html.twig #} ... <div id="sidebar"> {% render "AcmeArticleBundle:Article:recentArticles" with {max: 3} %} </div>

PHP

98

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<!-- app/Resources/views/base.html.php --> ... <div id="sidebar"> <?php echo $view[actions]->render(AcmeArticleBundle:Article:recentArticles, array(max </div>

Ogni volta che ci si trova ad aver bisogno di una variabile o di un pezzo di inforamzione a cui non si ha accesso in un template, considerare di rendere un controllore. I controllori sono veloci da eseguire e promuovono buona organizzazione e riuso del codice.
Collegare le pagine

Creare collegamenti alle altre pagine nella propria applicazioni uno dei lavori pi comuni per un template. Invece di inserire a mano URL nei template, usare la funzione path di Twig (o lhelper router in PHP) per generare URL basati sulla congurazione delle rotte. Pi avanti, se si vuole modicare lURL di una particolare pagina, tutto ci di cui si avr bisogno cambiare la congurazione delle rotte: i template genereranno automaticamente il nuovo URL. Primo, collegare la pagina _welcome, accessibile tramite la seguente congurazione delle rotte: YAML
_welcome: pattern: / defaults: { _controller: AcmeDemoBundle:Welcome:index }

XML
<route id="_welcome" pattern="/"> <default key="_controller">AcmeDemoBundle:Welcome:index</default> </route>

PHP
$collection = new RouteCollection(); $collection->add(_welcome, new Route(/, array( _controller => AcmeDemoBundle:Welcome:index, ))); return $collection;

Per collegare la pagina, usare la funzione path di Twig e riferirsi alla rotta: Twig
<a href="{{ path(_welcome) }}">Home</a>

PHP
<a href="<?php echo $view[router]->generate(_welcome) ?>">Home</a>

Come ci si aspettava, questo generer lURL /. Vediamo come funziona con una rotta pi complessa: YAML
article_show: pattern: /article/{slug} defaults: { _controller: AcmeArticleBundle:Article:show }

XML 2.1. Libro 99

Documentazione Symfony, Release 2.0

<route id="article_show" pattern="/article/{slug}"> <default key="_controller">AcmeArticleBundle:Article:show</default> </route>

PHP
$collection = new RouteCollection(); $collection->add(article_show, new Route(/article/{slug}, array( _controller => AcmeArticleBundle:Article:show, ))); return $collection;

In questo caso, occorre specicare sia il nome della rotta (article_show) che il valore del parametro {slug}. Usando questa rotta, rivisitiamo il template recentList della sezione precedente e colleghiamo correttamente gli articoli: Twig
{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} {% for article in articles %} <a href="{{ path(article_show, { slug: article.slug }) }}"> {{ article.title }} </a> {% endfor %}

PHP

<!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php --> <?php foreach ($articles in $article): ?> <a href="<?php echo $view[router]->generate(article_show, array(slug => $article->getS <?php echo $article->getTitle() ?> </a> <?php endforeach; ?>

Suggerimento: Si pu anche generare un URL assoluto, usando la funzione url di Twig:


<a href="{{ url(_welcome) }}">Home</a>

Lo stesso si pu fare nei template PHP, passando un terzo parametro al metodo generate():
<a href="<?php echo $view[router]->generate(_welcome, array(), true) ?>">Home</a>

Collegare le risorse

I template solitamente hanno anche riferimenti a immagini, Javascript, fogli di stile e altre risorse. Certamente, si potrebbe inserire manualmente il percorso a tali risorse (p.e. /images/logo.png), ma Symfony2 fornisce unopzione pi dinamica, tramite la funzione assets di Twig: Twig
<img src="{{ asset(images/logo.png) }}" alt="Symfony!" /> <link href="{{ asset(css/blog.css) }}" rel="stylesheet" type="text/css" />

PHP

100

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<img src="<?php echo $view[assets]->getUrl(images/logo.png) ?>" alt="Symfony!" />

<link href="<?php echo $view[assets]->getUrl(css/blog.css) ?>" rel="stylesheet" type="text/c

Lo scopo principale della funzione asset rendere pi portabile la propria applicazione. Se la propria applicazione si trova nella radice del proprio host (p.e. http://example.com), i percorsi resi dovrebbero essere del tipo /images/logo.png. Se invece la propria applicazione si trova in una sotto-cartella (p.e. http://example.com/my_app), ogni percorso dovrebbe includere la sotto-cartella (p.e. /my_app/images/logo.png). La funzione asset si prende cura di questi aspetti, determinando in che modo usata la propria applicazione e generando i percorsi adeguati. Inoltre, se si usa la funzione asset, Symfony pu aggiungere automaticamente un parametro allURL della risorsa, per garantire che le risorse statiche aggiornate non siano messe in cache. Per esempio, /images/logo.png potrebbe comparire come /images/logo.png?v2. Per ulteriori informazioni, vedere lopzione di congurazione assets_version. Includere fogli di stile e Javascript in Twig Nessun sito sarebbe completo senza linclusione di le Javascript e fogli di stile. In Symfony, linclusione di tali risorse gestita elegantemente sfruttando lereditariet dei template. Suggerimento: Questa sezione insegner la losoa che sta dietro linclusione di fogli di stile e Javascript in Symfony. Symfony dispone di unaltra libreria, chiamata Assetic, che segue la stessa losoa, ma consente di fare cose molto pi interessanti con queste risorse. Per maggiori informazioni sulluso di Assetic, vedere Come usare Assetic per la gestione delle risorse. Iniziamo aggiungendo due blocchi al template di base, che conterranno le risorse: uno chiamato stylesheets, dentro al tag head, e laltro chiamato javascripts, appena prima della chiusura del tag body. Questi blocchi conterranno tutti i fogli di stile e i Javascript che occorrerano al sito:
{# app/Resources/views/base.html.twig #} <html> <head> {# ... #} {% block stylesheets %} <link href="{{ asset(/css/main.css) }}" type="text/css" rel="stylesheet" /> {% endblock %} </head> <body> {# ... #} {% block javascripts %} <script src="{{ asset(/js/main.js) }}" type="text/javascript"></script> {% endblock %} </body> </html>

cos facile! Ma che succede quando si ha bisogno di includere un foglio di stile o un Javascript aggiuntivo in un template glio? Per esempio, supponiamo di avere una pagina di contatti e che occorra includere un foglio di stile contact.css solo su tale pagina. Da dentro il template della pagina di contatti, fare come segue:
{# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #} {# extends ::base.html.twig #} {% block stylesheets %}

2.1. Libro

101

Documentazione Symfony, Release 2.0

{{ parent() }} <link href="{{ asset(/css/contact.css) }}" type="text/css" rel="stylesheet" /> {% endblock %} {# ... #}

Nel template glio, basta sovrascrivere il blocco stylesheets ed inserire il nuovo tag del foglio di stile nel blocco stesso. Ovviamente, poich vogliamo aggiungere contenuto al blocco padre (e non sostituirlo), occorre usare la funzione parent() di Twig, per includere tutto ci che sta nel blocco stylesheets del template di base. Si possono anche includere risorse dalla cartella Resources/public del proprio bundle. Occorre poi eseguire il comando php app/console assets:install target [--symlink], che copia (o collega) i le nella posizione corretta (la posizione predenita sotto la cartella web).
<link href="{{ asset(bundles/acmedemo/css/contact.css) }}" type="text/css" rel="stylesheet" />

Il risultato nale una pagina che include i fogli di stile main.css e contact.css. Variabili globali nei template Durante ogni richiesta, Symfony2 imposta una variabile globale app, sia nei template Twig che in quelli PHP. La variabile app unistanza di Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables, che d accesso automaticamente ad alcune variabili speciche dellapplicazione: app.security - Il contesto della sicurezza. app.user - Loggetto dellutente attuale. app.request - Loggetto richiesta. app.session - Loggetto sessione. app.environment - Lambiente attuale (dev, prod, ecc). app.debug - True se in debug. False altrimenti. Twig
<p>Nome utente: {{ app.user.username }}</p> {% if app.debug %} <p>Metodo richiesta: {{ app.request.method }}</p> <p>Ambiente: {{ app.environment }}</p> {% endif %}

PHP
<p>Nome utente: <?php echo $app->getUser()->getUsername() ?></p> <?php if ($app->getDebug()): ?> <p>Metodo richiesta: <?php echo $app->getRequest()->getMethod() ?></p> <p>Ambiente: <?php echo $app->getEnvironment() ?></p> <?php endif; ?>

Suggerimento: Si possono aggiungere le proprie variabili globali ai template. Si veda la ricetta Variabili globali.

102

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Congurare e usare il servizio templating Il cuore del sistema dei template di Symfony2 il motore dei template. Loggetto speciale Engine responsabile della resa dei template e della restituzione del loro contenuto. Quando si rende un template in un controllore, per esempio, si sta in realt usando il servizio del motore dei template. Per esempio:
return $this->render(AcmeArticleBundle:Article:index.html.twig);

equivale a
$engine = $this->container->get(templating); $content = $engine->render(AcmeArticleBundle:Article:index.html.twig); return $response = new Response($content);

Il motore (o servizio) dei template pre-congurato per funzionare automaticamente dentro a Symfony2. Pu anche essere ulteriormente congurato nel le di congurazione dellapplicazione: YAML
# app/config/config.yml framework: # ... templating: { engines: [twig] }

XML
<!-- app/config/config.xml --> <framework:templating> <framework:engine id="twig" /> </framework:templating>

PHP
// app/config/config.php $container->loadFromExtension(framework, array( // ... templating => array( engines => array(twig), ), ));

Sono disponibili diverse opzioni di congurazione, coperte nellAppendice: congurazione. Nota: Il motore twig obbligatorio per poter usare il webproler (cos come molti altri bundle di terze parti).

Sovrascrivere template dei bundle La comunit di Symfony2 si vanta di creare e mantenere bundle di alta qualit (vedere KnpBundles.com (http://knpbundles.com)) per un gran numero di diverse caratteristiche. Quando si usa un bundle di terze parti, probabilmente occorrer sovrascrivere e personalizzare uno o pi dei suoi template. Supponiamo di aver incluso limmaginario bundle AcmeBlogBundle nel nostro progetto (p.e. nella cartella src/Acme/BlogBundle). Pur essendo soddisfatti, vogliamo sovrascrivere la pagina list del blog, per personalizzare il codice e renderlo specico per la nostra applicazione. Analizzando il controllore Blog di AcmeBlogBundle, troviamo:

2.1. Libro

103

Documentazione Symfony, Release 2.0

public function indexAction() { $blogs = // logica per recuperare i blog $this->render(AcmeBlogBundle:Blog:index.html.twig, array(blogs => $blogs)); }

Quando viene reso AcmeBlogBundle:Blog:index.html.twig, Symfony2 cerca il template in due diversi posti: 1. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig 2. src/Acme/BlogBundle/Resources/views/Blog/index.html.twig Per sovrascrivere il template del bundle, basta copiare il le index.html.twig dal bundle a app/Resources/AcmeBlogBundle/views/Blog/index.html.twig (la cartella app/Resources/AcmeBlogBundle non esiste ancora, quindi occorre crearla). Ora si pu personalizzare il template. Questa logica si applica anche ai template base dei bundle. Supponiamo che ogni template in AcmeBlogBundle erediti da un template base chiamato AcmeBlogBundle::layout.html.twig. Esattamente come prima, Symfony2 cercher il template i questi due posti: 1. app/Resources/AcmeBlogBundle/views/layout.html.twig 2. src/Acme/BlogBundle/Resources/views/layout.html.twig Anche qui, per sovrascrivere il template, basta copiarlo dal bundle app/Resources/AcmeBlogBundle/views/layout.html.twig. Ora lo si pu personalizzare. a

Facendo un passo indietro, si vedr che Symfony2 inizia sempre a cercare un template nella cartella app/Resources/{NOME_BUNDLE}/views/. Se il template non c, continua vericando nella cartella Resources/views del bundle stesso. Questo vuol dire che ogni template di bundle pu essere sovrascritto, inserendolo nella sotto-cartella app/Resources appropriata.
Sovrascrivere template del nucleo

Essendo il framework Symfony2 esso stesso un bundle, i template del nucleo possono essere sovrascritti allo stesso modo. Per esempio, TwigBundle contiene diversi template exception ed error, che possono essere sovrascritti, copiandoli dalla cartella Resources/views/Exception di TwigBundle a, come si pu immaginare, la cartella app/Resources/TwigBundle/views/Exception. Ereditariet a tre livelli Un modo comune per usare lereditariet lapproccio a tre livelli. Questo metodo funziona perfettamente con i tre diversi tipi di template di cui abbiamo appena parlato: Creare un le app/Resources/views/base.html.twig che contenga il layout principale per la propria applicazione (come nellesempio precedente). Internamente, questo template si chiama ::base.html.twig; Creare un template per ogni sezione del proprio sito. Per esempio, AcmeBlogBundle avrebbe un template di nome AcmeBlogBundle::layout.html.twig, che contiene solo elementi specici alla sezione blog;
{# src/Acme/BlogBundle/Resources/views/layout.html.twig #} {% extends ::base.html.twig %} {% block body %} <h1>Applicazione blog</h1>

104

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

{% block content %}{% endblock %} {% endblock %}

Creare i singoli template per ogni pagina, facendo estendere il template della sezione appropriata. Per esempio, la pagina index avrebbe un nome come AcmeBlogBundle:Blog:index.html.twig e mostrerebbe la lista dei post del blog.
{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} {% extends AcmeBlogBundle::layout.html.twig %} {% block content %} {% for entry in blog_entries %} <h2>{{ entry.title }}</h2> <p>{{ entry.body }}</p> {% endfor %} {% endblock %}

Si noti che questo template estende il template di sezione (AcmeBlogBundle::layout.html.twig), che a sua volte estende il layout base dellapplicazione (::base.html.twig). Questo il modello di ereditariet a tre livelli. Durante la costruzione della propria applicazione, si pu scegliere di seguire questo metodo oppure semplicemente far estendere direttamente a ogni template di pagina il template base dellapplicazione (p.e. {% extends ::base.html.twig %}). Il modello a tre template una best practice usata dai bundle dei venditori, in modo che il template base di un bundle possa essere facilmente sovrascritto per estendere correttamente il layout base della propria applicazione. Escape delloutput Quando si genera HTML da un template, c sempre il rischio che una variabile possa mostrare HTML indesiderato o codice pericoloso lato client. Il risultato che il contenuto dinamico potrebbe rompere il codice HTML della pagina risultante o consentire a un utente malintenzionato di eseguire un attacco Cross Site Scripting (http://it.wikipedia.org/wiki/Cross-site_scripting) (XSS). Consideriamo questo classico esempio: Twig
Ciao {{ name }}

PHP
Ciao <?php echo $name ?>

Si immagini che lutente inserisca nel suo nome il seguente codice:


<script>alert(ciao!)</script>

Senza alcun escape delloutput, il template risultante causerebbe la comparsa di una nestra di alert Javascript:
Ciao <script>alert(ciao!)</script>

Sebbene possa sembrare innocuo, se un utente arriva a tal punto, lo stesso utente sarebbe in grado di scrivere Javascript che esegua azioni dannose allinterno dellarea di un utente legittimo e ignaro. La risposta a questo problema lescape delloutput. Con lescape attivo, lo stesso template verrebbe reso in modo innocuo e scriverebbe alla lettera il tag script su schermo:
Ciao &lt;script&gt;alert(&#39;ciao!&#39;)&lt;/script&gt;

2.1. Libro

105

Documentazione Symfony, Release 2.0

Lapproccio dei sistemi di template Twig e PHP a questo problema sono diversi. Se si usa Twig, lescape attivo in modo predenito e si al sicuro. In PHP, lescape delloutput non automatico, il che vuol dire che occorre applicarlo a mano, dove necessario.
Escape delloutput in Twig

Se si usano i template Twig, lescape delloutput attivo in modo predenito. Questo vuol dire che si protetti dalle conseguenze non intenzionali del codice inviato dallutente. Per impostazione predenita, lescape delloutput assume che il contenuto sia sotto escape per loutput HTML. In alcuni casi, si avr bisogno di disabilitare lescape delloutput, quando si avr bisogno di rendere una variabile afdabile che contiene markup. Supponiamo che gli utenti amministratori siano abilitati a scrivere articoli che contengano codice HTML. Per impostazione predenita, Twig mostrer larticolo con escape. Per renderlo normalmente, aggiungere il ltro raw: {{ article.body | raw }}. Si pu anche disabilitare lescape delloutput dentro a un {% block %} o per un intero template. Per maggiori informazioni, vedere Escape delloutput (http://twig.sensiolabs.org) nella documentazione di Twig.
Escape delloutput in PHP

Lescape delloutput non automatico, se si usano i template PHP. Questo vuol dire che, a meno che non scelga esplicitamente di passare una variabile sotto escape, non si protetti. Per usare lescape, usare il metodo speciale escape():
Ciao <?php echo $view->escape($name) ?>

Per impostazione predenita, il metodo escape() assume che la variabile sia resa in un contesto HTML (quindi lescape render la variabile sicura per HTML). Il secondo parametro consente di cambiare contesto. Per esempio per mostrare qualcosa in una stringa Javascript, usare il contesto js:
var myMsg = Ciao <?php echo $view->escape($name, js) ?>;

Debug Nuovo nella versione 2.0.9: Questa caratteristica disponibile da Twig 1.5.x, che stato aggiunto in Symfony 2.0.9. Quando si usa PHP, si pu ricorrere a var_dump(), se occorre trovare rapidamente il valore di una variabile passata. Pu essere utile, per esempio, nel proprio controllore. Si pu ottenere lo stesso risultato con Twig, usando lestensione debug. Occorre abilitarla nella congurazione: YAML
# app/config/config.yml services: acme_hello.twig.extension.debug: class: Twig_Extension_Debug tags: - { name: twig.extension }

XML
<!-- app/config/config.xml --> <services> <service id="acme_hello.twig.extension.debug" class="Twig_Extension_Debug"> <tag name="twig.extension" />

106

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

</service> </services>

PHP
// app/config/config.php use Symfony\Component\DependencyInjection\Definition; $definition = new Definition(Twig_Extension_Debug); $definition->addTag(twig.extension); $container->setDefinition(acme_hello.twig.extension.debug, $definition);

Si pu quindi fare un dump dei parametri nei template, usando la funzione dump:
{# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} {{ dump(articles) }} {% for article in articles %} <a href="/article/{{ article.slug }}"> {{ article.title }} </a> {% endfor %}

Il dump delle variabili avverr solo se limpostazione debug (in config.yml) true. Questo vuol dire che, per impostazione predenita, il dump avverr in ambiente dev, ma non in prod. Formati di template I template sono un modo generico per rendere contenuti in qualsiasi formato. Pur usando nella maggior parte dei casi i template per rendere contenuti HTML, un template pu generare altrettanto facilmente Javascript, CSS, XML o qualsiasi altro formato desiderato. Per esempio, la stessa risorsa spesso resa in molti formati diversi. Per rendere una pagina in XML, basta includere il formato nel nome del template: nome del template XML: AcmeArticleBundle:Article:index.xml.twig nome del le del template XML: index.xml.twig In realt, questo non niente pi che una convenzione sui nomi e il template non effettivamente resto in modo diverso in base al suo formato. In molti casi, si potrebbe voler consentire a un singolo controllore di rendere formati diversi, in base al formato di richiesta. Per questa ragione, una soluzione comune fare come segue:
public function indexAction() { $format = $this->getRequest()->getRequestFormat(); return $this->render(AcmeBlogBundle:Blog:index..$format..twig); }

Il metodo getRequestFormat delloggetto Request ha come valore predenito html, ma pu restituire qualsiasi altro formato, in base al formato richiesto dallutente. Il formato di richiesta spesso gestito dalle rotte, quando una rotta congurata in modo che /contact imposti il formato di richiesta a html, mentre /contact.xml lo imposti a xml. Per maggiori informazioni, vedere Esempi avanzati nel capitolo delle rotte. Per creare collegamenti che includano il formato, usare la chiave _format come parametro:

2.1. Libro

107

Documentazione Symfony, Release 2.0

Twig
<a href="{{ path(article_show, {id: 123, _format: pdf}) }}"> versione PDF </a>

PHP

<a href="<?php echo $view[router]->generate(article_show, array(id => 123, _format => p versione PDF </a>

Considerazioni nali Il motore dei template in Symfony un potente strumento, che pu essere usato ogni volta che occorre generare contenuto relativo alla presentazione in HTML, XML o altri formati. Sebbene i template siano un modo comune per generare contenuti in un controllore, i loro utilizzo non obbligatorio. Loggetto Response restituito da un controllore pu essere creato con o senza luso di un template:
// crea un oggetto Response il cui contenuto il template reso $response = $this->render(AcmeArticleBundle:Article:index.html.twig); // crea un oggetto Response il cui contenuto semplice testo $response = new Response(contenuto della risposta);

Il motore dei template di Symfony molto essibile e mette a disposizione due sistemi di template: i tradizionali template PHP e i potenti e rafnati template Twig. Entrambi supportano una gerarchia di template e sono distribuiti con un ricco insieme di funzioni helper, capaci di eseguire i compiti pi comuni. Complessivamente, largomento template dovrebbe essere considerato come un potente strumento a disposizione. In alcuni casi, si potrebbe non aver bisogno di rendere un template , in Symfony2, questo non assolutamente un problema. Imparare di pi con il ricettario Come usare PHP al posto di Twig nei template Come personalizzare le pagine di errore

2.1.8 Database e Doctrine (Il modello)


Ammettiamolo, uno dei compiti pi comuni e impegnativi per qualsiasi applicazione implica la persistenza e la lettura di informazioni da un database. Fortunatamente, Symfony integrato con Doctrine (http://www.doctrine-project.org/), una libreria il cui unico scopo quello di fornire potenti strumenti per facilitare tali compiti. In questo capitolo, si imparer la losoa alla base di Doctrine e si vedr quanto possa essere facile lavorare con un database. Nota: Doctrine totalmente disaccoppiato da Symfony e il suo utilizzo facoltativo. Questo capitolo tutto su Doctrine, che si pregge lo scopo di consentire una mappatura tra oggetti un database relazionale (come MySQL, PostgreSQL o Microsoft SQL). Se si preferisce luso di query grezze, lo si pu fare facilmente, come spiegato nella ricetta Come usare il livello DBAL di Doctrine. Si possono anche persistere dati su MongoDB (http://www.mongodb.org/) usando la libreria ORM Doctrine. Per ulteriori informazioni, leggere la documentazione DoctrineMongoDBBundle.

108

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Un semplice esempio: un prodotto Il modo pi facile per capire come funziona Doctrine quello di vederlo in azione. In questa sezione, congureremo un database, creeremo un oggetto Product, lo persisteremo nel database e lo recupereremo da esso. Codice con lesempio Se si vuole seguire lesempio in questo capitolo, creare un bundle AcmeStoreBundle tramite:
php app/console generate:bundle --namespace=Acme/StoreBundle

Congurazione del database

Prima di iniziare, occorre congurare le informazioni sulla connessione al database. Per convenzione, questa informazione solitamente congurata in un le app/config/parameters.yml:
# app/config/parameters.yml parameters: database_driver: pdo_mysql database_host: localhost database_name: test_project database_user: root database_password: password

Nota: La denizione della congurazione tramite parameters.yml solo una convenzione. I parametri deniti in tale le sono riferiti dal le di congurazione principale durante le impostazioni iniziali di Doctrine:
doctrine: dbal: driver: host: dbname: user: password:

%database_driver% %database_host% %database_name% %database_user% %database_password%

Separando le informazioni sul database in un le a parte, si possono mantenere facilmente diverse versioni del le su ogni server. Si possono anche facilmente memorizzare congurazioni di database (o altre informazioni sensibili) fuori dal proprio progetto, come per esempio dentro la congurazione di Apache. Per ulteriori informazioni, vedere Congurare parametri esterni nel contenitore dei servizi. Ora che Doctrine ha informazioni sul proprio database, si pu fare in modo che crei il database al posto nostro:
php app/console doctrine:database:create

Creare una classe entit

Supponiamo di star costruendo unapplicazione in cui i prodotti devono essere mostrati. Senza nemmeno pensare a Doctrine o ai database, gi sappiamo di aver bisogno di un oggetto Product che rappresenti questi prodotti. Creare questa classe dentro la cartella Entity del proprio AcmeStoreBundle:
// src/Acme/StoreBundle/Entity/Product.php namespace Acme\StoreBundle\Entity;

2.1. Libro

109

Documentazione Symfony, Release 2.0

class Product { protected $name; protected $price; protected $description; }

La classe, spesso chiamata entit (che vuol dire una classe di base che contiene dati), semplice e aiuta a soddisfare i requisiti di business di necessit di prodotti della propria applicazione. Questa classe non pu ancora essere persistita in un database, solo una semplice classe PHP. Suggerimento: Una volta imparati i concetti dietro a Doctrine, si pu fare in modo che Doctrine crei questa classe entit al posto nostro:

php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Product" --fields="name:string(255

Aggiungere informazioni di mappatura

Doctrine consente di lavorare con i database in un modo molto pi interessante rispetto al semplice recupero di righe da tabelle basate su colonne in un array. Invece, Doctrine consente di persistere interi oggetti sul database e di recuperare interi oggetti dal database. Funziona mappando una classe PHP su una tabella di database e le propriet della classe PHP sulle colonne della tabella:

Per fare in modo che Doctrine possa fare ci, occorre solo creare dei meta-dati, ovvero la congurazione che dice esattamente a Doctrine come la classe Product e le sue propriet debbano essere mappate sul database. Questi meta-dati possono essere specicati in diversi formati, inclusi YAML, XML o direttamente dentro la classe Product, tramite annotazioni: Nota: Un bundle pu accettare un solo formato di denizione dei meta-dati. Per esempio, non possibile mischiare denizioni di meta-dati in YAML con denizioni tramite annotazioni. Annotations
// src/Acme/StoreBundle/Entity/Product.php namespace Acme\StoreBundle\Entity; use Doctrine\ORM\Mapping as ORM;

110

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

/** * @ORM\Entity * @ORM\Table(name="product") */ class Product { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @ORM\Column(type="string", length=100) */ protected $name; /** * @ORM\Column(type="decimal", scale=2) */ protected $price; /** * @ORM\Column(type="text") */ protected $description; }

YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml Acme\StoreBundle\Entity\Product: type: entity table: product id: id: type: integer generator: { strategy: AUTO } fields: name: type: string length: 100 price: type: decimal scale: 2 description: type: text

XML
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml --> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> <entity name="Acme\StoreBundle\Entity\Product" table="product">

2.1. Libro

111

Documentazione Symfony, Release 2.0

<id name="id" type="integer" column="id"> <generator strategy="AUTO" /> </id> <field name="name" column="name" type="string" length="100" /> <field name="price" column="price" type="decimal" scale="2" /> <field name="description" column="description" type="text" /> </entity> </doctrine-mapping>

Suggerimento: Il nome della tabella facoltativo e, se omesso, sar determinato automaticamente in base al nome della classe entit. Doctrine consente di scegliere tra una grande variet di tipi di campo, ognuno con le sue opzioni Per informazioni sui tipi disponibili, vedere la sezione Riferimento sui tipi di campo di Doctrine. Vedi anche: Si pu anche consultare la Documentazione di base sulla mappatura (http://docs.doctrineproject.org/orm/en/2.1/reference/basic-mapping.html) di Doctrine per tutti i dettagli sulla mappatura. Se si usano le annotazioni, occorrer aggiungere a ogni annotazione il presso ORM\ (p.e. ORM\Column(..)), che non mostrato nella documentazione di Doctrine. Occorrer anche includere listruzione use Doctrine\ORM\Mapping as ORM;, che importa il presso ORM delle annotazioni. Attenzione: Si faccia attenzione che il nome della classe e delle propriet scelti non siano mappati a delle parole riservate di SQL (come group o user). Per esempio, se il proprio nome di classe entit Group, allora il nome predenito della tabella sar group, che causer un errore SQL in alcuni sistemi di database. Vedere la Documentazione sulle parole riservate di SQL (http://docs.doctrine-project.org/projects/doctrineorm/en/2.1/reference/basic-mapping.html#quoting-reserved-words) per sapere come fare correttamente escape di tali nomi.

Nota: Se si usa unaltra libreria o programma che utilizza le annotazioni (come Doxygen), si dovrebbe inserire lannotazione @IgnoreAnnotation nella classe, per indicare a Symfony quali annotazioni ignorare. Per esempio, per evitare che lannotazione @fn sollevi uneccezione, aggiungere il seguente:
/** * @IgnoreAnnotation("fn") */ class Product

Generare getter e setter

Sebbene ora Doctrine sappia come persistere un oggetto Product nel database, la classe stessa non molto utile. Poich Product solo una normale classe PHP, occorre creare dei metodi getter e setter (p.e. getName(), setName()) per poter accedere alle sue propriet (essendo le propriet protette). Fortunatamente, Doctrine pu farlo al posto nostro, basta eseguire:
php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product

Il comando si assicura che i getter e i setter siano generati per la classe Product. un comando sicuro, lo si pu eseguire diverse volte: generer i getter e i setter solamente se non esistono (ovvero non sostituir eventuali metodi gi presenti).

112

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Di pi su doctrine:generate:entities Con il comando doctrine:generate:entities si pu: generare getter e setter, generare classi repository congurate con lannotazione @ORM\Entity(repositoryClass=...), generare il costruttore appropriato per relazioni 1:n e n:m. Il comando doctrine:generate:entities salva una copia di backup del le originale Product.php, chiamata Product.php~. In alcuni casi, la presenza di questo le pu causare un errore Cannot redeclare class. Il le pu essere rimosso senza problemi. Si noti che non necessario usare questo comando. Doctrine non si appoggia alla generazione di codice. Come con le normali classi PHP, occorre solo assicurarsi che le propriet protected/private abbiano metodi getter e setter. Questo comando stato creato perch una cosa comune da fare quando si usa Doctrine. Si possono anche generare tutte le entit note (cio ogni classe PHP con informazioni di mappatura di Doctrine) di un bundle o di un intero spazio dei nomi:
php app/console doctrine:generate:entities AcmeStoreBundle php app/console doctrine:generate:entities Acme

Nota: Doctrine non si cura se le propriet sono protected o private, o se siano o meno presenti getter o setter per una propriet. I getter e i setter sono generati qui solo perch necessari per interagire col proprio oggetto PHP.

Creare tabelle e schema del database

Ora si ha una classe Product usabile, con informazioni di mappatura che consentono a Doctrine di sapere esattamente come persisterla. Ovviamente, non si ha ancora la corrispondente tabella product nel proprio database. Fortunatamente, Doctrine pu creare automaticamente tutte le tabelle del database necessarie a ogni entit nota nella propria applicazione. Per farlo, eseguire:
php app/console doctrine:schema:update --force

Suggerimento: Questo comando incredibilmente potente. Confronta ci che il proprio database dovrebbe essere (basandosi sulle informazioni di mappatura delle entit) con ci che effettivamente , quindi genera le istruzioni SQL necessarie per aggiornare il database e portarlo a ci che dovrebbe essere. In altre parole, se si aggiunge una nuova propriet con meta-dati di mappatura a Product e si esegue nuovamente il task, esso generer listruzione alter table necessaria per aggiungere questa nuova colonna alla tabella product esistente. Un modo ancora migliore per trarre vantaggio da questa funzionalit tramite le migrazioni, che consentono di generare queste istruzioni SQL e di memorizzarle in classi di migrazione, che possono essere eseguite sistematicamente sul proprio server di produzione, per poter tracciare e migrare il proprio schema di database in modo sicuro e afdabile. Il proprio database ora ha una tabella product pienamente funzionante, con le colonne corrispondenti ai meta-dati specicati.
Persistere gli oggetti nel database

Ora che lentit Product stata mappata alla corrispondente tabella product, si pronti per persistere i dati nel database. Da dentro un controllore, molto facile. Aggiungere il seguente metodo a DefaultController del bundle:

2.1. Libro

113

Documentazione Symfony, Release 2.0

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

// src/Acme/StoreBundle/Controller/DefaultController.php use Acme\StoreBundle\Entity\Product; use Symfony\Component\HttpFoundation\Response; // ... public function createAction() { $product = new Product(); $product->setName(Pippo Pluto); $product->setPrice(19.99); $product->setDescription(Lorem ipsum dolor); $em = $this->getDoctrine()->getEntityManager(); $em->persist($product); $em->flush(); return new Response(Creato prodotto con id .$product->getId()); }

Nota: Se si sta seguendo questo esempio, occorrer creare una rotta che punti a questa azione, per poterla vedere in azione. Analizziamo questo esempio: righe 8-11 In questa sezione, si istanzia e si lavora con loggetto $product, come qualsiasi altro normale oggetto PHP; riga 13 Questa riga recupera loggetto gestore di entit di Doctrine, responsabile della gestione del processo di persistenza e del recupero di oggetti dal database; riga 14 Il metodo persist() dice a Doctrine di gestire loggetto $product. Questo non fa (ancora) eseguire una query sul database. riga 15 Quando il metodo flush() richiamato, Doctrine cerca tutti gli oggetti che sta gestendo, per vedere se hanno bisogno di essere persistiti sul database. In questo esempio, loggetto $product non stato ancora persistito, quindi il gestore di entit esegue una query INSERT e crea una riga nella tabella product. Nota: Di fatto, essendo Doctrine consapevole di tutte le proprie entit gestite, quando si chiama il metodo flush(), esso calcola un insieme globale di modiche ed esegue le query pi efcienti possibili. Per esempio, se si persiste un totale di 100 oggetti Product e quindi si richiama flush(), Doctrine creer una singola istruzione e la riuser per ogni inserimento. Questo pattern si chiama Unit of Work ed utilizzato in virt della sua velocit ed efcienza. Quando si creano o aggiornano oggetti, il usso sempre lo stesso. Nella prossima sezione, si vedr come Doctrine sia abbastanza intelligente da usare una query UPDATE se il record gi esistente nel database. Suggerimento: Doctrine fornisce una libreria che consente di caricare dati di test nel proprio progetto (le cosiddette xture). Per informazioni, vedere DoctrineFixturesBundle.

Recuperare oggetti dal database

Recuperare un oggetto dal database ancora pi facile. Per esempio, supponiamo di aver congurato una rotta per mostrare uno specico Product, in base al valore del suo id:

114

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

public function showAction($id) { $product = $this->getDoctrine() ->getRepository(AcmeStoreBundle:Product) ->find($id); if (!$product) { throw $this->createNotFoundException(Nessun prodotto trovato per l\id .$id); } // fare qualcosa, come passare loggetto $product a un template }

Quando si cerca un particolare tipo di oggetto, si usa sempre quello che noto come il suo repository. Si pu pensare a un repository come a una classe PHP il cui unico compito quello di aiutare nel recuperare entit di una certa classe. Si pu accedere alloggetto repository per una classe entit tramite:
$repository = $this->getDoctrine() ->getRepository(AcmeStoreBundle:Product);

Nota: La stringa AcmeStoreBundle:Product una scorciatoia utilizzabile ovunque in Doctrine al posto del nome intero della classe dellentit (cio Acme\StoreBundle\Entity\Product). Questo funzioner nch le proprie entit rimarranno sotto lo spazio dei nomi Entity del proprio bundle. Una volta ottenuto il proprio repository, si avr accesso a tanti metodi utili:
// cerca per chiave primaria (di solito "id") $product = $repository->find($id); // nomi di metodi dinamici per cercare in base al valore di una colonna $product = $repository->findOneById($id); $product = $repository->findOneByName(pippo); // trova *tutti* i prodotti $products = $repository->findAll(); // trova un gruppo di prodotti in base a un valore arbitrario di una colonna $products = $repository->findByPrice(19.99);

Nota: Si possono ovviamente fare anche query complesse, su cui si pu avere maggiori informazioni nella sezione Cercare gli oggetti. Si possono anche usare gli utili metodi findBy e findOneBy per recuperare facilmente oggetti in base a condizioni multiple:
// cerca un prodotto in base a nome e prezzo $product = $repository->findOneBy(array(name => pippo, price => 19.99)); // cerca tutti i prodotti in base al nome, ordinati per prezzo $product = $repository->findBy( array(name => pippo), array(price => ASC) );

2.1. Libro

115

Documentazione Symfony, Release 2.0

Suggerimento: Quando si rende una pagina, si pu vedere il numero di query eseguite nellangolo inferiore destro della barra di debug del web.

Cliccando sullicona, si aprir il proler, che mostrer il numero esatto di query eseguite.

Aggiornare un oggetto

Una volta che Doctrine ha recuperato un oggetto, il suo aggiornamento facile. Supponiamo di avere una rotta che mappi un id di prodotto a unazione di aggiornamento in un controllore:
public function updateAction($id) { $em = $this->getDoctrine()->getEntityManager(); $product = $em->getRepository(AcmeStoreBundle:Product)->find($id); if (!$product) { throw $this->createNotFoundException(Nessun prodotto trovato per l\id .$id); } $product->setName(Nome del nuovo prodotto!); $em->flush(); return $this->redirect($this->generateUrl(homepage)); }

Laggiornamento di un oggetto si svolge in tre passi: 1. recuperare loggetto da Doctrine; 2. modicare loggetto; 3. richiamare flush() sul gestore di entit Si noti che non necessario richiamare $em->persist($product). Ricordiamo che questo metodo dice semplicemente a Doctrine di gestire o osservare loggetto $product. In questo caso, poich loggetto $product stato recuperato da Doctrine, gi gestito.
Cancellare un oggetto

La cancellazione di un oggetto molto simile, ma richiede una chiamata al metodo remove() del gestore delle entit:

116

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

$em->remove($product); $em->flush();

Come ci si potrebbe aspettare, il metodo remove() rende noto a Doctrine che si vorrebbe rimuovere la data entit dal database. Tuttavia, la query DELETE non viene realmente eseguita nch non si richiama il metodo flush(). Cercare gli oggetti Abbiamo gi visto come loggetto repository consenta di eseguire query di base senza alcuno sforzo:
$repository->find($id); $repository->findOneByName(Pippo);

Ovviamente, Doctrine consente anche di scrivere query pi complesse, usando Doctrine Query Language (DQL). DQL simile a SQL, tranne per il fatto che bisognerebbe immaginare di stare cercando uno o pi oggetti di una classe entit (p.e. Product) e non le righe di una tabella (p.e. product). Durante una ricerca in Doctrine, si hanno due opzioni: scrivere direttamente query Doctrine, oppure usare il Query Builder di Doctrine.
Cercare oggetti con DQL

Si immagini di voler cercare dei prodotti, ma solo quelli che costino pi di 19.99, ordinati dal pi economico al pi caro. Da dentro un controllore, fare come segue:
$em = $this->getDoctrine()->getEntityManager(); $query = $em->createQuery( SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC )->setParameter(price, 19.99); $products = $query->getResult();

Se ci si trova a proprio agio con SQL, DQL dovrebbe sembrare molto naturale. La maggiore differenze che occorre pensare in termini di oggetti invece che di righe di database. Per questa ragione, si cerca da AcmeStoreBundle:Product e poi si usa p come suo alias. Il metodo getResult() restituisce un array di risultati. Se si cerca un solo oggetto, si pu usare invece il metodo getSingleResult():
$product = $query->getSingleResult();

2.1. Libro

117

Documentazione Symfony, Release 2.0

Attenzione: Il metodo getSingleResult() solleva uneccezione Doctrine\ORM\NoResultException se non ci sono risultati e una Doctrine\ORM\NonUniqueResultException se c pi di un risultato. Se si usa questo metodo, si potrebbe voler inserirlo in un blocco try-catch, per assicurarsi che sia restituito un solo risultato (nel caso in cui sia possibile che siano restituiti pi risultati):
$query = $em->createQuery(SELECT ....) ->setMaxResults(1); try { $product = $query->getSingleResult(); } catch (\Doctrine\Orm\NoResultException $e) { $product = null; } // ...

La sintassi DQL incredibilmente potente e consente di fare join tra entit (largomento relazioni sar affrontato successivamente), raggruppare, ecc. Per maggiori informazioni, vedere la documentazione ufciale di Doctrine Doctrine Query Language (http://docs.doctrine-project.org/orm/en/2.1/reference/dql-doctrine-query-language.html). Impostare i parametri Si prenda nota del metodo setParameter(). Lavorando con Doctrine, sempre una buona idea impostare ogni valore esterno come segnaposto, come stato fatto nella query precedente:
... WHERE p.price > :price ...

Si pu quindi impostare il valore del segnaposto price, richiamando il metodo setParameter():


->setParameter(price, 19.99)

Luso di parametri al posto dei valori diretti nella stringa della query serve a prevenire attacchi di tipo SQL injection e andrebbe fatto sempre. Se si usano pi parametri, si possono impostare i loro valori in una volta sola, usando il metodo setParameters():
->setParameters(array( price => 19.99, name => Pippo, ))

Usare query builder di Doctrine

Invece di scrivere direttamente le query, si pu invece usare QueryBuilder, per fare lo stesso lavoro usando uninterfaccia elegante e orientata agli oggetti. Se si usa un IDE, si pu anche trarre vantaggio dallauto-completamento durante la scrittura dei nomi dei metodi. Da dentro un controllore:
$repository = $this->getDoctrine() ->getRepository(AcmeStoreBundle:Product); $query = $repository->createQueryBuilder(p) ->where(p.price > :price) ->setParameter(price, 19.99) ->orderBy(p.price, ASC) ->getQuery();

118

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

$products = $query->getResult();

Loggetto QueryBuilder contiene tutti i metodi necessari per costruire la propria query. Richiamando il metodo getQuery(), query builder restituisce un normale oggetto Query, che lo stesso oggetto costruito direttamente nella sezione precedente. Per maggiori informazioni su query builder, consultare la documentazione di Doctrine Query Builder (http://docs.doctrine-project.org/orm/en/2.1/reference/query-builder.html).
Classi repository personalizzate

Nelle sezioni precedenti, si iniziato costruendo e usando query pi complesse da dentro un controllore. Per isolare, testare e riusare queste query, una buona idea creare una classe repository personalizzata per la propria entit e aggiungere metodi, come la propria logica di query, al suo interno. Per farlo, aggiungere il nome della classe del repository alla propria denizione di mappatura. Annotations
// src/Acme/StoreBundle/Entity/Product.php namespace Acme\StoreBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass="Acme\StoreBundle\Repository\ProductRepository") */ class Product { //... }

YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml Acme\StoreBundle\Entity\Product: type: entity repositoryClass: Acme\StoreBundle\Repository\ProductRepository # ...

XML
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml --> <!-- ... --> <doctrine-mapping> <entity name="Acme\StoreBundle\Entity\Product" repository-class="Acme\StoreBundle\Repository\ProductRepository"> <!-- ... --> </entity> </doctrine-mapping>

Doctrine pu generare la classe repository per noi, eseguendo lo stesso comando usato precedentemente per generare i metodi getter e setter mancanti:
php app/console doctrine:generate:entities Acme

Quindi, aggiungere un nuovo metodo, chiamato findAllOrderedByName(), alla classe repository appena generata. Questo metodo cercher tutte le entit Product, ordinate alfabeticamente. 2.1. Libro 119

Documentazione Symfony, Release 2.0

// src/Acme/StoreBundle/Repository/ProductRepository.php namespace Acme\StoreBundle\Repository; use Doctrine\ORM\EntityRepository; class ProductRepository extends EntityRepository { public function findAllOrderedByName() { return $this->getEntityManager() ->createQuery(SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC) ->getResult(); } }

Suggerimento: repository.

Si pu accedere al gestore di entit tramite $this->getEntityManager() da dentro il

Si pu usare il metodo appena creato proprio come i metodi predeniti del repository:
$em = $this->getDoctrine()->getEntityManager(); $products = $em->getRepository(AcmeStoreBundle:Product) ->findAllOrderedByName();

Nota: Quando si usa una classe repository personalizzata, si ha ancora accesso ai metodi predeniti di ricerca, come find() e findAll().

Relazioni e associazioni tra entit Supponiamo che i prodotti nella propria applicazione appartengano tutti a una categoria. In questo caso, occorrer un oggetto Category e un modo per per mettere in relazione un oggetto Product con un oggetto Category. Iniziamo creando lentit Category. Sapendo che probabilmente occorrer persistere la classe tramite Doctrine, lasciamo che sia Doctrine stesso a creare la classe.

php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Category" --fields="name:string(25

Questo task genera lentit Category, con un campo id, un campo name e le relative funzioni getter e setter.
Meta-dati di mappatura delle relazioni

Per correlare le entit Category e Product, iniziamo creando una propriet products nella classe Category: Annotations
// src/Acme/StoreBundle/Entity/Category.php // ... use Doctrine\Common\Collections\ArrayCollection; class Category { // ... /**

120

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

* @ORM\OneToMany(targetEntity="Product", mappedBy="category") */ protected $products; public function __construct() { $this->products = new ArrayCollection(); } }

YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Category.orm.yml Acme\StoreBundle\Entity\Category: type: entity # ... oneToMany: products: targetEntity: Product mappedBy: category # non dimenticare di inizializzare la collection nel metodo __construct() dellentit

Primo, poich un oggetto Category sar collegato a diversi oggetti Product, va aggiunta una propriet array products, per contenere questi oggetti Product. Di nuovo, non va fatto perch Doctrine ne abbia bisogno, ma perch ha senso nellapplicazione che ogni Category contenga un array di oggetti Product. Nota: Il codice nel metodo __construct() importante, perch Doctrine esige che la propriet $products sia un oggetto ArrayCollection. Questo oggetto sembra e si comporta quasi esattamente come un array, ma ha un po di essibilit in pi. Se non sembra confortevole, niente paura. Si immagini solamente che sia un array.

Suggerimento: Il valore targetEntity, usato in precedenza sul decoratore, pu riferirsi a qualsiasi entit con uno spazio dei nomi valido, non solo a entit denite nella stessa classe. Per riferirsi a entit denite in classi diverse, inserire uno spazio dei nomi completo come targetEntity. Poi, poich ogni classe Product pu essere in relazione esattamente con un oggetto Category, si deve aggiungere una propriet $category alla classe Product: Annotations
// src/Acme/StoreBundle/Entity/Product.php // ... class Product { // ... /** * @ORM\ManyToOne(targetEntity="Category", inversedBy="products") * @ORM\JoinColumn(name="category_id", referencedColumnName="id") */ protected $category; }

YAML

2.1. Libro

121

Documentazione Symfony, Release 2.0

# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml Acme\StoreBundle\Entity\Product: type: entity # ... manyToOne: category: targetEntity: Category inversedBy: products joinColumn: name: category_id referencedColumnName: id

Inne, dopo aver aggiunto una nuova propriet sia alla classe Category che a quella Product, dire a Doctrine di generare i metodi mancanti getter e setter:
php app/console doctrine:generate:entities Acme

Ignoriamo per un momento i meta-dati di Doctrine. Abbiamo ora due classi, Category e Product, con una relazione naturale uno-a-molti. La classe Category contiene un array di oggetti Product e loggetto Product pu contenere un oggetto Category. In altre parole, la classe stata costruita in un modo che ha senso per le proprie necessit. Il fatto che i dati necessitino di essere persistiti su un database sempre secondario. Diamo ora uno sguardo ai meta-dati nella propriet $category della classe Product. Qui le informazioni dicono a Doctrine che la classe correlata Category e che dovrebbe memorizzare il valore id della categoria in un campo category_id della tabella product. In altre parole, loggetto Category correlato sar memorizzato nella propriet $category, ma dietro le quinte Doctrine persister questa relazione memorizzando il valore dellid della categoria in una colonna category_id della tabella product.

122

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

I meta-dati della propriet $products delloggetto Category meno importante e dicono semplicemente a Doctrine di cercare la propriet Product.category per sapere come mappare la relazione. Prima di continuare, accertarsi di dire a Doctrine di aggiungere la nuova tabella category la nuova colonna product.category_id e la nuova chiave esterna:
php app/console doctrine:schema:update --force

Nota: Questo task andrebbe usato solo durante lo sviluppo. Per un metodo pi robusto di aggiornamento sistematico del proprio database di produzione, leggere Migrazioni doctrine.

Salvare le entit correlate

Vediamo ora il codice in azione. Immaginiamo di essere dentro un controllore:

2.1. Libro

123

Documentazione Symfony, Release 2.0

// ... use Acme\StoreBundle\Entity\Category; use Acme\StoreBundle\Entity\Product; use Symfony\Component\HttpFoundation\Response; // ... class DefaultController extends Controller { public function createProductAction() { $category = new Category(); $category->setName(Prodotti principali); $product = new Product(); $product->setName(Pippo); $product->setPrice(19.99); // correlare questo prodotto alla categoria $product->setCategory($category); $em = $this->getDoctrine()->getEntityManager(); $em->persist($category); $em->persist($product); $em->flush(); return new Response( Creati prodotto con id: .$product->getId(). e categoria con id: .$category->getId() ); } }

Una riga stata aggiunta alle tabelle category e product. La colonna product.category_id del nuovo prodotto impostata allo stesso valore di id della nuova categoria. Doctrine gestisce la persistenza di tale relazione per noi.
Recuperare gli oggetti correlati

Quando occorre recuperare gli oggetti correlati, il usso del tutto simile a quello precedente. Recuperare prima un oggetto $product e poi accedere alla sua Category correlata:
public function showAction($id) { $product = $this->getDoctrine() ->getRepository(AcmeStoreBundle:Product) ->find($id); $categoryName = $product->getCategory()->getName(); // ... }

In questo esempio, prima di cerca un oggetto Product in base al suo id. Questo implica una query solo per i dati del prodotto e idrata loggetto $product con tali dati. Poi, quando si richiama $product->getCategory()->getName(), Doctrine effettua una seconda query, per trovare la Category correlata con il Product. Prepara loggetto $category e lo restituisce.

124

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Quello che importante il fatto che si ha facile accesso al prodotto correlato con la categoria, ma i dati della categoria non sono recuperati nch la categoria non viene richiesta (processo noto come lazy load). Si pu anche cercare nella direzione opposta:
public function showProductAction($id) { $category = $this->getDoctrine() ->getRepository(AcmeStoreBundle:Category) ->find($id); $products = $category->getProducts(); // ... }

In questo caso succedono le stesse cose: prima si cerca un singolo oggetto Category, poi Doctrine esegue una seconda query per recuperare loggetto Product correlato, ma solo quando/se richiesto (cio al richiamo di ->getProducts()). La variabile $products un array di tutti gli oggetti Product correlati con il dato oggetto Category tramite il loro valore category_id.

2.1. Libro

125

Documentazione Symfony, Release 2.0

Relazioni e classi proxy Questo lazy load possibile perch, quando necessario, Doctrine restituisce un oggetto proxy al posto del vero oggetto. Guardiamo di nuovo lesempio precedente:
$product = $this->getDoctrine() ->getRepository(AcmeStoreBundle:Product) ->find($id); $category = $product->getCategory(); // mostra "Proxies\AcmeStoreBundleEntityCategoryProxy" echo get_class($category);

Questo oggetto proxy estende il vero oggetto Category e sembra e si comporta esattamente nello stesso modo. La differenza che, usando un oggetto proxy, Doctrine pu rimandare la query per i dati effettivi di Category no a che non sia effettivamente necessario (cio no alla chiamata di $category->getName()). Le classy proxy sono generate da Doctrine e memorizzate in cache. Sebbene probabilmente non si noter mai che il proprio oggetto $category sia in realt un oggetto proxy, importante tenerlo a mente. Nella prossima sezione, quando si recuperano i dati di prodotto e categoria in una volta sola (tramite una join), Doctrine restituir il vero oggetto Category, poich non serve alcun lazy load.

Join di record correlati

Negli esempi precedenti, sono state eseguite due query: una per loggetto originale (p.e. una Category) e una per gli oggetti correlati (p.e. gli oggetti Product). Suggerimento: Si ricordi che possibile vedere tutte le query eseguite durante una richiesta, tramite la barra di web debug. Ovviamente, se si sa in anticipo di aver bisogno di accedere a entrambi gli oggetti, si pu evitare la seconda query, usando una join nella query originale. Aggiungere il seguente metodo alla classe ProductRepository:
// src/Acme/StoreBundle/Repository/ProductRepository.php public function findOneByIdJoinedToCategory($id) { $query = $this->getEntityManager() ->createQuery( SELECT p, c FROM AcmeStoreBundle:Product p JOIN p.category c WHERE p.id = :id )->setParameter(id, $id); try { return $query->getSingleResult(); } catch (\Doctrine\ORM\NoResultException $e) { return null; } }

Ora si pu usare questo metodo nel proprio controllore per cercare un oggetto Product e la relativa Category con una sola query:
public function showAction($id) {

126

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

$product = $this->getDoctrine() ->getRepository(AcmeStoreBundle:Product) ->findOneByIdJoinedToCategory($id); $category = $product->getCategory(); // ... }

Ulteriori informazioni sulle associazioni

Questa sezione stata unintroduzione a un tipo comune di relazione tra entit, la relazione uno-a-molti. Per dettagli ed esempi pi avanzati su come usare altri tipi di relazioni (p.e. uno-a-uno, molti-a-molti), vedere la Documentazione sulla mappatura delle associazioni (http://docs.doctrine-project.org/orm/en/2.1/reference/association-mapping.html). Nota: Se si usano le annotazioni, occorrer aggiungere a tutte le annotazioni il presso ORM\ (p.e. ORM\OneToMany), che non si trova nella documentazione di Doctrine. Occorrer anche includere listruzione use Doctrine\ORM\Mapping as ORM;, che importa il presso delle annotazioni ORM.

Congurazione Doctrine altamente congurabile, sebbene probabilmente non si avr nemmeno bisogno di preoccuparsi di gran parte delle sue opzioni. Per saperne di pi sulla congurazione di Doctrine, vedere la sezione Doctrine del manuale di riferimento. Callback del ciclo di vita A volte, occorre eseguire unazione subito prima o subito dopo che un entit sia inserita, aggiornata o cancellata. Questi tipi di azioni sono noti come callback del ciclo di vita, perch sono metodi callback che occorre eseguire durante i diversi stadi del ciclo di vita di unentit (p.e. lentit inserita, aggiornata, cancellata, eccetera). Se si usano le annotazioni per i meta-dati, iniziare abilitando i callback del ciclo di vita. Questo non necessario se si usa YAML o XML per la mappatura:
/** * @ORM\Entity() * @ORM\HasLifecycleCallbacks() */ class Product { // ... }

Si pu ora dire a Doctrine di eseguire un metodo su uno degli eventi disponibili del ciclo di vita. Per esempio, supponiamo di voler impostare una colonna di data created alla data attuale, solo quando lentit persistita la prima volta (cio inserita): Annotations
/** * @ORM\prePersist */ public function setCreatedValue()

2.1. Libro

127

Documentazione Symfony, Release 2.0

{ $this->created = new \DateTime(); }

YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml Acme\StoreBundle\Entity\Product: type: entity # ... lifecycleCallbacks: prePersist: [ setCreatedValue ]

XML
<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.xml --> <!-- ... --> <doctrine-mapping> <entity name="Acme\StoreBundle\Entity\Product"> <!-- ... --> <lifecycle-callbacks> <lifecycle-callback type="prePersist" method="setCreatedValue" /> </lifecycle-callbacks> </entity> </doctrine-mapping>

Nota: Lesempio precedente presume che sia stata creata e mappata una propriet created (non mostrata qui). Ora, appena prima che lentit sia persistita per la prima volta, Doctrine richiamer automaticamente questo metodo e il campo created sar valorizzato con la data attuale. Si pu ripetere questa operazione per ogni altro evento del ciclo di vita: preRemove postRemove prePersist postPersist preUpdate postUpdate postLoad loadClassMetadata Per maggiori informazioni sul signicato di questi eventi del ciclo di vita e in generale sui callback del ciclo di vita, vedere la Documentazione sugli eventi del ciclo di vita (http://docs.doctrineproject.org/orm/en/2.1/reference/events.html#lifecycle-events)

128

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Callback del ciclo di vita e ascoltatori di eventi Si noti che il metodo setCreatedValue() non riceve parametri. Questo sempre il caso di callback del ciclo di vita ed intenzionale: i callback del ciclo di vita dovrebbero essere metodi semplici, riguardanti la trasformazione interna di dati nellentit (p.e. impostare un campo di creazione/aggiornamento, generare un valore per uno slug). Se occorre un lavoro pi pesante, come eseguire un log o inviare una email, si dovrebbe registrare una classe esterna come ascoltatore di eventi e darle accesso a qualsiasi risorsa necessaria. Per maggiori informazioni, vedere Registrare ascoltatori e sottoscrittori di eventi.

Estensioni di Doctrine: Timestampable, Sluggable, ecc. Doctrine alquanto essibile e diverse estensioni di terze parti sono disponibili, consentendo di eseguire facilmente compiti comuni e ripetitivi sulle proprie entit. Sono inclusi Sluggable, Timestampable, Loggable, Translatable e Tree. Per maggiori informazioni su come trovare e usare tali estensioni, vedere la ricetta usare le estensioni comuni di Doctrine. Riferimento sui tipi di campo di Doctrine Doctrine ha un gran numero di tipi di campo a disposizione. Ognuno di questi mappa un tipo di dato PHP su un tipo specico di colonna in qualsiasi database si utilizzi. I seguenti tipi sono supportati in Doctrine: Stringhe string (per stringhe pi corte) text (per stringhe pi lunghe) Numeri integer smallint bigint decimal float Date e ore (usare un oggetto DateTime (http://php.net/manual/en/class.datetime.php) per questi campi in PHP) date time datetime Altri tipi boolean object (serializzato e memorizzato in un campo CLOB) array (serializzato e memorizzato in un campo CLOB) Per maggiori informazioni, vedere Documentazione sulla mappatura (http://docs.doctrine-project.org/orm/en/2.1/reference/basic-mapping.html#doctrine-mapping-types). dei tipi

2.1. Libro

129

Documentazione Symfony, Release 2.0

Opzioni dei campi

Ogni campo pu avere un insieme di opzioni da applicare. Le opzioni disponibili includono type (predenito string), name, length, unique e nullable. Vediamo alcuni esempi con le annotazioni: Annotations
/** * Un campo stringa con lunghezza 255 che non pu essere nullo * (riflette i valori predefiniti per le opzioni "type", "length" e *nullable*) * * @ORM\Column() */ protected $name; /** * Un campo stringa con lunghezza 150 che persiste su una colonna "email_address" * e ha un vincolo di unicit. * * @ORM\Column(name="email_address", unique="true", length="150") */ protected $email;

YAML
fields: # Un campo stringa con lunghezza 255 che non pu essere nullo # (riflette i valori predefiniti per le opzioni "type", "length" e *nullable*) # lattributo type necessario nelle definizioni yaml name: type: string # Un campo stringa con lunghezza 150 che persiste su una colonna "email_address" # e ha un vincolo di unicit. email: type: string column: email_address length: 150 unique: true

Nota: Ci sono alcune altre opzioni, non elencate qui. Per maggiori dettagli, vedere la Documentazione sulla mappatura delle propriet (http://docs.doctrine-project.org/orm/en/2.1/reference/basic-mapping.html#property-mapping)

Comandi da console Lintegrazione con lORM Doctrine2 offre diversi comandi da console, sotto lo spazio dei nomi doctrine. Per vedere la lista dei comandi, si pu eseguire la console senza parametri:
php app/console

Verr mostrata una lista dei comandi disponibili, molti dei quali iniziano col presso doctrine:. Si possono trovare maggiori informazioni eseguendo il comando help. Per esempio, per ottenere dettagli sul task doctrine:database:create, eseguire:
php app/console help doctrine:database:create

Alcuni task interessanti sono: 130 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

doctrine:ensure-production-settings - verica se lambiente attuale sia congurato efcientemente per la produzione. Dovrebbe essere sempre eseguito nellambiente prod:
php app/console doctrine:ensure-production-settings --env=prod

doctrine:mapping:import - consente a Doctrine lintrospezione di un database esistente e di creare quindi le informazioni di mappatura. Per ulteriori informazioni, vedere Come generare entit da una base dati esistente. doctrine:mapping:info - elenca tutte le entit di cui Doctrine a conoscenza e se ci sono o meno errori di base con la mappatura. doctrine:query:dql e doctrine:query:sql - consente lesecuzione di query DQL o SQL direttamente dalla linea di comando. Nota: Per poter caricare xture nel proprio database, occorrer avere il bundle DoctrineFixturesBundle installato. Per sapere come farlo, leggere la voce DoctrineFixturesBundle della documentazione.

Riepilogo Con Doctrine, ci si pu concentrare sui propri oggetti e su come siano utili nella propria applicazione e preoccuparsi della persistenza su database in un secondo momento. Questo perch Doctrine consente di usare qualsiasi oggetto PHP per tenere i propri dati e si appoggia su meta-dati di mappatura per mappare i dati di un oggetto su una particolare tabella di database. Sebbene Doctrine giri intorno a un semplice concetto, incredibilmente potente, consentendo di creare query complesse e sottoscrivere eventi che consentono di intraprendere diverse azioni, mentre gli oggetti viaggiano lungo il loro ciclo di vita della persistenza. Per maggiori informazioni su Doctrine, vedere la sezione Doctrine del ricettario, che include i seguenti articoli: DoctrineFixturesBundle Estensioni di Doctrine: Timestampable: Sluggable, Translatable, ecc.

2.1.9 Test
Ogni volta che si scrive una nuova riga di codice, si aggiungono potenzialmente nuovi bug. Per costruire applicazioni migliori e pi afdabili, si dovrebbe sempre testare il proprio codice, usando sia i test funzionali che quelli unitari. Il framework dei test PHPUnit Symfony2 si integra con una libreria indipendente, chiamata PHPUnit, per fornire un ricco framework per i test. Questo capitolo non approfondisce PHPUnit stesso, che ha comunque uneccellente documentazione (http://www.phpunit.de/manual/3.5/en/). Nota: Symfony2 funziona con PHPUnit 3.5.11 o successivi, ma per testare il codice del nucleo di Symfony occorre la versione 3.6.4. Ogni test, sia esso unitario o funzionale, una classe PHP, che dovrebbe trovarsi in una sotto-cartella Tests/ del proprio bundle. Seguendo questa regola, si possono eseguire tutti i test della propria applicazione con il seguente comando:
# specifica la cartella di configurazione nella linea di comando $ phpunit -c app/

2.1. Libro

131

Documentazione Symfony, Release 2.0

Lopzione -c dice a PHPUnit di cercare nella cartella app/ un le di congurazione. Chi fosse curioso di conoscere le opzioni di PHPUnit, pu dare uno sguardo al le app/phpunit.xml.dist. Suggerimento: Si pu generare la copertura del codice, con lopzione --coverage-html.

Test unitari Un test unitario solitamente un test di una specica classe PHP. Se si vuole testare il comportamento generale della propria applicazione, vedere la sezione dei Test funzionali. La scrittura di test unitari in Symfony2 non diversa dalla scrittura standard di test unitari in PHPUnit. Si supponga, per esempio, di avere una classe incredibilmente semplice, chiamata Calculator, nella cartella Utility/ del proprio bundle:
// src/Acme/DemoBundle/Utility/Calculator.php namespace Acme\DemoBundle\Utility; class Calculator { public function add($a, $b) { return $a + $b; } }

Per testarla, creare un le CalculatorTest nella cartella Tests/Utility del proprio bundle:
// src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php namespace Acme\DemoBundle\Tests\Utility; use Acme\DemoBundle\Utility\Calculator; class CalculatorTest extends \PHPUnit_Framework_TestCase { public function testAdd() { $calc = new Calculator(); $result = $calc->add(30, 12); // asserisce che il calcolatore aggiunga correttamente i numeri! $this->assertEquals(42, $result); } }

Nota: Per convenzione, si raccomanda di replicare la struttura di cartella di un bundle nella sua sotto-cartella Tests/. Quindi, se si testa una classe nella cartella Utility/ del proprio bundle, mettere il test nella cartella Tests/Utility/. Proprio come per lapplicazione reale, lautoloading abilitato automaticamente tramite bootstrap.php.cache (come congurato in modo predenito nel le phpunit.xml.dist). Anche eseguire i test per un dato le o una data cartella molto facile:
# eseguire tutti i test nella cartella Utility $ phpunit -c app src/Acme/DemoBundle/Tests/Utility/

il

le

132

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

# eseguire i test per la classe Calculator $ phpunit -c app src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php # eseguire tutti i test per lintero bundle $ phpunit -c app src/Acme/DemoBundle/

Test funzionali I test funzionali vericano lintegrazione dei diversi livelli di unapplicazione (dalle rotte alle viste). Non differiscono dai test unitari per quello che riguarda PHPUnit, ma hanno un usso di lavoro molto specico: Fare una richiesta; Testare la risposta; Cliccare su un collegamento o inviare un form; Testare la risposta; Ripetere.
Un primo test funzionale

I test funzionali sono semplici le PHP, che tipicamente risiedono nella cartella Tests/Controller del proprio bundle. Se si vogliono testare le pagine gestite dalla propria classe DemoController, si inizi creando un le DemoControllerTest.php, che estende una classe speciale WebTestCase. Per esempio, ledizione standard di Symfony2 fornisce un semplice test funzionale per il suo DemoController (DemoControllerTest (https://github.com/symfony/symfonystandard/blob/master/src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php)), fatto in questo modo:
// src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php namespace Acme\DemoBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class DemoControllerTest extends WebTestCase { public function testIndex() { $client = static::createClient(); $crawler = $client->request(GET, /demo/hello/Fabien); $this->assertTrue($crawler->filter(html:contains("Hello Fabien"))->count() > 0); } }

Suggerimento: Per eseguire i test funzionali, la classe WebTestCase inizializza il kernel dellapplicazione. Nella maggior parte dei casi, questo avviene in modo automatico. Tuttavia, se il proprio kernel si trova in una cartella non standard, occorre modicare il le phpunit.xml.dist e impostare nella variabile dambiente KERNEL_DIR la cartella del proprio kernel:
<phpunit <!-- ... -->

2.1. Libro

133

Documentazione Symfony, Release 2.0

<php> <server name="KERNEL_DIR" value="/percorso/della/propria/applicazione/" /> </php> <!-- ... --> </phpunit>

Il metodo createClient() restituisce un client, che come un browser da usare per visitare il proprio sito:
$crawler = $client->request(GET, /demo/hello/Fabien);

Il metodo request() (vedere di pi sul metodo della richiesta) restituisce un oggetto Symfony\Component\DomCrawler\Crawler, che pu essere usato per selezionare elementi nella risposta, per cliccare su collegamenti e per inviare form. Suggerimento: Il crawler pu essere usato solo se il contenuto della risposta un documento XML o HTML. Per altri tipi di contenuto, richiamare $client->getResponse()->getContent(). Cliccare su un collegamento, seleziondolo prima con il Crawler, usando o unespressione XPath o un selettore CSS, quindi usando il Client per cliccarlo. Per esempio, il codice seguente trova tutti i collegamenti con il testo Greet, quindi sceglie il secondo e inne lo clicca:
$link = $crawler->filter(a:contains("Greet"))->eq(1)->link(); $crawler = $client->click($link);

Inviare un form molto simile: selezionare il bottone di un form, eventualmente sovrascrivere alcuni valori del form e inviare il form corrispondente:
$form = $crawler->selectButton(submit)->form(); // impostare alcuni valori $form[name] = Lucas; $form[form_name[subject]] = Bella per te!; // inviare il form $crawler = $client->submit($form);

Suggerimento: Il form pu anche gestire caricamenti di le e contiene metodi utili per riempire diversi tipi di campi (p.e. select() e tick()). Per maggiori dettagli, vedere la sezione Form pi avanti. Ora che si in grado di navigare facilmente nellapplicazione, usare le asserzioni per testare che faccia effettivamente quello che ci si aspetta. Usare il Crawler per fare asserzioni sul DOM:
// Asserisce che la risposta corrisponda a un dato selettore CSS. $this->assertTrue($crawler->filter(h1)->count() > 0);

Oppure, testare direttamente il contenuto della risposta, se si vuole solo asserire che il contenuto debba contenere del testo o se la risposta non un documento XML/HTML:
$this->assertRegExp(/Hello Fabien/, $client->getResponse()->getContent());

134

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Di pi sul metodo request(): La rma completa del metodo request() :


request( $method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true )

Larray server contiene i valori grezzi che ci si aspetta di trovare normalmente nellarray superglobale $_SERVER (http://php.net/manual/en/reserved.variables.server.php) di PHP. Per esempio, per impostare gli header HTTP Content-Type e Referer, passare i seguenti:
$client->request( GET, /demo/hello/Fabien, array(), array(), array( CONTENT_TYPE => application/json, HTTP_REFERER => /foo/bar, ) );

Lavorare con il client dei test Il client dei test emula un client HTTP, come un browser, ed effettua richieste allapplicazione Symfony2:
$crawler = $client->request(GET, /hello/Fabien);

Il metodo request() accetta come parametri il metodo HTTP e un URL e restituisce unistanza di Crawler. Usare il crawler per cercare elementi del DOM nella risposta. Questi elementi possono poi essere usati per cliccare su collegamenti e inviare form:
$link = $crawler->selectLink(Vai da qualche parte...)->link(); $crawler = $client->click($link); $form = $crawler->selectButton(validare)->form(); $crawler = $client->submit($form, array(name => Fabien));

I metodi click() e submit() restituiscono entrambi un oggetto Crawler. Questi metodi sono il modo migliore per navigare unapplicazione, perch si occupano di diversi dettagli, come il metodo HTTP di un form e il fornire unutile API per caricare le. Suggerimento: Gli oggetti Link e Form nel crawler saranno approfonditi nella sezione Crawler, pi avanti. Il metodo request() pu anche essere usato per simulare direttamente linvio di form o per eseguire richieste pi complesse:

2.1. Libro

135

Documentazione Symfony, Release 2.0

// Invio diretto di form $client->request(POST, /submit, array(name => Fabien)); // Invio di form di con caricamento di file use Symfony\Component\HttpFoundation\File\UploadedFile; $photo = new UploadedFile( /path/to/photo.jpg, photo.jpg, image/jpeg, 123 ); // oppure $photo = array( tmp_name => /path/to/photo.jpg, name => photo.jpg, type => image/jpeg, size => 123, error => UPLOAD_ERR_OK ); $client->request( POST, /submit, array(name => Fabien), array(photo => $photo) ); // Eseguire richieste DELETE e passare header HTTP $client->request( DELETE, /post/12, array(), array(), array(PHP_AUTH_USER => username, PHP_AUTH_PW => pa$$word) );

Inne, ma non meno importante, si pu forzare lesecuzione di ogni richiesta nel suo processo PHP, per evitare effetti collaterali quando si lavora con molti client nello stess script:
$client->insulate();

Browser

Il client supporta molte operazioni eseguibili in un browser reale:


$client->back(); $client->forward(); $client->reload(); // Pulisce tutti i cookie e la cronologia $client->restart();

Accesso agli oggetti interni

Se si usa il client per testare la propria applicazione, si potrebbe voler accedere agli oggetti interni del client:

136

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

$history = $client->getHistory(); $cookieJar = $client->getCookieJar();

I possono anche ottenere gli oggetti relativi allultima richiesta:


$request = $client->getRequest(); $response = $client->getResponse(); $crawler = $client->getCrawler();

Se le richieste non sono isolate, si pu accedere agli oggetti Container e Kernel:


$container = $client->getContainer(); $kernel = $client->getKernel();

Accesso al contenitore

caldamente raccomandato che un test funzionale testi solo la risposta. Ma sotto alcune rare circostanze, si potrebbe voler accedere ad alcuni oggetti interni, per scrivere asserzioni. In questi casi, si pu accedere al dependency injection container:
$container = $client->getContainer();

Attenzione, perch questo non funziona se si isola il client o se si usa un livello HTTP. Per un elenco di servizi disponibili nellapplicazione, usare il comando container:debug. Suggerimento: Se linformazione che occorre vericare disponibile nel proler, si usi invece questultimo.

Accedere ai dati del prolatore

A ogni richiesta, il proler di Symfony raccoglie e memorizza molti dati, che riguardano la gestione interna della richiesta stessa. Per esempio, il prolatore pu essere usato per vericare che una data pagina esegua meno di un certo numero di query alla base dati. Si pu ottenere il prolatore dellultima richiesta in questo modo:
$profile = $client->getProfile();

Per dettagli specici sulluso del prolatore in un test, vedere la ricetta Come usare il prolatore nei test funzionali.
Rinvii

Quando una richiesta restituisce una risposta di rinvio, il client la segue automaticamente. Se si vuole esaminare la rispostsa prima del rinvio, si pu forzare il client a non seguire i rinvii, usando il metodo followRedirect():
$crawler = $client->followRedirect(false);

Quando il client non segue i rinvvi, lo si pu forzare con il metodo followRedirect():


$crawler = $client->followRedirect();

2.1. Libro

137

Documentazione Symfony, Release 2.0

Il crawler

Unistanza del crawler creata automaticamente quando si esegue una richiesta con un client. Consente di attraversare i documenti HTML, selezionare nodi, trovare collegamenti e form.
Attraversamento

Come jQuery, il crawler dispone di metodi per attraversare il DOM di documenti HTML/XML. Per esempio, per estrarre tutti gli elementi input[type=submit], trovarne lultimo e quindi selezionare il suo genitore:
$newCrawler = $crawler->filter(input[type=submit]) ->last() ->parents() ->first() ;

Ci sono molti altri metodi a disposizione: Metodo filter(h1.title) filterXpath(h1) eq(1) first() last() siblings() nextAll() previousAll() parents() children() reduce($lambda) Descrizione Nodi corrispondenti al selettore CSS Nodi corrispondenti allespressione XPath Nodi per lindice specicato Primo nodo Ultimo nodo Fratelli Tutti i fratelli successivi Tutti i fratelli precedenti Genitori Figli Nodi per cui la funzione non restituisce false

Si pu iterativamente restringere la selezione del nodo, concatenando le chiamate ai metodi, perch ogni metodo restituisce una nuova istanza di Crawler per i nodi corrispondenti:
$crawler ->filter(h1) ->reduce(function ($node, $i) { if (!$node->getAttribute(class)) { return false; } }) ->first();

Suggerimento: Usare la funzione count() per ottenere il numero di nodi memorizzati in un crawler: count($crawler)

Estrarre informazioni

Il crawler pu estrarre informazioni dai nodi:


// Restituisce il valore dellattributo del primo nodo $crawler->attr(class);

138

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

// Restituisce il valore del nodo del primo nodo $crawler->text(); // Estrae un array di attributi per tutti i nodi (_text restituisce il valore del nodo) $crawler->extract(array(_text, href)); // Esegue una funzione lambda per ogni nodo e restituisce un array di risultati $data = $crawler->each(function ($node, $i) { return $node->getAttribute(href); });

Collegamenti

Si possono selezionare collegamenti coi metodi di attraversamento, ma la scorciatoia selectLink() spesso pi conveniente:
$crawler->selectLink(Clicca qui);

Seleziona i collegamenti che contengono il testo dato, oppure le immagini cliccabili per cui lattributi alt contiene il testo dato. Come gli altri metodi ltro, restituisce un altro oggetto Crawler. Una volta selezionato un collegamento, si ha accesso a uno speciale oggetto Link, che ha utili metodi specici per i collegamenti (come getMethod() e getUri()). Per cliccare sul collegamento, usare il metodo click() di Client e passargli un oggetto Link:
$link = $crawler->selectLink(Click here)->link(); $client->click($link);

Form

Come per i collegamenti, si possono selezionare i form col metodo selectButton():


$buttonCrawlerNode = $crawler->selectButton(submit);

Nota: Si noti che si selezionano i bottoni dei form e non i form stessi, perch un form pu avere pi bottoni; se si usa lAPI di attraversamento, si tenga a mente che si deve cercare un bottone. Il metodo selectButton() pu selezionare i tag button e i tag input con attributo submit. Ha diverse euristiche per trovarli: Il valore dellattributo value; Il valore dellattributo id o alt per le immagini; Il valore dellattributo id o name per i tag button. Quando si a un nodo che rappresenta un bottone, richiamare il metodo form() per ottenere unistanza Form per il form, che contiene il nodo bottone. $form = $buttonCrawlerNode->form(); Quando si richiama il metodo form(), si pu anche passare un array di valori di campi, che sovrascrivano quelli predeniti:

2.1. Libro

139

Documentazione Symfony, Release 2.0

$form = $buttonCrawlerNode->form(array( name => Fabien, my_form[subject] => Symfony spacca!, ));

Se si vuole emulare uno specico metodo HTTP per il form, passarlo come secondo parametro:
$form = $buttonCrawlerNode->form(array(), DELETE);

Il client puoi inviare istanze di Form:


$client->submit($form);

Si possono anche passare i valori dei campi come secondo parametro del metodo submit():
$client->submit($form, array( name => Fabien, my_form[subject] => Symfony spacca!, ));

Per situazioni pi complesse, usare listanza di Form come un array, per impostare ogni valore di campo individualmente:
// Cambiare il valore di un campo $form[name] = Fabien; $form[my_form[subject]] = Symfony spacca!;

C anche unutile API per manipolare i valori dei campi, a seconda del tipo:
// Selezionare unopzione o un radio $form[country]->select(France); // Spuntare un checkbox $form[like_symfony]->tick(); // Caricare un file $form[photo]->upload(/path/to/lucas.jpg);

Suggerimento: Si possono ottenere i valori che saranno inviati, richiamando il metodo getValues(). I le caricati sono disponibili in un array separato, restituito dal metodo getFiles(). Anche i metodi getPhpValues() e getPhpFiles() restituiscono i valori inviati, ma nel formato di PHP (convertendo le chiavi con parentesi quadre, p.e. my_form[subject] da, nella notazione degli array di PHP).

Congurazione dei test Il client usato dai test funzionali crea un kernel che gira in uno speciale ambiente test. Sicomme Symfonu carica app/config/config_test.yml in ambiente test, si possono modicare le impostazioni della propria applicazione spericatamente per i test. Per esempio, swiftmailer congurato in modo predenito per non inviare le email in ambiente test. Lo si pu vedere sotto lopzione di congurazione swiftmailer: YAML
# app/config/config_test.yml # ...

140

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

swiftmailer: disable_delivery: true

XML
<!-- app/config/config_test.xml --> <container> <!-- ... --> <swiftmailer:config disable-delivery="true" /> </container>

PHP
// app/config/config_test.php // ... $container->loadFromExtension(swiftmailer, array( disable_delivery => true ));

Si pu anche cambiare lambiente predenito (test) e sovrascrivere la modalit predenita di debug (true) passandoli come opzioni al metodo createClient():
$client = static::createClient(array( environment => my_test_env, debug => false, ));

Se la propria applicazione necessita di alcuni header HTTP, passarli come secondo parametro di createClient():
$client = static::createClient(array(), array( HTTP_HOST => en.example.com, HTTP_USER_AGENT => MySuperBrowser/1.0, ));

Si possono anche sovrascrivere gli header HTTP a ogni richiesta:


$client->request(GET, /, array(), array(), array( HTTP_HOST => en.example.com, HTTP_USER_AGENT => MySuperBrowser/1.0, ));

Suggerimento: Il client dei test disponibile come servizio nel contenitore, in ambiente test (o dovunque sia abilitata lopzione framework.test). Questo vuol dire che si pu ridenire completamente il servizio, qualora se ne avesse la necessit.

Congurazione di PHPUnit

Ogni applicazione ha la sua congurazione di PHPUnit, memorizzata nel le phpunit.xml.dist. Si pu modicare tale le per cambiare i default, oppure creare un le phpunit.xml per aggiustare la congurazione per la propria macchina locale. Suggerimento: Inserire il le phpunit.xml.dist nel proprio repository e ignorare il le phpunit.xml.

2.1. Libro

141

Documentazione Symfony, Release 2.0

Per impostazione predenita, solo i test memorizzati nei bundle standard sono eseguiti dal comando phpunit (per standard si intendono i test nelle cartelle src/*/Bundle/Tests o src/*/Bundle/*Bundle/Tests). Ma si possono facilmente aggiungere altri spazi dei nomi. Per esempio, la congurazione seguente aggiunge i test per i bundle installati di terze parti:
<!-- hello/phpunit.xml.dist --> <testsuites> <testsuite name="Project Test Suite"> <directory>../src/*/*Bundle/Tests</directory> <directory>../src/Acme/Bundle/*Bundle/Tests</directory> </testsuite> </testsuites>

Per includere altre cartelle nella copertura del codice, modicare anche la sezione <filter>:
<filter> <whitelist> <directory>../src</directory> <exclude> <directory>../src/*/*Bundle/Resources</directory> <directory>../src/*/*Bundle/Tests</directory> <directory>../src/Acme/Bundle/*Bundle/Resources</directory> <directory>../src/Acme/Bundle/*Bundle/Tests</directory> </exclude> </whitelist> </filter>

Imparare di pi con le ricette Come simulare unautenticazione HTTP in un test funzionale Come testare linterazione con diversi client Come usare il prolatore nei test funzionali

2.1.10 Validazione
La validazione un compito molto comune nella applicazioni web. I dati inseriti nei form hanno bisogno di essere validati. I dati hanno bisogno di essere validati anche prima di essere inseriti in una base dati o passati a un servizio web. Symfony2 ha un componente Validator (https://github.com/symfony/Validator) , che rende questo compito facile e trasparente. Questo componente bastato sulle speciche di validazione JSR303 Bean (http://jcp.org/en/jsr/detail?id=303). Cosa? Speciche Java in PHP? Proprio cos, ma non cos male come potrebbe sembrare. Vediamo come usarle in PHP. Le basi della validazione Il modo migliore per capire la validazione quello di vederla in azione. Per iniziare, supponiamo di aver creato un classico oggetto PHP, da usare in qualche parte della propria applicazione:
// src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity; class Author {

142

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

public $name; }

Finora, questa solo una normale classe, che ha una qualche utilit allinterno della propria applicazione. Lo scopo della validazione dire se i dati di un oggetto siano validi o meno. Per poterlo fare, occorre congurare una lisa di regole (chiamate vincoli) che loggetto deve seguire per poter essere valido. Queste regole possono essere specicate tramite diversi formati (YAML, XML, annotazioni o PHP). Per esempio, per garantire che la propriet $name non sia vuota, aggiungere il seguente: YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: name: - NotBlank: ~

Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\NotBlank() */ public $name; }

XML

<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/sche <class name="Acme\BlogBundle\Entity\Author"> <property name="name"> <constraint name="NotBlank" /> </property> </class> </constraint-mapping>

PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; class Author { public $name; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint(name, new NotBlank());

2.1. Libro

143

Documentazione Symfony, Release 2.0

} }

Suggerimento: Anche le propriet private e protette possono essere validati, cos come i metodi getter (vedere validator-constraint-targets).

Usare il servizio validator

Successivamente, per validare veramente un oggetto Author, usare il metodo validate sul servizio validator (classe Symfony\Component\Validator\Validator). Il compito di validator semplice: leggere i vincoli (cio le regole) di una classe e vericare se i dati delloggetto soddis o no tali vincoli. Se la validazione fallisce, viene restituito un array di errori. Prendiamo questo semplice esempio dallinterno di un controllore:
use Symfony\Component\HttpFoundation\Response; use Acme\BlogBundle\Entity\Author; // ... public function indexAction() { $author = new Author(); // ... fare qualcosa con loggetto $author $validator = $this->get(validator); $errors = $validator->validate($author); if (count($errors) > 0) { return new Response(print_r($errors, true)); } else { return new Response(L\autore valido! S!); } }

Se la propriet $name vuota, si vedr il seguente messaggio di errore:


Acme\BlogBundle\Author.name: This value should not be blank

Se si inserisce un valore per la propriet $name, apparir il messaggio di successo. Suggerimento: La maggior parte delle volte, non si interagir direttamente con il servizio validator, n ci si dovr occupare di stampare gli errori. La maggior parte delle volte, si user indirettamente la validazione, durante la gestione di dati inviati tramite form. Per maggiori informazioni, vedere Validazione e form. Si pu anche passare un insieme di errori in un template.
if (count($errors) > 0) { return $this->render(AcmeBlogBundle:Author:validate.html.twig, array( errors => $errors, )); } else { // ... }

Dentro al template, si pu stampare la lista di errori, come necessario:

144

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Twig
{# src/Acme/BlogBundle/Resources/views/Author/validate.html.twig #} <h3>Lautore ha i seguenti errori</h3> <ul> {% for error in errors %} <li>{{ error.message }}</li> {% endfor %} </ul>

PHP
<!-- src/Acme/BlogBundle/Resources/views/Author/validate.html.php --> <h3>Lautore ha i seguenti errori</h3> <ul> <?php foreach ($errors as $error): ?> <li><?php echo $error->getMessage() ?></li> <?php endforeach; ?> </ul>

Nota: Ogni errore di validazione (chiamato violazione di vincolo) rappresentato da un oggetto Symfony\Component\Validator\ConstraintViolation.

Validazione e form

Il servizio validator pu essere usato per validare qualsiasi oggetto. In realt, tuttavia, solitamente si lavorer con validator indirettamente, lavorando con i form. La libreria dei form di Symfony usa internamente il servizio validator, per validare loggetto sottostante dopo che i valori sono stati inviati e collegati. Le violazioni dei vincoli sulloggetto sono convertite in oggetti FieldError, che possono essere facilmente mostrati con il proprio form. Il tipico usso dellinvio di un form assomiglia al seguente, allinterno di un controllore:
use Acme\BlogBundle\Entity\Author; use Acme\BlogBundle\Form\AuthorType; use Symfony\Component\HttpFoundation\Request; // ... public function updateAction(Request $request) { $author = new Acme\BlogBundle\Entity\Author(); $form = $this->createForm(new AuthorType(), $author); if ($request->getMethod() == POST) { $form->bindRequest($request); if ($form->isValid()) { // validazionoe passata, fare qualcosa con loggetto $author return $this->redirect($this->generateUrl(...)); } } return $this->render(BlogBundle:Author:form.html.twig, array( form => $form->createView(),

2.1. Libro

145

Documentazione Symfony, Release 2.0

)); }

Nota: Questo esempio usa una classe AuthorType, non mostrata qui. Per maggiori informazioni, vedere il capitolo sui Form. Congurazione La validazione in Symfony2 abilitata per congurazione predenita, ma si devono abilitare esplicitamente le annotazioni, se le si usano per specicare i vincoli: YAML
# app/config/config.yml framework: validation: { enable_annotations: true }

XML
<!-- app/config/config.xml --> <framework:config> <framework:validation enable_annotations="true" /> </framework:config>

PHP
// app/config/config.php $container->loadFromExtension(framework, array(validation => array( enable_annotations => true, )));

Vincoli Il servizio validator progettato per validare oggetti in base a vincoli (cio regole). Per poter validare un oggetto, basta mappare uno o pi vincoli alle rispettive classi e quindi passarli al servizio validator. Dietro le quinte, un vincolo semplicemente un oggetto PHP che esegue unistruzione assertiva. Nella vita reale, un vincolo potrebbe essere la torta non deve essere bruciata. In Symfony2, i vincoli sono simili: sono asserzioni sulla verit di una condizione. Dato un valore, un vincolo dir se tale valore sia aderente o meno alle regole del vincolo.
Vincoli supportati

Symfony2 dispone di un gran numero dei vincoli pi comunemente necessari:


Vincoli di base

Questi sono i vincoli di base: usarli per asserire cose molto basilari sul valore delle propriet o sui valori restituiti dai metodi del proprio oggetto. NotBlank Blank

146

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

NotNull Null True False Type


Vincoli stringhe

Email MinLength MaxLength Url Regex Ip


Vincoli numerici

Max Min
Vincoli date

Date DateTime Time


Vincoli di insiemi

Choice Collection UniqueEntity Language Locale Country


Vincoli di le

File Image

2.1. Libro

147

Documentazione Symfony, Release 2.0

Altri vincoli

Callback All UserPassword Valid Si possono anche creare i propri vincoli personalizzati. Largomento coperto nellarticolo Come creare vincoli di validazione personalizzati del ricettario.
Congurazione dei vincoli

Alcuni vincoli, come NotBlank, sono semplici, mentre altri, come Choice, hanno diverse opzioni di congurazione disponibili. Supponiamo che la classe Author abbia unaltra propriet, gender, che possa valore solo M oppure F: YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: gender: - Choice: { choices: [M, F], message: Scegliere un genere valido. }

Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\Choice( choices = { "M", "F" }, * message = "Scegliere un genere valido." * * ) */ public $gender; }

XML

<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/sche <class name="Acme\BlogBundle\Entity\Author"> <property name="gender"> <constraint name="Choice"> <option name="choices"> <value>M</value> <value>F</value> </option> <option name="message">Scegliere un genere valido.</option>

148

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

</constraint> </property> </class> </constraint-mapping>

PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; class Author { public $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint(gender, new Choice(array( choices => array(M, F), message => Scegliere un genere valido., ))); } }

Le opzioni di un vincolo possono sempre essere passate come array. Alcuni vincoli, tuttavia, consentono anche di passare il valore di una sola opzione, predenita, al posto dellarray. Nel caso del vincolo Choice, lopzione choices pu essere specicata in tal modo. YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: gender: - Choice: [M, F]

Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /** * @Assert\Choice({"M", "F"}) */ protected $gender; }

XML

<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/sche <class name="Acme\BlogBundle\Entity\Author"> <property name="gender"> <constraint name="Choice">

2.1. Libro

149

Documentazione Symfony, Release 2.0

<value>M</value> <value>F</value> </constraint> </property> </class> </constraint-mapping>

PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\Choice; class Author { protected $gender; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint(gender, new Choice(array(M, F))); } }

Questo ha il solo scopo di rendere la congurazione delle opzioni pi comuni di un vincolo pi breve e rapida. Se non si sicuri di come specicare unopzione, vericare la documentazione delle API per il vincolo relativo, oppure andare sul sicuro passando sempre un array di opzioni (il primo metodo mostrato sopra). Obiettivi dei vincoli I vincoli possono essere applicati alle propriet di una classe (p.e. $name) oppure a un metodo getter pubblico (p.e. getFullName). Il primo il modo pi comune e facile, ma il secondo consente di specicare regole di validazione pi complesse.
Propriet

La validazione delle propriet di una classe la tecnica di base. Symfony2 consente di validare propriet private, protette o pubbliche. Lelenco seguente mostra come congurare la propriet $firstName di una classe Author, per avere almeno 3 caratteri. YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: properties: firstName: - NotBlank: ~ - MinLength: 3

Annotations
// Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author { /**

150

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

* @Assert\NotBlank() * @Assert\MinLength(3) */ private $firstName; }

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\Author"> <property name="firstName"> <constraint name="NotBlank" /> <constraint name="MinLength">3</constraint> </property> </class>

PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\MinLength; class Author { private $firstName; public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint(firstName, new NotBlank()); $metadata->addPropertyConstraint(firstName, new MinLength(3)); } }

Getter

I vincoli si possono anche applicare ai valori restituiti da un metodo. Symfony2 consente di aggiungere un vincolo a qualsiasi metodo il cui nome inizi per get o is. In questa guida, si fa riferimento a questi due tipi di metodi come getter. Il vantaggio di questa tecnica che consente di validare i proprio oggetti dinamicamente. Per esempio, supponiamo che ci si voglia assicurare che un campo password non corrisponda al nome dellutente (per motivi di sicurezza). Lo si pu fare creando un metodo isPasswordLegal e asserendo che tale metodo debba restituire true: YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\Author: getters: passwordLegal: - "True": { message: "La password non pu essere uguale al nome" }

Annotations
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Constraints as Assert; class Author

2.1. Libro

151

Documentazione Symfony, Release 2.0

{ /** * @Assert\True(message = "La password non pu essere uguale al nome") */ public function isPasswordLegal() { // return true or false } }

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\Author"> <getter property="passwordLegal"> <constraint name="True"> <option name="message">La password non pu essere uguale al nome</option> </constraint> </getter> </class>

PHP
// src/Acme/BlogBundle/Entity/Author.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\True; class Author { public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addGetterConstraint(passwordLegal, new True(array( message => La password non pu essere uguale al nome, ))); } }

Creare ora il metodo isPasswordLegal() e includervi la logica necessaria:


public function isPasswordLegal() { return ($this->firstName != $this->password); }

Nota: I lettori pi attenti avranno notato che il presso del getter (get o is) viene omesso nella mappatura. Questo consente di spostare il vincolo su una propriet con lo stesso nome, in un secondo momento (o viceversa), senza dover cambiare la logica di validazione.

Classi

Alcuni vincoli si applicano allintera classe da validare. Per esempio, il vincolo Callback un vincolo generico, che si applica alla classe stessa. Quano tale classe viene validata, i metodi specici di questo vincolo vengono semplicemente eseguiti, in modo che ognuno possa fornire una validazione personalizzata.

152

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Gruppi di validazione Finora, si stati in grado di aggiungere vincoli a una classe e chiedere se tale classe passasse o meno tutti i vincoli deniti. In alcuni casi, tuttavia, occorre validare un oggetto solo per alcuni vincoli della sua classe. Per poterlo fare, si pu organizzare ogni vincolo in uno o pi gruppi di validazione e quindi applicare la validazione solo su un gruppo di vincoli. Per esempio, si supponga di avere una classe User, usata sia quando un utente si registra che quando aggiorna successivamente le sue informazioni: YAML
# src/Acme/BlogBundle/Resources/config/validation.yml Acme\BlogBundle\Entity\User: properties: email: - Email: { groups: [registration] } password: - NotBlank: { groups: [registration] } - MinLength: { limit: 7, groups: [registration] } city: - MinLength: 2

Annotations
// src/Acme/BlogBundle/Entity/User.php namespace Acme\BlogBundle\Entity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; class User implements UserInterface { /** * @Assert\Email(groups={"registration"}) */ private $email; /** * @Assert\NotBlank(groups={"registration"}) * @Assert\MinLength(limit=7, groups={"registration"}) */ private $password; /** * @Assert\MinLength(2) */ private $city; }

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml --> <class name="Acme\BlogBundle\Entity\User"> <property name="email"> <constraint name="Email"> <option name="groups"> <value>registration</value> </option> </constraint>

2.1. Libro

153

Documentazione Symfony, Release 2.0

</property> <property name="password"> <constraint name="NotBlank"> <option name="groups"> <value>registration</value> </option> </constraint> <constraint name="MinLength"> <option name="limit">7</option> <option name="groups"> <value>registration</value> </option> </constraint> </property> <property name="city"> <constraint name="MinLength">7</constraint> </property> </class>

PHP
// src/Acme/BlogBundle/Entity/User.php namespace Acme\BlogBundle\Entity; use use use use Symfony\Component\Validator\Mapping\ClassMetadata; Symfony\Component\Validator\Constraints\Email; Symfony\Component\Validator\Constraints\NotBlank; Symfony\Component\Validator\Constraints\MinLength;

class User { public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint(email, new Email(array( groups => array(registration) ))); $metadata->addPropertyConstraint(password, new NotBlank(array( groups => array(registration) ))); $metadata->addPropertyConstraint(password, new MinLength(array( limit => 7, groups => array(registration) ))); $metadata->addPropertyConstraint(city, new MinLength(3)); } }

Con questa congurazione, ci sono due gruppi di validazione: Default - contiene i vincoli non assegnati ad altri gruppi; registration - contiene solo i vincoli sui campi email e password. Per dire al validatore di usare uno specico gruppo, passare uno o pi nomi di gruppo come secondo parametro del metodo validate():
$errors = $validator->validate($author, array(registration));

154

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Ovviamente, di solito si lavorer con la validazione in modo indiretto, tramite la libreria dei form. Per informazioni su come usare i gruppi di validazione dentro ai form, vedere Gruppi di validatori. Validare valori e array Finora abbiamo visto come si possono validare oggetti interi. Ma a volte si vuole validare solo un semplice valore, come vericare che una stringa sia un indirizzo email valido. Lo si pu fare molto facilmente. Da dentro a un controllore, assomiglia a questo:
// aggiungere questa riga in cima alla propria classe use Symfony\Component\Validator\Constraints\Email; public function addEmailAction($email) { $emailConstraint = new Email(); // tutte le opzioni sui vincoli possono essere impostate in questo modo $emailConstraint->message = Invalid email address; // usa il validatore per validare il valore $errorList = $this->get(validator)->validateValue($email, $emailConstraint); if (count($errorList) == 0) { // un indirizzo email valido, fare qualcosa } else { // *non* un indirizzo email valido $errorMessage = $errorList[0]->getMessage() // fare qualcosa con lerrore } // ... }

Richiamando validateValue sul validatore, si pu passare un valore grezzo e loggetto vincolo su cui si vuole validare tale valore. Una lista completa di vincoli disponibili, cos come i nomi completi delle classi per ciascun vincolo, disponibile nella sezione riferimento sui vincoli. Il metodo validateValule restituisce un oggetto Symfony\Component\Validator\ConstraintViolationList, che si comporta come un array di errori. Ciascun errore della lista un oggetto Symfony\Component\Validator\ConstraintViolation, che contiene il messaggio di errore nel suo metodo getMessage. Considerazioni nali validator di Symfony2 uno strumento potente, che pu essere sfruttato per garantire che i dati di qualsiasi oggetto siano validi. La potenza dietro alla validazione risiede nei vincoli, che sono regole da applicare alle propriet o ai metodi getter del proprio oggetto. Sebbene la maggior parte delle volte si user il framework della validazione indirettamente, usando i form, si ricordi che pu essere usato ovunque, per validare qualsiasi oggetto. Imparare di pi con le ricette Come creare vincoli di validazione personalizzati

2.1. Libro

155

Documentazione Symfony, Release 2.0

2.1.11 Form
Lutilizzo dei form HTML uno degli utilizzi pi comuni e stimolanti per uno sviluppatore web. Symfony2 integra un componente Form che permette di gestire facilmente i form. Con laiuto di questo capitolo si potr creare da zero un form complesso, e imparare le caratteristiche pi importanti della libreria dei form. Nota: Il componente form di Symfony una libreria autonoma che pu essere usata al di fuori dei progetti Symfony2. Per maggiori informazioni, vedere il Componente Form di Symfony2 (https://github.com/symfony/Form) su Github.

Creazione di un form semplice Supponiamo che si stia costruendo un semplice applicazione elenco delle cose da fare che dovr visualizzare le attivit. Poich gli utenti avranno bisogno di modicare e creare attivit, sar necessario costruire un form. Ma prima di iniziare, si andr a vedere la generica classe Task che rappresenta e memorizza i dati di una singola attivit:
// src/Acme/TaskBundle/Entity/Task.php namespace Acme\TaskBundle\Entity; class Task { protected $task; protected $dueDate; public function getTask() { return $this->task; } public function setTask($task) { $this->task = $task; } public function getDueDate() { return $this->dueDate; } public function setDueDate(\DateTime $dueDate = null) { $this->dueDate = $dueDate; } }

Nota: Se si sta provando a digitare questo esempio, bisogna prima creare AcmeTaskBundle lanciando il seguente comando (e accettando tutte le opzioni predenite):
php app/console generate:bundle --namespace=Acme/TaskBundle

Questa classe un vecchio-semplice-oggetto-PHP, perch nora non ha nulla a che fare con Symfony o qualsiasi altra libreria. semplicemente un normale oggetto PHP, che risolve un problema direttamente dentro la propria applicazione (cio la necessit di rappresentare un task nella propria applicazione). Naturalmente, alla ne di questo capitolo, si sar in grado di inviare dati allistanza di un Task (tramite un form HTML), validare i suoi dati e persisterli nella base dati.

156

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Costruire il Form

Ora che la classe Task stata creata, il prossimo passo creare e visualizzare il form HTML. In Symfony2, lo si fa costruendo un oggetto form e poi visualizzandolo in un template. Per ora, lo si pu fare allinterno di un controllore:
// src/Acme/TaskBundle/Controller/DefaultController.php namespace Acme\TaskBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Acme\TaskBundle\Entity\Task; use Symfony\Component\HttpFoundation\Request; class DefaultController extends Controller { public function newAction(Request $request) { // crea un task fornendo alcuni dati fittizi per questo esempio $task = new Task(); $task->setTask(Scrivere un post sul blog); $task->setDueDate(new \DateTime(tomorrow)); $form = $this->createFormBuilder($task) ->add(task, text) ->add(dueDate, date) ->getForm(); return $this->render(AcmeTaskBundle:Default:new.html.twig, array( form => $form->createView(), )); } }

Suggerimento: Questo esempio mostra come costruire il form direttamente nel controllore. Pi tardi, nella sezione Creare classi per i form, si imparer come costruire il form in una classe autonoma, metodo consigliato perch in questo modo il form diventa riutilizzabile. La creazione di un form richiede relativamente poco codice, perch gli oggetti form di Symfony2 sono costruiti con un costruttore di form. Lo scopo del costruttore di form quello di consentire di scrivere una semplice ricetta per il form e fargli fare tutto il lavoro pesante della costruzione del form. In questo esempio sono stati aggiunti due campi al form, task e dueDate, corrispondenti alle propriet task e dueDate della classe Task. stato anche assegnato un tipo ciascuno (ad esempio text, date), che, tra le altre cose, determina quale tag form HTML viene utilizzato per tale campo. Symfony2 ha molti tipi predeniti che verranno trattati a breve (see Tipi di campo predeniti).
Visualizzare il Form

Ora che il modulo stato creato, il passo successivo quello di visualizzarlo. Questo viene fatto passando uno speciale oggetto form view al template (notare il $form->createView() nel controllore sopra) e utilizzando una serie di funzioni helper per i form: Twig
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}

2.1. Libro

157

Documentazione Symfony, Release 2.0

<form action="{{ path(task_new) }}" method="post" {{ form_enctype(form) }}> {{ form_widget(form) }} <input type="submit" /> </form>

PHP
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->

<form action="<?php echo $view[router]->generate(task_new) ?>" method="post" <?php echo $vie <?php echo $view[form]->widget($form) ?> <input type="submit" /> </form>

Nota: Questo esempio presuppone che sia stata creata una rotta chiamata task_new che punta al controllore AcmeTaskBundle:Default:new che era stato creato precedentemente. Questo tutto! Scrivendo form_widget(form), ciascun campo del form viene reso, insieme a unetichetta e a un messaggio di errore (se presente). Per quanto semplice, questo metodo non molto essibile (ancora). Di solito, si ha bisogno di rendere individualmente ciascun campo in modo da poter controllare la visualizzazione del form. Si imparer a farlo nella sezione Rendere un form in un template. Prima di andare avanti, notare come il campo input task reso ha il value della propriet task dalloggetto $task (ad esempio Scrivere un post sul blog). Questo il primo compito di un form: prendere i dati da un oggetto e tradurli in un formato adatto a essere reso in un form HTML. Suggerimento: Il sistema dei form abbastanza intelligente da accedere al valore della propriet protetta task attraverso i metodi getTask() e setTask() della classe Task. A meno che una propriet non sia privata, deve avere un metodo getter e uno setter, in modo che il componente form possa ottenere e mettere dati nella propriet. Per una propriet booleana, possibile utilizzare un metodo isser (ad esempio isPublished()) invece di un getter ad esempio getPublished()).

Gestione dellinvio del form

Il secondo compito di un form quello di tradurre i dati inviati dallutente alle propriet di un oggetto. Afnch ci avvenga, i dati inviati dallutente devono essere associati al form. Aggiungere le seguenti funzionalit al controllore:
// ... public function newAction(Request $request) {

158

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

// crea un nuovo oggetto $task (rimuove i dati fittizi) $task = new Task(); $form = $this->createFormBuilder($task) ->add(task, text) ->add(dueDate, date) ->getForm(); if ($request->getMethod() == POST) { $form->bindRequest($request); if ($form->isValid()) { // esegue alcune azioni, come ad esempio salvare il task nella base dati return $this->redirect($this->generateUrl(task_success)); } } // ... }

Ora, quando si invia il form, il controllore associa i dati inviati al form, che traduce nuovamente i dati alle propriet task e dueDate delloggetto $task. Tutto questo avviene attraverso il metodo bindRequest(). Nota: Appena viene chiamata bindRequest(), i dati inviati vengono immediatamente trasferiti alloggetto sottostante. Questo avviene indipendentemente dal fatto che i dati sottostanti siano validi o meno. Questo controllore segue uno schema comune per gestire i form e ha tre possibili percorsi: 1. Quando in un browser inizia il caricamento di una pagina, il metodo request GET e il form semplicemente creato e reso; 2. Quando lutente invia il form (cio il metodo POST) con dati non validi (la validazione trattata nella sezione successiva), il form associato e poi reso, questa volta mostrando tutti gli errori di validazione; 3. Quando lutente invia il form con dati validi, il form viene associato e si ha la possibilit di eseguire alcune azioni usando loggetto $task (ad esempio persistendo i dati nella base dati) prima di rinviare lutente a unaltra pagina (ad esempio una pagina thank you o success). Nota: Reindirizzare un utente dopo aver inviato con successo un form impedisce lutente di essere in grado di premere il tasto aggiorna e re-inviare i dati.

Validare un form Nella sezione precedente, si appreso come un form pu essere inviato con dati validi o invalidi. In Symfony2, la validazione viene applicata alloggetto sottostante (per esempio Task). In altre parole, la questione non se il form valido, ma se loggetto $task valido o meno dopo che al form sono stati applicati i dati inviati. La chiamata di $form->isValid() una scorciatoia che chiede alloggetto $task se ha dati validi o meno. La validazione fatta aggiungendo di una serie di regole (chiamate vincoli) a una classe. Per vederla in azione, verranno aggiunti vincoli di validazione in modo che il campo task non possa essere vuoto e il campo dueDate non possa essere vuoto e debba essere un oggetto DateTime valido. YAML

2.1. Libro

159

Documentazione Symfony, Release 2.0

# Acme/TaskBundle/Resources/config/validation.yml Acme\TaskBundle\Entity\Task: properties: task: - NotBlank: ~ dueDate: - NotBlank: ~ - Type: \DateTime

Annotations
// Acme/TaskBundle/Entity/Task.php use Symfony\Component\Validator\Constraints as Assert; class Task { /** * @Assert\NotBlank() */ public $task; /** * @Assert\NotBlank() * @Assert\Type("\DateTime") */ protected $dueDate; }

XML
<!-- Acme/TaskBundle/Resources/config/validation.xml --> <class name="Acme\TaskBundle\Entity\Task"> <property name="task"> <constraint name="NotBlank" /> </property> <property name="dueDate"> <constraint name="NotBlank" /> <constraint name="Type"> <value>\DateTime</value> </constraint> </property> </class>

PHP
// Acme/TaskBundle/Entity/Task.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Type; class Task { // ... public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint(task, new NotBlank()); $metadata->addPropertyConstraint(dueDate, new NotBlank());

160

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

$metadata->addPropertyConstraint(dueDate, new Type(\DateTime)); } }

Questo tutto! Se si re-invia il form con i dati non validi, si vedranno i rispettivi errori visualizzati nel form. Validazione HTML5 DallHTML5, molti browser possono nativamente imporre alcuni vincoli di validazione sul lato client. La validazione pi comune attivata con la resa di un attributo required sui campi che sono obbligatori. Per i browser che supportano HTML5, questo si tradurr in un messaggio nativo del browser che verr visualizzato se lutente tenta di inviare il form con quel campo vuoto. I form generati traggono il massimo vantaggio di questa nuova funzionalit con laggiunta di appropriati attributi HTML che verichino la convalida. La convalida lato client, tuttavia, pu essere disabilitata aggiungendo lattributo novalidate al tag form o formnovalidate al tag submit. Ci particolarmente utile quando si desidera testare i propri vincoli di convalida lato server, ma viene impedito dal browser, per esempio, inviando campi vuoti. La validazione una caratteristica molto potente di Symfony2 e dispone di un proprio capitolo dedicato.
Gruppi di validatori

Suggerimento: Se non si usano i gruppi di validatori, possibile saltare questa sezione. Se il proprio oggetto si avvale dei gruppi di validatori, si avr bisogno di specicare quelle/i gruppi di convalida deve usare il form:
$form = $this->createFormBuilder($users, array( validation_groups => array(registration), ))->add(...) ;

Se si stanno creando classi per i form (una buona pratica), allora si avr bisogno di aggiungere quanto segue al metodo getDefaultOptions():
public function getDefaultOptions(array $options) { return array( validation_groups => array(registration) ); }

In entrambi i casi, solo il gruppo di validazione registration verr utilizzato per validare loggetto sottostante. Tipi di campo predeniti Symfony dispone di un folto gruppo di tipi di campi che coprono tutti i campi pi comuni e i tipi di dati di cui necessitano i form:
Campi testo

text 2.1. Libro 161

Documentazione Symfony, Release 2.0

textarea email integer money number password percent search url


Campi di scelta

choice entity country language locale timezone


Campi data e ora

date datetime time birthday


Altri campi

checkbox le radio
Gruppi di campi

collection repeated

162

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Campi nascosti

hidden csrf
Campi di base

eld form anche possibile creare dei tipi di campi personalizzati. Questo argomento trattato nellarticolo Come creare un tipo di campo personalizzato di un form del ricettario.
Opzioni dei tipi di campo

Ogni tipo di campo ha un numero di opzioni che pu essere utilizzato per la congurazione. Ad esempio, il campo dueDate attualmente reso con 3 menu select. Tuttavia, il campo data pu essere congurato per essere reso come una singola casella di testo (in cui lutente deve inserire la data nella casella come una stringa):
->add(dueDate, date, array(widget => single_text))

Ogni tipo di campo ha un numero di opzioni differente che possono essere passate a esso. Molte di queste sono speciche per il tipo di campo e i dettagli possono essere trovati nella documentazione di ciascun tipo. Lopzione required Lopzione pi comune lopzione required, che pu essere applicata a qualsiasi campo. Per impostazione predenita, lopzione required impostata a true e questo signica che i browser che interpretano lHTML5 applicheranno la validazione lato client se il campo viene lasciato vuoto. Se non si desidera questo comportamento, impostare lopzione required del campo a false o disabilitare la validazione HTML5. Si noti inoltre che limpostazione dellopzione required a true non far applicare la validazione lato server. In altre parole, se un utente invia un valore vuoto per il campo (sia con un browser vecchio o un servizio web, per esempio), sar accettata come valore valido a meno che si utilizzi il vincolo di validazione NotBlank o NotNull. In altre parole, lopzione required bella, ma la vera validazione lato server dovrebbe sempre essere utilizzata.

2.1. Libro

163

Documentazione Symfony, Release 2.0

Lopzione label La label per il campo del form pu essere impostata con lopzione label, applicabile a qualsiasi campo:
->add(dueDate, date, array( widget => single_text, label => Due Date, ))

La label per un campo pu anche essere impostata nel template che rende il form, vedere sotto.

Indovinare il tipo di campo Ora che sono stati aggiunti i metadati di validazione alla classe Task, Symfony sa gi un po dei campi. Se lo si vuole permettere, Symfony pu indovinare il tipo del campo e impostarlo al posto vostro. In questo esempio, Symfony pu indovinare dalle regole di validazione che il campo task un normale campo text e che il campo dueDate un campo date:
public function newAction() { $task = new Task(); $form = $this->createFormBuilder($task) ->add(task) ->add(dueDate, null, array(widget => single_text)) ->getForm(); }

Questa funzionalit si attiva quando si omette il secondo parametro del metodo add() (o se si passa null a esso). Se si passa un array di opzioni come terzo parametro (fatto sopra per dueDate), queste opzioni vengono applicate al campo indovinato. Attenzione: Se il form utilizza un gruppo specico di validazione, la funzionalit che indovina il tipo di campo prender ancora in considerazione tutti i vincoli di validazione quando andr a indovinare i tipi di campi (compresi i vincoli che non fanno parte del processo di convalida dei gruppi in uso).

Indovinare le opzioni dei tipi di campo

Oltre a indovinare il tipo di un campo, Symfony pu anche provare a indovinare i valori corretti di una serie di opzioni del campo. Suggerimento: Quando queste opzioni vengono impostate, il campo sar reso con speciali attributi HTML che forniscono la validazione HTML5 lato client. Tuttavia, non genera i vincoli equivalenti lato server (ad esempio Assert\MaxLength). E anche se si ha bisogno di aggiungere manualmente la validazione lato server, queste opzioni dei tipi di campo possono essere ricavate da queste informazioni. required: Lopzione required pu essere indovinata in base alle regole di validazione (cio se il campo NotBlank o NotNull) o dai metadati di Doctrine (vale a dire se il campo nullable). Questo molto utile, perch la validazione lato client corrisponder automaticamente alle vostre regole di validazione. min_length: Se il campo un qualche tipo di campo di testo, allora lopzione min_length pu essere indovinata dai vincoli di validazione (se viene utilizzato MinLength o Min) o dai metadati Doctrine (tramite la lunghezza del campo). 164 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

max_length: Similmente a min_length, pu anche essere indovinata la lunghezza massima. Nota: Queste opzioni di campi vengono indovinate solo se si sta usando Symfony per ricavare il tipo di campo (ovvero omettendo o passando null nel secondo parametro di add()). Se si desidera modicare uno dei valori indovinati, possibile sovrascriverlo passando lopzione nellarray di opzioni del campo:
->add(task, null, array(min_length => 4))

Rendere un form in un template Finora si visto come un intero form pu essere reso con una sola linea di codice. Naturalmente, solitamente si ha bisogno di molta pi essibilit: Twig
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} <form action="{{ path(task_new) }}" method="post" {{ form_enctype(form) }}> {{ form_errors(form) }} {{ form_row(form.task) }} {{ form_row(form.dueDate) }} {{ form_rest(form) }} <input type="submit" /> </form>

PHP
<!-- // src/Acme/TaskBundle/Resources/views/Default/newAction.html.php -->

<form action="<?php echo $view[router]->generate(task_new) ?>" method="post" <?php echo $vie <?php echo $view[form]->errors($form) ?> <?php echo $view[form]->row($form[task]) ?> <?php echo $view[form]->row($form[dueDate]) ?> <?php echo $view[form]->rest($form) ?> <input type="submit" /> </form>

Diamo uno sguardo a ogni parte: form_enctype(form) - Se almeno un campo un campo di upload di le, questo inserisce lobbligatorio enctype=multipart/form-data; form_errors(form) - Rende eventuali errori globali per lintero modulo (gli errori specici dei campi vengono visualizzati accanto a ciascun campo); form_row(form.dueDate) - Rende letichetta, eventuali errori e il widget HTML del form per il dato campo (ad esempio dueDate) allinterno, per impostazione predenita, di un elemento div; form_rest(form) - Rende tutti i campi che non sono ancora stati resi. Di solito una buona idea mettere una chiamata a questo helper in fondo a ogni form (nel caso in cui ci si dimenticati di mostrare un campo o

2.1. Libro

165

Documentazione Symfony, Release 2.0

non ci si voglia annoiare a inserire manualmente i campi nascosti). Questo helper utile anche per utilizzare automaticamente i vantaggi della protezione CSRF. La maggior parte del lavoro viene fatto dallhelper form_row, che rende letichetta, gli errori e i widget HTML del form di ogni campo allinterno di un tag div per impostazione predenita. Nella sezione Temi con i form, si apprender come loutput di form_row possa essere personalizzato su diversi levelli. Suggerimento: Si pu accedere ai dati attuali del form tramite form.vars.value: Twig
{{ form.vars.value.task }}

PHP
<?php echo $view[form]->get(value)->getTask() ?>

Rendere manualmente ciascun campo

Lhelper form_row utile perch si pu rendere ciascun campo del form molto facilmente (e il markup utilizzato per la riga pu essere personalizzato come si vuole). Ma poich la vita non sempre cos semplice, anche possibile rendere ogni campo interamente a mano. Il risultato nale del codice che segue lo stesso di quando si utilizzato lhelper form_row: Twig
{{ form_errors(form) }} <div> {{ form_label(form.task) }} {{ form_errors(form.task) }} {{ form_widget(form.task) }} </div> <div> {{ form_label(form.dueDate) }} {{ form_errors(form.dueDate) }} {{ form_widget(form.dueDate) }} </div> {{ form_rest(form) }}

PHP
<?php echo $view[form]->errors($form) ?> <div> <?php echo $view[form]->label($form[task]) ?> <?php echo $view[form]->errors($form[task]) ?> <?php echo $view[form]->widget($form[task]) ?> </div> <div> <?php echo $view[form]->label($form[dueDate]) ?> <?php echo $view[form]->errors($form[dueDate]) ?> <?php echo $view[form]->widget($form[dueDate]) ?> </div>

166

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<?php echo $view[form]->rest($form) ?>

Se letichetta auto-generata di un campo non giusta, si pu specicarla esplicitamente: Twig


{{ form_label(form.task, Task Description) }}

PHP
<?php echo $view[form]->label($form[task], Task Description) ?>

Alcuni tipi di campi hanno opzioni di resa aggiuntive che possono essere passate al widget. Queste opzioni sono documentate con ogni tipo, ma unopzione comune attr, che permette di modicare gli attributi dellelemento form. Di seguito viene aggiunta la classe task_field al resa del campo casella di testo: Twig
{{ form_widget(form.task, { attr: {class: task_field} }) }}

PHP
<?php echo $view[form]->widget($form[task], array( attr => array(class => task_field), )) ?>

Se occorre rendere dei campi a mano, si pu accedere ai singoli valori dei campi, come id, name e label. Per esempio, per ottenere id: Twig
{{ form.task.vars.id }}

PHP
<?php echo $form[task]->get(id) ?>

Per ottenere il valore usato per lattributo nome dei campi del form, occorre usare il valore full_name: Twig
{{ form.task.vars.full_name }}

PHP
<?php echo $form[task]->get(full_name) ?>

Riferimento alle funzioni del template Twig

Se si utilizza Twig, un riferimento completo alle funzioni di resa disponibile nel manuale di riferimento. Leggendolo si pu sapere tutto sugli helper disponibili e le opzioni che possono essere usate con ciascuno di essi. Creare classi per i form Come si visto, un form pu essere creato e utilizzato direttamente in un controllore. Tuttavia, una pratica migliore quella di costruire il form in una apposita classe PHP, che pu essere riutilizzata in qualsiasi punto dellapplicazione. Creare una nuova classe che ospiter la logica per la costruzione del form task:

2.1. Libro

167

Documentazione Symfony, Release 2.0

// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add(task); $builder->add(dueDate, null, array(widget => single_text)); } public function getName() { return task; } }

Questa nuova classe contiene tutte le indicazioni necessarie per creare il form task (notare che il metodo getName() dovrebbe restituire un identicatore univoco per questo tipo di form). Pu essere usato per costruire rapidamente un oggetto form nel controllore:
// src/Acme/TaskBundle/Controller/DefaultController.php // add this new use statement at the top of the class use Acme\TaskBundle\Form\Type\TaskType; public function newAction() { $task = // ... $form = $this->createForm(new TaskType(), $task); // ... }

Porre la logica del form in una propria classe signica che il form pu essere facilmente riutilizzato in altre parti del progetto. Questo il modo migliore per creare form, ma la scelta in ultima analisi, spetta a voi. Impostare data_class Ogni form ha bisogno di sapere il nome della classe che detiene i dati sottostanti (ad esempio Acme\TaskBundle\Entity\Task). Di solito, questo viene indovinato in base alloggetto passato al secondo parametro di createForm (vale a dire $task). Dopo, quando si inizia a incorporare i form, questo non sar pi sufciente. Cos, anche se non sempre necessario, in genere una buona idea specicare esplicitamente lopzione data_class aggiungendo il codice seguente alla classe del tipo di form:
public function getDefaultOptions(array $options) { return array( data_class => Acme\TaskBundle\Entity\Task, ); }

168

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Suggerimento: Quando si mappano form su oggetti, tutti i campi vengono mappati. Ogni campo nel form che non esiste nelloggetto mappato causer il lancio di uneccezione. Nel caso in cui servano campi extra nel form (per esempio, un checkbox accetto i termini), che non saranno mappati nelloggetto sottostante, occorre impostare lopzione property_path a false:
public function buildForm(FormBuilder $builder, array $options) { $builder->add(task); $builder->add(dueDate, null, array(property_path => false)); }

Inoltre, se ci sono campi nel form che non sono inclusi nei dati inviati, tali campi saranno impostati esplicitamente a null.

I form e Doctrine Lobiettivo di un form quello di tradurre i dati da un oggetto (ad esempio Task) a un form HTML e quindi tradurre i dati inviati dallutente indietro alloggetto originale. Come tale, il tema della persistenza delloggetto Task nella base dati del tutto scollegato dal discorso form. Ma, se la classe Task stata congurata per essere salvata attraverso Doctrine (vale a dire che per farlo si aggiunta la mappatura dei meta-dati), allora si pu salvare dopo linvio di un form, quando il form stesso valido:
if ($form->isValid()) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($task); $em->flush(); return $this->redirect($this->generateUrl(task_success)); }

Se, per qualche motivo, non si ha accesso alloggetto originale $task, possibile recuperarlo dal form:
$task = $form->getData();

Per maggiori informazioni, vedere il capitolo ORM Doctrine. La cosa fondamentale da capire che quando il form viene riempito, i dati inviati vengono trasferiti immediatamente alloggetto sottostante. Se si vuole persistere i dati, sufciente persistere loggetto stesso (che gi contiene i dati inviati). Incorporare form Spesso, si vuole costruire form che includono campi provenienti da oggetti diversi. Ad esempio, un form di registrazione pu contenere dati appartenenti a un oggetto User cos come a molti oggetti Address. Fortunatamente, questo semplice e naturale con il componente per i form.
Incorporare un oggetto singolo

Supponiamo che ogni Task appartenga a un semplice oggetto Category. Si parte, naturalmente, con la creazione di un oggetto Category:
// src/Acme/TaskBundle/Entity/Category.php namespace Acme\TaskBundle\Entity;

2.1. Libro

169

Documentazione Symfony, Release 2.0

use Symfony\Component\Validator\Constraints as Assert; class Category { /** * @Assert\NotBlank() */ public $name; }

Poi, aggiungere una nuova propriet category alla classe Task:


// ... class Task { // ... /** * @Assert\Type(type="Acme\TaskBundle\Entity\Category") */ protected $category; // ... public function getCategory() { return $this->category; } public function setCategory(Category $category = null) { $this->category = $category; } }

Ora che lapplicazione stata aggiornata per riettere le nuove esigenze, creare una classe di form in modo che loggetto Category possa essere modicato dallutente:
// src/Acme/TaskBundle/Form/Type/CategoryType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class CategoryType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add(name); } public function getDefaultOptions(array $options) { return array( data_class => Acme\TaskBundle\Entity\Category, ); }

170

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

public function getName() { return category; } }

Lobiettivo nale quello di far si che la Category di un Task possa essere correttamente modicata allinterno dello stesso form task. Per farlo, aggiungere il campo category alloggetto TaskType, il cui tipo unistanza della nuova classe CategoryType:
public function buildForm(FormBuilder $builder, array $options) { // ... $builder->add(category, new CategoryType()); }

I campi di CategoryType ora possono essere resi accanto a quelli della classe TaskType. Rendere i campi di Category allo stesso modo dei campi Task originali: Twig
{# ... #} <h3>Category</h3> <div class="category"> {{ form_row(form.category.name) }} </div> {{ form_rest(form) }} {# ... #}

PHP
<!-- ... --> <h3>Category</h3> <div class="category"> <?php echo $view[form]->row($form[category][name]) ?> </div> <?php echo $view[form]->rest($form) ?> <!-- ... -->

Quando lutente invia il form, i dati inviati con i campi Category sono utilizzati per costruire unistanza di Category, che viene poi impostata sul campo category dellistanza Task. Listanza Category accessibile naturalmente attraverso $task->getCategory() e pu essere memorizzata nella base dati o utilizzata quando serve.
Incorporare un insieme di form

anche possibile incorporare un insieme di form in un form (si immagini un form Category con tanti sotto-form Product. Lo si pu fare utilizzando il tipo di campo collection. Per maggiori informazioni, vedere la ricetta Come unire una collezione di form e il riferimento al tipo collection.

2.1. Libro

171

Documentazione Symfony, Release 2.0

Temi con i form Ogni parte nel modo in cui un form viene reso pu essere personalizzata. Si liberi di cambiare come ogni riga del form viene resa, modicare il markup utilizzato per rendere gli errori, o anche personalizzare la modalit con cui un tag textarea dovrebbe essere rappresentato. Nulla off-limits, e personalizzazioni differenti possono essere utilizzate in posti diversi. Symfony utilizza i template per rendere ogni singola parte di un form, come ad esempio i tag label, i tag input, i messaggi di errore e ogni altra cosa. In Twig, ogni frammento di form rappresentato da un blocco Twig. Per personalizzare una qualunque parte di come un form reso, basta sovrascrivere il blocco appropriato. In PHP, ogni frammento reso tramite un le template individuale. Per personalizzare una qualunque parte del modo in cui un form viene reso, basta sovrascrivere il template esistente creandone uno nuovo. Per capire come funziona, cerchiamo di personalizzare il frammento form_row e aggiungere un attributo class allelemento div che circonda ogni riga. Per farlo, creare un nuovo le template per salvare il nuovo codice: Twig
{# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #} {% block field_row %} {% spaceless %} <div class="form_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endspaceless %} {% endblock field_row %}

PHP
<!-- src/Acme/TaskBundle/Resources/views/Form/field_row.html.php --> <div class="form_row"> <?php echo $view[form]->label($form, $label) ?> <?php echo $view[form]->errors($form) ?> <?php echo $view[form]->widget($form, $parameters) ?> </div>

Il frammento di form field_row utilizzato per rendere la maggior parte dei campi attraverso la funzione form_row. Per dire al componente form di utilizzare il nuovo frammento field_row denito sopra, aggiungere il codice seguente allinizio del template che rende il form: Twig
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} {% form_theme form AcmeTaskBundle:Form:fields.html.twig %} <form ...>

PHP
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php --> <?php $view[form]->setTheme($form, array(AcmeTaskBundle:Form)) ?>

172

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<form ...>

Il tag form_theme (in Twig) importa i frammenti deniti nel dato template e li usa quando deve rendere il form. In altre parole, quando la funzione form_row successivamente chiamata in questo template, utilizzer il blocco field_row dal tema personalizzato (al posto del blocco predenito field_row fornito con Symfony). Per personalizzare una qualsiasi parte di un form, basta sovrascrivere il frammento appropriato. Sapere esattamente qual il blocco o il le da sovrascrivere loggetto della sezione successiva. Per una trattazione pi ampia, vedere Come personalizzare la resa dei form.
Nomi per i frammenti di form

In Symfony, ogni parte di un form che viene reso (elementi HTML del form, errori, etichette, ecc.) denito in un tema base, che in Twig una raccolta di blocchi e in PHP una collezione di le template. In Twig, ogni blocco necessario denito in un singolo le template (form_div_layout.html.twig (https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig)) che vive allinterno di Twig Bridge (https://github.com/symfony/symfony/tree/master/src/Symfony/Bridge/Twig). Dentro questo le, possibile ogni blocco necessario alla resa del form e ogni tipo predenito di campo. In PHP, i frammenti sono le template individuali. Per impostazione predenita sono posizionati nella cartella Resources/views/Form del bundle framework (vedere su GitHub (https://github.com/symfony/symfony/tree/master/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form)). Ogni nome di frammento segue lo stesso schema di base ed suddiviso in due pezzi, separati da un singolo carattere di sottolineatura (_). Alcuni esempi sono: field_row - usato da form_row per rendere la maggior parte dei campi; textarea_widget - usato da form_widget per rendere un campo di tipo textarea; field_errors - usato da form_errors per rendere gli errori di un campo; Ogni frammento segue lo stesso schema di base: type_part. La parte type corrisponde al campo type che viene reso (es. textarea, checkbox, date, ecc) mentre la parte part corrisponde a cosa si sta rendendo (es. label, widget, errors, ecc). Per impostazione predenita, ci sono 4 possibili parti di un form che possono essere rese: label widget errors row (es. (es. (es. (es. field_label) field_widget) field_errors) field_row) rende letichetta dei campi rende la rappresentazione HTML dei campi rende gli errori dei campi rende lintera riga del campo (etichetta, widget ed errori)

Nota: In realt ci sono altre 3 parti (rows, rest e enctype), ma raramente c la necessit di sovrascriverle. Conoscendo il tipo di campo (ad esempio textarea) e che parte si vuole personalizzare (ad esempio widget), si pu costruire il nome del frammento che deve essere sovrascritto (esempio textarea_widget).
Ereditariet dei frammenti di template

In alcuni casi, il frammento che si vuole personalizzare sembrer mancare. Ad esempio, non c nessun frammento textarea_errors nei temi predeniti forniti con Symfony. Quindi dove sono gli errori di un campo textarea che deve essere reso? La risposta : nel frammento field_errors. Quando Symfony rende gli errori per un tipo textarea, prima cerca un frammento textarea_errors, poi cerca un frammento field_errors. Ogni tipo di campo ha un tipo genitore

2.1. Libro

173

Documentazione Symfony, Release 2.0

(il tipo genitore di textarea field) e Symfony utilizza il frammento per il tipo del genitore se il frammento di base non esiste. Quindi, per ignorare gli errori dei soli campi textarea, copiare il frammento field_errors, rinominarlo in textarea_errors e personalizzrlo. Per sovrascrivere la resa degli errori predeniti di tutti i campi, copiare e personalizzare direttamente il frammento field_errors. Suggerimento: Il tipo genitore di ogni tipo di campo disponibile per ogni tipo di campo in form type reference

Temi globali per i form

Nellesempio sopra, stato utilizzato lhelper form_theme (in Twig) per importare i frammenti personalizzati solo in quel form. Si pu anche dire a Symfony di importare personalizzazioni del form nellintero progetto. Twig Per includere automaticamente i blocchi personalizzati del template fields.html.twig creato in precedenza, in tutti i template, modicare il le della congurazione dellapplicazione: YAML
# app/config/config.yml twig: form: resources: - AcmeTaskBundle:Form:fields.html.twig # ...

XML
<!-- app/config/config.xml --> <twig:config ...> <twig:form> <resource>AcmeTaskBundle:Form:fields.html.twig</resource> </twig:form> <!-- ... --> </twig:config>

PHP
// app/config/config.php $container->loadFromExtension(twig, array( form => array(resources => array( AcmeTaskBundle:Form:fields.html.twig, )) // ... ));

Tutti i blocchi allinterno del template fields.html.twig vengono ora utilizzati a livello globale per denire loutput del form.

174

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Personalizzare tutti gli output del form in un singolo le con Twig Con Twig, si pu anche personalizzare il blocco di un form allinterno del template in cui questa personalizzazione necessaria:
{% extends ::base.html.twig %} {# importa "_self" come tema del form #} {% form_theme form _self %} {# personalizza il frammento del form #} {% block field_row %} {# output personalizzato della riga del campo #} {% endblock field_row %} {% block content %} {# ... #} {{ form_row(form.task) }} {% endblock %}

Il tag {% form_theme form _self %} consente ai blocchi del form di essere personalizzati direttamente allinterno del template che utilizzer tali personalizzazioni. Utilizzare questo metodo per creare velocemente personalizzazioni del form che saranno utilizzate solo in un singolo template.

PHP Per includere automaticamente i template personalizzati dalla cartella Acme/TaskBundle/Resources/views/Form creata in precedenza in tutti i template, modicare il le con la congurazione dellapplicazione: YAML
# app/config/config.yml framework: templating: form: resources: - AcmeTaskBundle:Form # ...

XML
<!-- app/config/config.xml --> <framework:config ...> <framework:templating> <framework:form> <resource>AcmeTaskBundle:Form</resource> </framework:form> </framework:templating> <!-- ... --> </framework:config>

PHP
// app/config/config.php $container->loadFromExtension(framework, array(

2.1. Libro

175

Documentazione Symfony, Release 2.0

templating => array(form => array(resources => array( AcmeTaskBundle:Form, ))) // ... ));

Ogni frammento allinterno della cartella Acme/TaskBundle/Resources/views/Form ora usato globalmente per denire loutput del form. Protezione da CSRF CSRF, o Cross-site request forgery (http://it.wikipedia.org/wiki/Cross-site_request_forgery), un metodo mediante il quale un utente malintenzionato cerca di fare inviare inconsapevolmente agli utenti legittimi dati che non intendono far conoscere. Fortunatamente, gli attacchi CSRF possono essere prevenuti utilizzando un token CSRF allinterno dei form. La buona notizia che, per impostazione predenita, Symfony integra e convalida i token CSRF automaticamente. Questo signica che possibile usufruire della protezione CSRF senza far nulla. Infatti, ogni form di questo capitolo sfrutta la protezione CSRF! La protezione CSRF funziona con laggiunta al form di un campo nascosto, chiamato per impostazione predenita _token, che contiene un valore che solo sviluppatore e utente conoscono. Questo garantisce che proprio lutente, non qualcun altro, stia inviando i dati. Symfony valida automaticamente la presenza e lesattezza di questo token. Il campo _token un campo nascosto e sar reso automaticamente se si include la funzione form_rest() nel template, perch questa assicura che tutti i campi non resi vengano visualizzati. Il token CSRF pu essere personalizzato specicatamente per ciascun form. Ad esempio:
class TaskType extends AbstractType { // ... public function getDefaultOptions(array $options) { return array( data_class => Acme\TaskBundle\Entity\Task, csrf_protection => true, csrf_field_name => _token, // una chiave univoca per generare il token intention => task_item, ); } // ... }

Per disabilitare la protezione CSRF, impostare lopzione csrf_protection a false. Le personalizzazioni possono essere fatte anche a livello globale nel progetto. Per ulteriori informazioni, vedere la sezione riferimento della congurazione dei form. Nota: Lopzione intention opzionale, ma migliora notevolmente la sicurezza del token generato, rendendolo diverso per ogni modulo.

176

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Usare un form senza una classe Nella maggior parte dei casi, un form legato a un oggetto e i campi del form prendono i loro dati dalle propriet di tale oggetto. Questo quanto visto nora in questo capitolo, con la classe Task. A volte, per, si vuole solo usare un form senza classi, per ottenere un array di dati inseriti. Lo si pu fare in modo molto facile:
// assicurarsi di aver importato lo spazio dei nomi Request allinizio della classe use Symfony\Component\HttpFoundation\Request // ... public function contactAction(Request $request) { $defaultData = array(message => Type your message here); $form = $this->createFormBuilder($defaultData) ->add(name, text) ->add(email, email) ->add(message, textarea) ->getForm(); if ($request->getMethod() == POST) { $form->bindRequest($request); // data is an array with "name", "email", and "message" keys $data = $form->getData(); } // ... rendere il form }

Per impostazione predenita, un form ipotizza che si voglia lavorare con array di dati, invece che con oggetti. Ci sono due modi per modicare questo comportamento e legare un form a un oggetto: 1. Passare un oggetto alla creazione del form (come primo parametro di createFormBuilder o come secondo parametro di createForm); 2. Dichiarare lopzione data_class nel form. Se non si fa nessuna di queste due cose, il form restituir i dati come array. In questo esempio, poich $defaultData non un oggetto (e lopzione data_class omessa), $form->getData() restituir un array. Suggerimento: Si pu anche accedere ai valori POST (name, in questo caso) direttamente tramite loggetto Request, in questo modo:
$this->get(request)->request->get(name);

Tuttavia, si faccia attenzione che in molti casi luso del metodo getData() preferibile, poich restituisce i dati (solitamente un oggetto) dopo che sono stati manipolati dal sistema dei form.

Aggiungere la validazione

Lultima parte mancante la validazione. Solitamente, quando si richiama $form->isValid(), loggetto viene validato dalla lettura dei vincoli applicati alla classe. Ma senza una classe, come si possono aggiungere vincoli ai dati del form?

2.1. Libro

177

Documentazione Symfony, Release 2.0

La risposta : impostare i vincoli in modo autonomo e passarli al proprio form. Lapproccio generale spiegato meglio nel capitolo sulla validazione, ma ecco un breve esempio:
// importare gli spazi dei nomi allinizio della classe use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\MinLength; use Symfony\Component\Validator\Constraints\Collection; $collectionConstraint = new Collection(array( name => new MinLength(5), email => new Email(array(message => Invalid email address)), )); // creare un form, senza valori predefiniti, e passarlo allopzione constraint $form = $this->createFormBuilder(null, array( validation_constraint => $collectionConstraint, ))->add(email, email) // ... ;

Ora, richiamando $form->bindRequest($request), i vincoli impostati sono eseguiti sui dati del form. Se si usa una classe form, sovrascrivere il metodo getDefaultOptions per specicare lopzione:
namespace Acme\TaskBundle\Form\Type; use use use use use Symfony\Component\Form\AbstractType; Symfony\Component\Form\FormBuilder; Symfony\Component\Validator\Constraints\Email; Symfony\Component\Validator\Constraints\MinLength; Symfony\Component\Validator\Constraints\Collection;

class ContactType extends AbstractType { // ... public function getDefaultOptions(array $options) { $collectionConstraint = new Collection(array( name => new MinLength(5), email => new Email(array(message => Invalid email address)), )); return array(validation_constraint => $collectionConstraint); } }

Si possiede ora la essibilit di creare form, con validazione, che restituiscano array di dati, invece di oggetti. In molti casi, meglio (e sicuramente pi robusto) legare il form a un oggetto. Ma questo un bellapproccio per form pi semplici. Considerazioni nali Ora si a conoscenza di tutti i mattoni necessari per costruire form complessi e funzionali per la propria applicazione. Quando si costruiscono form, bisogna tenere presente che il primo gol di un form quello di tradurre i dati da un oggetto (Task) a un form HTML in modo che lutente possa modicare i dati. Il secondo obiettivo di un form quello di prendere i dati inviati dallutente e ri-applicarli alloggetto.

178

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Ci sono altre cose da imparare sul potente mondo dei form, ad esempio come gestire il caricamento di le con Doctrine o come creare un form dove un numero dinamico di sub-form possono essere aggiunti (ad esempio una todo list in cui possibile continuare ad aggiungere pi campi tramite Javascript prima di inviare). Vedere il ricettario per questi argomenti. Inoltre, assicurarsi di basarsi sulla documentazione di riferimento sui tipi di campo, che comprende esempi di come usare ogni tipo di campo e le relative opzioni. Saperne di pi con il ricettario Come gestire il caricamento di le con Doctrine Riferimento del tipo di campo le Creare tipi di campo personalizzati Come personalizzare la resa dei form Come generare dinamicamente form usando gli eventi form Utilizzare i data transformer

2.1.12 Sicurezza
La sicurezza una procedura che avviene in due fasi, il cui obiettivo quello di impedire a un utente di accedere a una risorsa a cui non dovrebbe avere accesso. Nella prima fase del processo, il sistema di sicurezza identica chi lutente, chiedendogli di presentare una sorta di identicazione. Questultima chiamata autenticazione e signica che il sistema sta cercando di scoprire chi sei. Una volta che il sistema sa chi sei, il passo successivo quello di determinare se dovresti avere accesso a una determinata risorsa. Questa parte del processo chiamato autorizzazione e signica che il sistema verica se disponi dei privilegi per eseguire una certa azione.

Il modo migliore per imparare quello di vedere un esempio, vediamolo subito.

2.1. Libro

179

Documentazione Symfony, Release 2.0

Nota: Il componente della sicurezza (https://github.com/symfony/Security) di Symfony disponibile come libreria PHP a s stante, per lutilizzo allinterno di qualsiasi progetto PHP.

Esempio di base: lautenticazione HTTP Il componente della sicurezza pu essere congurato attraverso la congurazione dellapplicazione. In realt, per molte congurazioni standard di sicurezza basta solo usare la giusta congurazione. La seguente congurazione dice a Symfony di proteggere qualunque URL corrispondente a /admin/* e chiedere le credenziali allutente utilizzando lautenticazione base HTTP (cio il classico vecchio box nome utente/password): YAML
# app/config/security.yml security: firewalls: secured_area: pattern: ^/ anonymous: ~ http_basic: realm: "Area demo protetta" access_control: - { path: ^/admin, roles: ROLE_ADMIN } providers: in_memory: users: ryan: { password: ryanpass, roles: ROLE_USER } admin: { password: kitten, roles: ROLE_ADMIN } encoders: Symfony\Component\Security\Core\User\User: plaintext

XML

<!-- app/config/security.xml --> <srv:container xmlns="http://symfony.com/schema/dic/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ser <config> <firewall name="secured_area" pattern="^/"> <anonymous /> <http-basic realm="Area demo protetta" /> </firewall> <access-control> <rule path="^/admin" role="ROLE_ADMIN" /> </access-control> <provider name="in_memory"> <user name="ryan" password="ryanpass" roles="ROLE_USER" /> <user name="admin" password="kitten" roles="ROLE_ADMIN" /> </provider>

180

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<encoder class="Symfony\Component\Security\Core\User\User" algorithm="plaintext" /> </config> </srv:container>

PHP
// app/config/security.php $container->loadFromExtension(security, array( firewalls => array( secured_area => array( pattern => ^/, anonymous => array(), http_basic => array( realm => Area demo protetta, ), ), ), access_control => array( array(path => ^/admin, role => ROLE_ADMIN), ), providers => array( in_memory => array( users => array( ryan => array(password => ryanpass, roles => ROLE_USER), admin => array(password => kitten, roles => ROLE_ADMIN), ), ), ), encoders => array( Symfony\Component\Security\Core\User\User => plaintext, ), ));

Suggerimento: Una distribuzione standard di Symfony pone la congurazione di sicurezza in un le separato (ad esempio app/config/security.yml). Se non si ha un le di sicurezza separato, possibile inserire la congurazione direttamente nel le di congurazione principale (ad esempio app/config/config.yml). Il risultato nale di questa congurazione un sistema di sicurezza pienamente funzionale, simile al seguente: Ci sono due utenti nel sistema (ryan e admin); Gli utenti si autenticano tramite autenticazione HTTP; Qualsiasi URL corrispondente a /admin/* protetto e solo lutente admin pu accedervi; Tutti gli URL che non corrispondono ad /admin/* sono accessibili da tutti gli utenti (e allutente non viene chiesto il login). Di seguito si vedr brevemente come funziona la sicurezza e come ogni parte della congurazione entra in gioco. Come funziona la sicurezza: autenticazione e autorizzazione Il sistema di sicurezza di Symfony funziona determinando lidentit di un utente (autenticazione) e poi controllando se lutente deve avere accesso a una risorsa specica o URL.

2.1. Libro

181

Documentazione Symfony, Release 2.0

Firewall (autenticazione)

Quando un utente effettua una richiesta a un URL che protetta da un rewall, viene attivato il sistema di sicurezza. Il compito del rewall quello di determinare se lutente deve o non deve essere autenticato e se deve autenticarsi, rimandare una risposta allutente, avviando il processo di autenticazione. Un rewall viene attivato quando lURL di una richiesta in arrivo corrisponde al valore pattern dellespressione regolare del rewall congurato. In questo esempio, pattern (^/) corrisponder a ogni richiesta in arrivo. Il fatto che il rewall venga attivato non signica tuttavia che venga visualizzato il box di autenticazione con nome utente e password per ogni URL. Per esempio, qualunque utente pu accedere a /foo senza che venga richiesto di autenticarsi.

Questo funziona in primo luogo perch il rewall consente utenti anonimi, attraverso il parametro di congurazione anonymous. In altre parole, il rewall non richiede allutente di fare immediatamente unautenticazione. E poich non necessario nessun ruolo speciale per accedere a /foo (sotto la sezione access_control), la richiesta pu essere soddisfatta senza mai chiedere allutente di autenticarsi. Se si rimuove la chiave anonymous, il rewall chieder sempre lautenticazione allutente.
Controlli sullaccesso (autorizzazione)

Se un utente richiede /admin/foo, il processo ha un diverso comportamento. Questo perch la sezione di congurazione access_control dice che qualsiasi URL che corrispondono allo schema dellespressione regolare ^/admin (cio /admin o qualunque URL del tipo /admin/*) richiede il ruolo ROLE_ADMIN. I ruoli sono la base per la maggior parte delle autorizzazioni: un utente pu accedere /admin/foo solo se ha il ruolo ROLE_ADMIN.

182

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Come prima, quando lutente effettua inizialmente la richiesta, il rewall non chiede nessuna identicazione. Tuttavia, non appena il livello di controllo di accesso nega laccesso allutente (perch lutente anonimo non ha il ruolo ROLE_ADMIN), il rewall entra in azione e avvia il processo di autenticazione. Il processo di autenticazione dipende dal meccanismo di autenticazione in uso. Per esempio, se si sta utilizzando il metodo di autenticazione tramite form di login, lutente verr rinviato alla pagina di login. Se si utilizza lautenticazione HTTP, allutente sar inviata una risposta HTTP 401 e verr visualizzato una nestra del browser con nome utente e password. Ora lutente ha la possibilit di inviare le credenziali allapplicazione. Se le credenziali sono valide, pu essere riprovata la richiesta originale.

2.1. Libro

183

Documentazione Symfony, Release 2.0

In questo esempio, lutente ryan viene autenticato con successo con il rewall. Ma poich ryan non ha il ruolo ROLE_ADMIN, viene ancora negato laccesso a /admin/foo. In denitiva, questo signica che lutente vedr un qualche messaggio che indica che laccesso stato negato. Suggerimento: Quando Symfony nega laccesso allutente, lutente vedr una schermata di errore e ricever un codice di stato HTTP 403 (Forbidden). possibile personalizzare la schermata di errore di accesso negato seguendo le istruzioni sulle pagine di errore presenti nel ricettario per personalizzare la pagina di errore 403. Inne, se lutente admin richiede /admin/foo, avviene un processo simile, solo che adesso, dopo essere stato autenticato, il livello di controllo di accesso lascer passare la richiesta:

184

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Il usso di richiesta quando un utente richiede una risorsa protetta semplice, ma incredibilmente essibile. Come si vedr in seguito, lautenticazione pu essere gestita in molti modi, come un form di login, un certicato X.509, o da unautenticazione dellutente tramite Twitter. Indipendentemente dal metodo di autenticazione, il usso di richiesta sempre lo stesso: 1. Un utente accede a una risorsa protetta; 2. Lapplicazione rinvia lutente al form di login; 3. Lutente invia le proprie credenziali (ad esempio nome utente / password); 4. Il rewall autentica lutente; 5. Lutente autenticato riprova la richiesta originale. Nota: Lesatto processo in realt dipende un po da quale meccanismo di autenticazione si sta usando. Per esempio, quando si utilizza il form di login, lutente invia le sue credenziali a un URL che elabora il form (ad esempio /login_check) e poi viene rinviato allURL originariamente richiesto (ad esempio /admin/foo). Ma con lautenticazione HTTP, lutente invia le proprie credenziali direttamente allURL originale (ad esempio /admin/foo) e poi la pagina viene restituita allutente nella stessa richiesta (cio senza rinvio). Questo tipo di idiosincrasie non dovrebbe causare alcun problema, ma bene tenerle a mente.

Suggerimento: Pi avanti si imparer che in Symfony2 qualunque cosa pu essere protetto, tra cui controllori specici, oggetti, o anche metodi PHP.

2.1. Libro

185

Documentazione Symfony, Release 2.0

Utilizzo di un form di login tradizionale Finora, si visto come proteggere lapplicazione con un rewall e poi proteggere laccesso a determinate aree tramite i ruoli. Utilizzando lautenticazione HTTP, si pu sfruttare senza fatica il box nativo nome utente/password offerti da tutti i browser. Tuttavia, Symfony supporta nativamente molti meccanismi di autenticazione. Per i dettagli su ciascuno di essi, vedere il Riferimento sulla congurazione di sicurezza. In questa sezione, si potr proseguire lapprendimento, consentendo allutente di autenticarsi attraverso un tradizionale form di login HTML. In primo luogo, abilitare il form di login sotto il rewall: YAML
# app/config/security.yml security: firewalls: secured_area: pattern: ^/ anonymous: ~ form_login: login_path: check_path:

/login /login_check

XML

<!-- app/config/security.xml --> <srv:container xmlns="http://symfony.com/schema/dic/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:srv="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ser <config> <firewall name="secured_area" pattern="^/"> <anonymous /> <form-login login_path="/login" check_path="/login_check" /> </firewall> </config> </srv:container>

PHP
// app/config/security.php $container->loadFromExtension(security, array( firewalls => array( secured_area => array( pattern => ^/, anonymous => array(), form_login => array( login_path => /login, check_path => /login_check, ), ), ), ));

Suggerimento: Se non necessario personalizzare i valori login_path o check_path (i valori usati qui sono i valori predeniti), possibile accorciare la congurazione: YAML 186 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

form_login: ~

XML
<form-login />

PHP
form_login => array(),

Ora, quando il sistema di sicurezza inizia il processo di autenticazione, rinvier lutente al form di login (/login per impostazione predenita). Implementare visivamente il form di login compito dello sviluppatore. In primo luogo, bisogna creare due rotte: una che visualizzer il form di login (cio /login) e unaltra che gestir linvio del form di login (ad esempio /login_check): YAML
# app/config/routing.yml login: pattern: /login defaults: { _controller: AcmeSecurityBundle:Security:login } login_check: pattern: /login_check

XML
<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="login" pattern="/login"> <default key="_controller">AcmeSecurityBundle:Security:login</default> </route> <route id="login_check" pattern="/login_check" /> </routes>

PHP
// app/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(login, new Route(/login, array( _controller => AcmeDemoBundle:Security:login, ))); $collection->add(login_check, new Route(/login_check, array())); return $collection;

Nota: Non necessario implementare un controllore per lURL /login_check perch il rewall catturer ed elaborer qualunque form inviato a questo URL. facoltativo, ma utile, creare una rotta, in modo che la si possa usare per generare lURL di invio del form nel template del login.

2.1. Libro

187

Documentazione Symfony, Release 2.0

Notare che il nome della rotta login non importante. Quello che importante che lURL della rotta (/login) corrisponda al valore di congurazione login_path, in quanto l che il sistema di sicurezza rinvier gli utenti che necessitano di effettuare il login. Successivamente, creare il controllore che visualizzer il form di login:
// src/Acme/SecurityBundle/Controller/Main; namespace Acme\SecurityBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Security\Core\SecurityContext; class SecurityController extends Controller { public function loginAction() { $request = $this->getRequest(); $session = $request->getSession(); // verifica di eventuali errori if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR); } else { $error = $session->get(SecurityContext::AUTHENTICATION_ERROR); } return $this->render(AcmeSecurityBundle:Security:login.html.twig, array( // ultimo nome utente inserito last_username => $session->get(SecurityContext::LAST_USERNAME), error => $error, )); } }

Non bisogna farsi confondere da questo controllore. Come si vedr a momenti, quando lutente compila il form, il sistema di sicurezza lo gestisce automaticamente. Se lutente ha inviato un nome utente o una password non validi, questo controllore legge lerrore di invio del form dal sistema di sicurezza, in modo che possano essere visualizzati allutente. In altre parole, il vostro compito quello di visualizzare il form di login e gli eventuali errori di login che potrebbero essersi vericati, ma il sistema di sicurezza stesso che si prende cura di vericare il nome utente e la password inviati e di autenticare lutente. Inne, creare il template corrispondente: Twig
{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} {% if error %} <div>{{ error.message }}</div> {% endif %} <form action="{{ path(login_check) }}" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="{{ last_username }}" /> <label for="password">Password:</label> <input type="password" id="password" name="_password" /> {#

188

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Se si desidera controllare lURL a cui lutente viene rinviato in caso di successo (magg <input type="hidden" name="_target_path" value="/account" /> #} <input type="submit" name="login" /> </form>

PHP
<?php // src/Acme/SecurityBundle/Resources/views/Security/login.html.php ?> <?php if ($error): ?> <div><?php echo $error->getMessage() ?></div> <?php endif; ?> <form action="<?php echo $view[router]->generate(login_check) ?>" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="<?php echo $last_username ?>" /> <label for="password">Password:</label> <input type="password" id="password" name="_password" />

<!-Se si desidera controllare lURL a cui lutente viene rinviato in caso di successo (magg <input type="hidden" name="_target_path" value="/account" /> --> <input type="submit" name="login" /> </form>

Suggerimento: La variabile error passata nel template Symfony\Component\Security\Core\Exception\AuthenticationException. contenere informazioni, anche sensibili, sullerrore di autenticazione: va quindi usata con cautela.

unistanza di Potrebbe

Il form ha pochi requisiti. In primo luogo, inviando il form a /login_check (tramite la rotta login_check), il sistema di sicurezza intercetter linvio del form e lo processer automaticamente. In secondo luogo, il sistema di sicurezza si aspetta che i campi inviati siano chiamati _username e _password (questi nomi di campi possono essere congurati). E questo tutto! Quando si invia il form, il sistema di sicurezza controller automaticamente le credenziali dellutente e autenticher lutente o rimander lutente al form di login, dove sono visualizzati gli errori. Rivediamo lintero processo: 1. Lutente prova ad accedere a una risorsa protetta; 2. Il rewall avvia il processo di autenticazione rinviando lutente al form di login (/login); 3. La pagina /login rende il form di login, attraverso la rotta e il controllore creato in questo esempio; 4. Lutente invia il form di login /login_check; 5. Il sistema di sicurezza intercetta la richiesta, verica le credenziali inviate dallutente, autentica lutente se sono corrette e, se non lo sono, lo rinvia al form di login. Per impostazione predenita, se le credenziali inviate sono corrette, lutente verr rinviato alla pagina originale che stata richiesta (ad esempio /admin/foo). Se lutente originariamente andato direttamente alla pagina di login, sar rinviato alla pagina iniziale. Questo comportamento pu essere personalizzato, consentendo, ad esempio, di rinviare lutente a un URL specico.

2.1. Libro

189

Documentazione Symfony, Release 2.0

Per maggiori dettagli su questo e su come personalizzare in generale il processo di login con il form, vedere Come personalizzare il form di login.

190

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Come evitare gli errori pi comuni Quando si imposta il proprio form di login, bisogna fare attenzione a non incorrere in alcuni errori comuni. 1. Creare le rotte giuste In primo luogo, essere sicuri di aver denito correttamente le rotte /login e /login_check e che corrispondano ai valori di congurazione login_path e check_path. Un errore di congurazione qui pu signicare che si viene rinviati a una pagina 404 invece che nella pagina di login, o che inviando il form di login non succede nulla (continuando a vedere sempre il form di login). 2. Assicurarsi che la pagina di login non sia protetta Inoltre, bisogna assicurarsi che la pagina di login non richieda nessun ruolo per essere visualizzata. Per esempio, la seguente congurazione, che richiede il ruolo ROLE_ADMIN per tutti gli URL (includendo lURL /login), causer un loop di redirect: YAML
access_control: - { path: ^/, roles: ROLE_ADMIN }

XML
<access-control> <rule path="^/" role="ROLE_ADMIN" /> </access-control>

PHP
access_control => array( array(path => ^/, role => ROLE_ADMIN), ),

Rimuovendo il controllo degli accessi sullURL /login il problema si risolve: YAML


access_control: - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/, roles: ROLE_ADMIN }

XML
<access-control> <rule path="^/login" role="IS_AUTHENTICATED_ANONYMOUSLY" /> <rule path="^/" role="ROLE_ADMIN" /> </access-control>

PHP
access_control => array( array(path => ^/login, role => IS_AUTHENTICATED_ANONYMOUSLY), array(path => ^/, role => ROLE_ADMIN), ),

Inoltre, se il rewall non consente utenti anonimi, sar necessario creare un rewall speciale, che consenta agli utenti anonimi la pagina di login: YAML
firewalls: login_firewall: pattern: anonymous: secured_area: pattern: form_login:

^/login$ ~ ^/ ~

XML
<firewall name="login_firewall" pattern="^/login$"> <anonymous /> </firewall> 2.1. Libro <firewall name="secured_area" pattern="^/"> <form_login /> </firewall>

191

Documentazione Symfony, Release 2.0

Autorizzazione Il primo passo per la sicurezza sempre lautenticazione: il processo di vericare lidentit dellutente. Con Symfony, lautenticazione pu essere fatta in qualunque modo, attraverso un form di login, autenticazione HTTP o anche tramite Facebook. Una volta che lutente stato autenticato, lautorizzazione ha inizio. Lautorizzazione fornisce un metodo standard e potente per decidere se un utente pu accedere a una qualche risorsa (un URL, un oggetto del modello, una chiamata a metodo, ...). Questo funziona tramite lassegnazione di specici ruoli a ciascun utente e quindi richiedendo ruoli diversi per differenti risorse. Il processo di autorizzazione ha due diversi lati: 1. Lutente ha un insieme specico di ruoli; 2. Una risorsa richiede un ruolo specico per poter accedervi. In questa sezione, ci si concentrer su come proteggere risorse diverse (ad esempio gli URL, le chiamate a metodi, ecc) con ruoli diversi. Pi avanti, si imparer di pi su come i ruoli sono creati e assegnati agli utenti.
Protezione di specici schemi di URL

Il modo pi semplice per proteggere parte dellapplicazione quello di proteggere un intero schema di URL. Si gi visto questo nel primo esempio di questo capitolo, dove tutto ci a cui corrisponde lo schema di espressione regolare ^/admin richiede il ruolo ROLE_ADMIN. possibile denire tanti schemi di URL quanti ne occorrono, ciascuno unespressione regolare. YAML
# app/config/security.yml security: # ... access_control: - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN } - { path: ^/admin, roles: ROLE_ADMIN }

XML
<!-- app/config/security.xml --> <config> <!-- ... --> <rule path="^/admin/users" role="ROLE_SUPER_ADMIN" /> <rule path="^/admin" role="ROLE_ADMIN" /> </config>

PHP
// app/config/security.php $container->loadFromExtension(security, array( // ... access_control => array( array(path => ^/admin/users, role => ROLE_SUPER_ADMIN), array(path => ^/admin, role => ROLE_ADMIN), ), ));

192

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Suggerimento: Anteporre il percorso con il simbolo ^ assicura che corrispondano solo gli URL che iniziano con lo schema. Per esempio, un semplice percorso /admin (senza simbolo ^) corrisponderebbe correttamente a /admin/foo, ma corrisponderebbe anche a URL come /foo/admin. Per ogni richiesta in arrivo, Symfony2 cerca di trovare una regola per il controllo dellaccesso che corrisponde (la prima vince). Se lutente non ancora autenticato, viene avviato il processo di autenticazione (cio viene data allutente la possibilit di fare login). Tuttavia, se lutente autenticato ma non ha il ruolo richiesto, viene lanciata uneccezione Symfony\Component\Security\Core\Exception\AccessDeniedException, che possibile gestire e trasformare in una simpatica pagina di errore accesso negato per lutente. Vedere Come personalizzare le pagine di errore per maggiori informazioni. Poich Symfony utilizza la prima regola di controllo accesso trovata, un URL del tipo /admin/users/new corrisponder alla prima regola e richieder solo il ruolo ROLE_SUPER_ADMIN. Qualunque URL tipo /admin/blog corrisponder alla seconda regola e richieder ROLE_ADMIN.
Protezione tramite IP

In certe situazioni pu succedere di limitare laccesso a una data rotta basata su IP. Questo particolarmente rilevante nel caso di Edge Side Includes (ESI), per esempio, che utilizzano una rotta chiamata _internal. Quando viene utilizzato ESI, richiesta la rotta interna dal gateway della cache per abilitare diverse opzioni di cache per le sottosezioni allinterno di una determinata pagina. Queste rotte fornite con il presso ^/_internal per impostazione predenita nelledizione standard di Symfony (assumendo di aver scommentato queste linee dal le delle rotte). Ecco un esempio di come si possa garantire questa rotta da intrusioni esterne: YAML
# app/config/security.yml security: # ... access_control: - { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }

XML
<access-control> <rule path="^/_internal" role="IS_AUTHENTICATED_ANONYMOUSLY" ip="127.0.0.1" /> </access-control>

PHP

access_control => array( array(path => ^/_internal, role => IS_AUTHENTICATED_ANONYMOUSLY, ip => 127.0.0.1 ),

Protezione tramite canale

Molto simile alla sicurezza basata su IP, richiedere luso di SSL semplice, basta aggiungere la voce access_control: YAML
# app/config/security.yml security: # ... access_control: - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https

2.1. Libro

193

Documentazione Symfony, Release 2.0

XML

<access-control> <rule path="^/cart/checkout" role="IS_AUTHENTICATED_ANONYMOUSLY" requires_channel="https" /> </access-control>

PHP

access_control => array( array(path => ^/cart/checkout, role => IS_AUTHENTICATED_ANONYMOUSLY, requires_chann ),

Proteggere un controllore

Proteggere lapplicazione basandosi su schemi di URL semplice, ma in alcuni casi pu non essere abbastanza granulare. Quando necessario, si pu facilmente forzare lautorizzazione dallinterno di un controllore:
use Symfony\Component\Security\Core\Exception\AccessDeniedException; // ... public function helloAction($name) { if (false === $this->get(security.context)->isGranted(ROLE_ADMIN)) { throw new AccessDeniedException(); } // ... }

anche possibile scegliere di installare e utilizzare lopzionale JMSSecurityExtraBundle, che pu proteggere il controllore utilizzando le annotazioni:
use JMS\SecurityExtraBundle\Annotation\Secure; /** * @Secure(roles="ROLE_ADMIN") */ public function helloAction($name) { // ... }

Per maggiori informazioni, vedere la documentazione di JMSSecurityExtraBundle (https://github.com/schmittjoh/JMSSecurityExtraBundle). Se si sta utilizzando la distribuzione standard di Symfony, questo bundle disponibile per impostazione predenita. In caso contrario, si pu facilmente scaricare e installare.
Protezione degli altri servizi

In realt, con Symfony si pu proteggere qualunque cosa, utilizzando una strategia simile a quella vista nella sezione precedente. Per esempio, si supponga di avere un servizio (ovvero una classe PHP) il cui compito quello di inviare email da un utente allaltro. possibile limitare luso di questa classe, non importa dove stata utilizzata, per gli utenti che hanno un ruolo specico. Per ulteriori informazioni su come utilizzare il componente della sicurezza per proteggere servizi e metodi diversi nellapplicazione, vedere Proteggere servizi e metodi di unapplicazione.

194

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Access Control List (ACL): protezione dei singoli oggetti del database

Si immagini di progettare un sistema di blog, in cui gli utenti possono commentare i messaggi. Si vuole che un utente possa modicare i propri commenti, ma non quelli degli altri. Inoltre, come utente admin, si vuole essere in grado di modicare tutti i commenti. Il componente della sicurezza viene fornito con un sistema opzionale di access control list (ACL), che possibile utilizzare quando necessario controllare laccesso alle singole istanze di un oggetto nel sistema. Senza ACL, possibile proteggere il sistema in modo che solo certi utenti possono modicare i commenti sui blog. Ma con ACL, si pu limitare o consentire laccesso commento per commento. Per maggiori informazioni, vedere larticolo del ricettario: Access Control List (ACL). Utenti Nelle sezioni precedenti, si appreso come sia possibile proteggere diverse risorse, richiedendo una serie di ruoli per una risorsa. In questa sezione, esploreremo laltro lato delle autorizzazioni: gli utenti.
Da dove provengono utenti? (User Provider)

Durante lautenticazione, lutente invia un insieme di credenziali (di solito un nome utente e una password). Il compito del sistema di autenticazione quello di soddisfare queste credenziali con linsieme degli utenti. Quindi da dove proviene questa lista di utenti? In Symfony2, gli utenti possono arrivare da qualsiasi parte: un le di congurazione, una tabella di un database, un servizio web o qualsiasi altra cosa si pu pensare. Qualsiasi cosa che prevede uno o pi utenti nel sistema di autenticazione noto come fornitore di utenti. Symfony2 viene fornito con i due fornitori utenti pi diffusi; uno che carica gli utenti da un le di congurazione e uno che carica gli utenti da una tabella di un database. Denizione degli utenti in un le di congurazione Il modo pi semplice per specicare gli utenti direttamente in un le di congurazione. In effetti, questo si gi aver visto nellesempio di questo capitolo. YAML
# app/config/security.yml security: # ... providers: default_provider: users: ryan: { password: ryanpass, roles: ROLE_USER } admin: { password: kitten, roles: ROLE_ADMIN }

XML
<!-- app/config/security.xml --> <config> <!-- ... --> <provider name="default_provider"> <user name="ryan" password="ryanpass" roles="ROLE_USER" /> <user name="admin" password="kitten" roles="ROLE_ADMIN" /> </provider> </config>

PHP

2.1. Libro

195

Documentazione Symfony, Release 2.0

// app/config/security.php $container->loadFromExtension(security, array( // ... providers => array( default_provider => array( users => array( ryan => array(password => ryanpass, roles => ROLE_USER), admin => array(password => kitten, roles => ROLE_ADMIN), ), ), ), ));

Questo fornitore utenti chiamato in-memory , dal momento che gli utenti non sono memorizzati in un database. Loggetto utente effettivo fornito da Symfony (Symfony\Component\Security\Core\User\User). Suggerimento: Qualsiasi fornitore utenti pu caricare gli utenti direttamente dalla congurazione, specicando il parametro di congurazione users ed elencando gli utenti sotto di esso. Attenzione: Se il nome utente completamente numerico (ad esempio 77) o contiene un trattino (ad esempio user-name), consigliabile utilizzare la seguente sintassi alternativa quando si specicano utenti in YAML:
users: - { name: 77, password: pass, roles: ROLE_USER } - { name: user-name, password: pass, roles: ROLE_USER }

Per i siti pi piccoli, questo metodo semplice e veloce da congurare. Per sistemi pi complessi, si consiglia di caricare gli utenti dal database. Caricare gli utenti da un database Se si vuole caricare gli utenti tramite lORM Doctrine, si pu farlo facilmente attraverso la creazione di una classe User e congurando il fornitore entity. Con questo approccio, bisogna prima creare la propria classe User, che sar memorizzata nel database.
// src/Acme/UserBundle/Entity/User.php namespace Acme\UserBundle\Entity; use Symfony\Component\Security\Core\User\UserInterface; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity */ class User implements UserInterface { /** * @ORM\Column(type="string", length="255") */ protected $username; // ... }

196

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Per come stato pensato il sistema di sicurezza, lunico requisito per la classe utente personalizzata che implementi linterfaccia Symfony\Component\Security\Core\User\UserInterface. Questo signica che il concetto di utente pu essere qualsiasi cosa, purch implementi questa interfaccia. Nota: Loggetto utente verr serializzato e salvato nella sessione durante le richieste, quindi si consiglia di implementare linterfaccia Serializable (http://php.net/manual/en/class.serializable.php) nel proprio oggetto utente. Ci particolarmente importante se la classe User ha una classe genitore con propriet private. Quindi, congurare un fornitore utenti entity e farlo puntare alla classe User: YAML
# app/config/security.yml security: providers: main: entity: { class: Acme\UserBundle\Entity\User, property: username }

XML
<!-- app/config/security.xml --> <config> <provider name="main"> <entity class="Acme\UserBundle\Entity\User" property="username" /> </provider> </config>

PHP

// app/config/security.php $container->loadFromExtension(security, array( providers => array( main => array( entity => array(class => Acme\UserBundle\Entity\User, property => username ), ), ));

Con lintroduzione di questo nuovo fornitore, il sistema di autenticazione tenter di caricare un oggetto User dal database, utilizzando il campo username di questa classe. Nota: Questo esempio ha come unico scopo quello di mostrare lidea di base dietro al fornitore entity. Per un esempio completamente funzionante, vedere Come caricare gli utenti dal database (il fornitore di entit). Per ulteriori informazioni sulla creazione di un proprio fornitore personalizzato (ad esempio se necessario caricare gli utenti tramite un servizio web), vedere Come creare un fornitore utenti personalizzato.
Codicare la password dellutente

Finora, per semplicit, tutti gli esempi hanno memorizzato le password dellutente in formato testo (se tali utenti sono memorizzati in un le di congurazione o in un qualche database). Naturalmente, in unapplicazione reale, si consiglia per ragioni di sicurezza di codicare le password degli utenti. Questo facilmente realizzabile, mappando la classe User in uno dei numerosi encoder disponibili. Per esempio, per memorizzare gli utenti in memoria, ma oscurare le loro password tramite sha1, fare come segue: YAML 2.1. Libro 197

Documentazione Symfony, Release 2.0

# app/config/security.yml security: # ... providers: in_memory: users: ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: ROLE_USER admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: ROLE_ADMIN encoders: Symfony\Component\Security\Core\User\User: algorithm: sha1 iterations: 1 encode_as_base64: false

XML

<!-- app/config/security.xml --> <config> <!-- ... --> <provider name="in_memory"> <user name="ryan" password="bb87a29949f3a1ee0559f8a57357487151281386" roles="ROLE_USER" <user name="admin" password="74913f5cd5f61ec0bcfdb775414c2fb3d161b620" roles="ROLE_ADMIN </provider>

<encoder class="Symfony\Component\Security\Core\User\User" algorithm="sha1" iterations="1" e </config>

PHP

// app/config/security.php $container->loadFromExtension(security, array( // ... providers => array( in_memory => array( users => array( ryan => array(password => bb87a29949f3a1ee0559f8a57357487151281386, roles admin => array(password => 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, role ), ), ), encoders => array( Symfony\Component\Security\Core\User\User => array( algorithm => sha1, iterations => 1, encode_as_base64 => false, ), ), ));

Impostando iterations a 1 ed encode_as_base64 a false, sufciente eseguire una sola volta lalgoritmo sha1 sulla password e senza alcuna codica supplementare. ora possibile calcolare lhash della password a livello di codice (ad esempio hash(sha1, ryanpass)) o tramite qualche strumento online come functions-online.com (http://www.functions-online.com/sha1.html) Se si creano gli utenti in modo dinamico (e si memorizzano in una base dati), possibile utilizzare algoritmi di hash ancora pi complessi e poi contare su un oggetto encoder, per codicare le password. Per esempio, supponiamo che loggetto User sia Acme\UserBundle\Entity\User (come nellesempio precedente). In primo luogo, congurare lencoder per User: 198 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

YAML
# app/config/security.yml security: # ... encoders: Acme\UserBundle\Entity\User: sha512

XML
<!-- app/config/security.xml --> <config> <!-- ... --> <encoder class="Acme\UserBundle\Entity\User" algorithm="sha512" /> </config>

PHP
// app/config/security.php $container->loadFromExtension(security, array( // ... encoders => array( Acme\UserBundle\Entity\User => sha512, ), ));

In questo caso, si utilizza il pi forte algoritmo sha512. Inoltre, poich si semplicemente specicato lalgoritmo (sha512) come stringa, il sistema per impostazione predenita far lhash 5000 volte in un riga e poi la codicher in base64. In altre parole, la password stata notevolmente offuscata, in modo che lhash della password non possa essere decodicato (cio non possibile determinare la password partendo dal suo hash). Se si ha un form di registrazione per gli utenti, necessario essere in grado di determinare la password con hash, in modo che sia possibile impostarla per lutente. Indipendentemente dallalgoritmo congurato per loggetto User, la password con hash pu essere determinata nel seguente modo, da dentro un controllore:
$factory = $this->get(security.encoder_factory); $user = new Acme\UserBundle\Entity\User(); $encoder = $factory->getEncoder($user); $password = $encoder->encodePassword(ryanpass, $user->getSalt()); $user->setPassword($password);

Recuperare loggetto User

Dopo lautenticazione, si pu accedere alloggetto User per l utente corrente tramite il servizio security.context. Da dentro un controllore, assomiglier a questo:
public function indexAction() { $user = $this->get(security.context)->getToken()->getUser(); }

Nota: Gli utenti anonimi sono tecnicamente autenticati, nel senso che il metodo isAuthenticated() delloggetto di un utente anonimo restituir true. Per controllare se lutente sia effettivamente autenticato, vericare il ruolo IS_AUTHENTICATED_FULLY. 2.1. Libro 199

Documentazione Symfony, Release 2.0

In un template Twig, si pu accedere a questo oggetto tramite la chiave app.user, che richiama il metodo :method:GlobalVariables::getUser()<Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables::getUser>: Twig
<p>Nome utente: {{ app.user.username }}</p>

Utilizzare fornitori utenti multipli

Ogni meccanismo di autenticazione (ad esempio lautenticazione HTTP, il form di login, ecc.) utilizza esattamente un fornitore utenti e, per impostazione predenita, user il primo fornitore dichiarato. Ma cosa succede se si desidera specicare alcuni utenti tramite congurazione e il resto degli utenti nella base dati? Questo possibile attraverso la creazione di un nuovo fornitore, che li unisca: YAML
# app/config/security.yml security: providers: chain_provider: providers: [in_memory, user_db] in_memory: users: foo: { password: test } user_db: entity: { class: Acme\UserBundle\Entity\User, property: username }

XML
<!-- app/config/security.xml --> <config> <provider name="chain_provider"> <provider>in_memory</provider> <provider>user_db</provider> </provider> <provider name="in_memory"> <user name="foo" password="test" /> </provider> <provider name="user_db"> <entity class="Acme\UserBundle\Entity\User" property="username" /> </provider> </config>

PHP
// app/config/security.php $container->loadFromExtension(security, array( providers => array( chain_provider => array( providers => array(in_memory, user_db), ), in_memory => array( users => array( foo => array(password => test), ), ), user_db => array(

200

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

entity => array(class => Acme\UserBundle\Entity\User, property => username ), ), ));

Ora, tutti i meccanismi di autenticazione utilizzeranno il chain_provider, dal momento che il primo specicato. Il chain_provider, a sua volta, tenta di caricare lutente da entrambi i fornitori in_memory e user_db. Suggerimento: Se non ci sono ragioni per separare gli utenti in_memory dagli utenti user_db, possibile ottenere ancora pi facilmente questo risultato, combinando le due sorgenti in un unico fornitore: YAML
# app/config/security.yml security: providers: main_provider: users: foo: { password: test } entity: { class: Acme\UserBundle\Entity\User, property: username }

XML
<!-- app/config/security.xml --> <config> <provider name=="main_provider"> <user name="foo" password="test" /> <entity class="Acme\UserBundle\Entity\User" property="username" /> </provider> </config>

PHP

// app/config/security.php $container->loadFromExtension(security, array( providers => array( main_provider => array( users => array( foo => array(password => test), ), entity => array(class => Acme\UserBundle\Entity\User, property => username ), ), ));

anche possibile congurare il rewall o meccanismi di autenticazione individuali per utilizzare un provider specico. Ancora una volta, a meno che un provider sia specicato esplicitamente, viene sempre utilizzato il primo fornitore: YAML
# app/config/security.yml security: firewalls: secured_area: # ... provider: user_db http_basic: realm: "Demo area sicura"

2.1. Libro

201

Documentazione Symfony, Release 2.0

provider: in_memory form_login: ~

XML
<!-- app/config/security.xml --> <config> <firewall name="secured_area" pattern="^/" provider="user_db"> <!-- ... --> <http-basic realm="Demo area sicura" provider="in_memory" /> <form-login /> </firewall> </config>

PHP
// app/config/security.php $container->loadFromExtension(security, array( firewalls => array( secured_area => array( // ... provider => user_db, http_basic => array( // ... provider => in_memory, ), form_login => array(), ), ), ));

In questo esempio, se un utente cerca di accedere tramite autenticazione HTTP, il sistema di autenticazione utilizzer il fornitore utenti in_memory. Ma se lutente tenta di accedere tramite il form di login, sar usato il fornitore user_db (in quanto limpostazione predenita per il rewall). Per ulteriori informazioni su fornitori utenti e congurazione del rewall, vedere il Riferimento congurazione sicurezza. Ruoli Lidea di un ruolo la chiave per il processo di autorizzazione. A ogni utente viene assegnato un insieme di ruoli e quindi ogni risorsa richiede uno o pi ruoli. Se lutente ha i ruoli richiesti, laccesso concesso. In caso contrario, laccesso negato. I ruoli sono abbastanza semplici e sono fondamentalmente stringhe che si possono inventare e utilizzare secondo necessit (anche se i ruoli internamente sono oggetti). Per esempio, se necessario limitare laccesso alla sezione admin del sito web del blog , si potrebbe proteggere quella parte con un ruolo ROLE_BLOG_ADMIN. Questo ruolo non ha bisogno di essere denito ovunque, sufciente iniziare a usarlo. Nota: Tutti i ruoli devono iniziare con il presso ROLE_ per poter essere gestiti da Symfony2. Se si deniscono i propri ruoli con una classe Role dedicata (caratteristica avanzata), non bisogna usare il presso ROLE_.

202

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

I ruoli gerarchici

Invece di associare molti ruoli agli utenti, possibile denire regole di ereditariet dei ruoli creando una gerarchia di ruoli: YAML
# app/config/security.yml security: role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

XML
<!-- app/config/security.xml --> <config> <role id="ROLE_ADMIN">ROLE_USER</role> <role id="ROLE_SUPER_ADMIN">ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH</role> </config>

PHP
// app/config/security.php $container->loadFromExtension(security, array( role_hierarchy => array( ROLE_ADMIN => ROLE_USER, ROLE_SUPER_ADMIN => array(ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH), ), ));

Nella congurazione sopra, gli utenti con ruolo ROLE_ADMIN avranno anche il ruolo ROLE_USER. Il ruolo ROLE_SUPER_ADMIN ha ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH e ROLE_USER (ereditati da ROLE_ADMIN). Logout Generalmente, si vuole che gli utenti possano disconnettersi tramite logout. Fortunatamente, il rewall pu gestire automaticamente questo caso quando si attiva il parametro di congurazione logout: YAML
# app/config/security.yml security: firewalls: secured_area: # ... logout: path: /logout target: / # ...

XML
<!-- app/config/security.xml --> <config> <firewall name="secured_area" pattern="^/"> <!-- ... --> <logout path="/logout" target="/" />

2.1. Libro

203

Documentazione Symfony, Release 2.0

</firewall> <!-- ... --> </config>

PHP
// app/config/security.php $container->loadFromExtension(security, array( firewalls => array( secured_area => array( // ... logout => array(path => logout, target => /), ), ), // ... ));

Una volta che questo viene congurato sotto il rewall, linvio di un utente in /logout (o qualunque debba essere il percorso) far disconnettere lutente corrente. Lutente sar quindi inviato alla pagina iniziale (il valore denito dal parametro target). Entrambi i parametri di congurazione path e target assumono come impostazione predenita ci che specicato qui. In altre parole, se non necessario personalizzarli, possibile ometterli completamente e accorciare la congurazione: YAML
logout: ~

XML
<logout />

PHP
logout => array(),

Si noti che non necessario implementare un controllore per lURL /logout, perch il rewall si occupa di tutto. Si pu, tuttavia, creare una rotta da poter utilizzare per generare lURL: YAML
# app/config/routing.yml logout: pattern: /logout

XML
<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="logout" pattern="/logout" /> </routes>

PHP

204

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

// app/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(logout, new Route(/logout, array())); return $collection;

Una volta che lutente stato disconnesso, viene rinviato al percorso denito dal parametro target sopra (ad esempio, la homepage). Per ulteriori informazioni sulla congurazione di logout, vedere il Riferimento della congurazione di sicurezza. Controllare laccesso nei template Nel caso si voglia controllare allinterno di un template se lutente corrente ha un ruolo, usare la funzione helper: Twig
{% if is_granted(ROLE_ADMIN) %} <a href="...">Delete</a> {% endif %}

PHP
<?php if ($view[security]->isGranted(ROLE_ADMIN)): ?> <a href="...">Delete</a> <?php endif; ?>

Nota: Se si utilizza questa funzione e non si in un URL dove c un rewall attivo, viene lanciata uneccezione. Anche in questo caso, quasi sempre una buona idea avere un rewall principale che copra tutti gli URL (come si visto in questo capitolo).

Verica dellaccesso nei controllori Quando si vuole vericare se lutente corrente abbia un ruolo nel controllore, usare il metodo isGranted del contesto di sicurezza:
public function indexAction() { // mostrare contenuti diversi agli utenti admin if($this->get(security.context)->isGranted(ADMIN)) { // caricare qui contenuti di amministrazione } // caricare qui altri contenuti normali }

Nota: Un rewall deve essere attivo o verr lanciata uneccezione quando viene chiamato il metodo isGranted. Vedere la nota precedente sui template per maggiori dettagli.

2.1. Libro

205

Documentazione Symfony, Release 2.0

Impersonare un utente A volte, utile essere in grado di passare da un utente allaltro senza dover uscire e rientrare tutte le volte (per esempio quando si esegue il debug o si cerca di capire un bug che un utente vede ma che non si riesce a riprodurre). Lo si pu fare facilmente, attivando lascoltatore switch_user del rewall: YAML
# app/config/security.yml security: firewalls: main: # ... switch_user: true

XML
<!-- app/config/security.xml --> <config> <firewall> <!-- ... --> <switch-user /> </firewall> </config>

PHP
// app/config/security.php $container->loadFromExtension(security, array( firewalls => array( main=> array( // ... switch_user => true ), ), ));

Per passare a un altro utente, basta aggiungere una stringa query allURL corrente, con il parametro _switch_user e il nome utente come valore : http://example.com/indirizzo?_switch_user=thomas Per tornare indietro allutente originale, usare il nome utente speciale _exit: http://example.com/indirizzo?_switch_user=_exit Naturalmente, questa funzionalit deve essere messa a disposizione di un piccolo gruppo di utenti. Per impostazione predenita, laccesso limitato agli utenti che hanno il ruolo ROLE_ALLOWED_TO_SWITCH. Il nome di questo ruolo pu essere modicato tramite limpostazione role. Per maggiore sicurezza, anche possibile modicare il nome del parametro della query tramite limpostazione parameter: YAML
# app/config/security.yml security: firewalls: main: // ... switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }

XML

206

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<!-- app/config/security.xml --> <config> <firewall> <!-- ... --> <switch-user role="ROLE_ADMIN" parameter="_want_to_be_this_user" /> </firewall> </config>

PHP

// app/config/security.php $container->loadFromExtension(security, array( firewalls => array( main=> array( // ... switch_user => array(role => ROLE_ADMIN, parameter => _want_to_be_this_user ), ), ));

Autenticazione senza stato Per impostazione predenita, Symfony2 si basa su un cookie (Session) per persistere il contesto di sicurezza dellutente. Ma se si utilizzano certicati o lautenticazione HTTP, per esempio, la persistenza non necessaria, in quanto le credenziali sono disponibili a ogni richiesta. In questo caso e se non necessario memorizzare nientaltro tra le richieste, possibile attivare lautenticazione senza stato (il che signica Symfony non creer alcun cookie): YAML
# app/config/security.yml security: firewalls: main: http_basic: ~ stateless: true

XML
<!-- app/config/security.xml --> <config> <firewall stateless="true"> <http-basic /> </firewall> </config>

PHP
// app/config/security.php $container->loadFromExtension(security, array( firewalls => array( main => array(http_basic => array(), stateless => true), ), ));

Nota: Se si usa un form di login, Symfony2 creer un cookie anche se si imposta stateless a true.

2.1. Libro

207

Documentazione Symfony, Release 2.0

Considerazioni nali La sicurezza pu essere un problema profondo e complesso nellapplicazione da risolvere in modo corretto. Per fortuna, il componente della sicurezza di Symfony segue un ben collaudato modello di sicurezza basato su autenticazione e autorizzazione. Lautenticazione, che avviene sempre per prima, gestita da un rewall il cui compito quello di determinare lidentit degli utenti attraverso diversi metodi (ad esempio lautenticazione HTTP, il form di login, ecc.). Nel ricettario, si trovano esempi di altri metodi per la gestione dellautenticazione, includendo quello che tratta limplementazione della funzionalit cookie Ricorda i dati. Una volta che un utente autenticato, lo strato di autorizzazione pu stabilire se lutente debba o meno avere accesso a una specica risorsa. Pi frequentemente, i ruoli sono applicati a URL, classi o metodi e se lutente corrente non ha quel ruolo, laccesso negato. Lo strato di autorizzazione, per, molto pi profondo e segue un sistema di voto, in modo che tutte le parti possono determinare se lutente corrente dovrebbe avere accesso a una data risorsa. Ulteriori informazioni su questo e altri argomenti nel ricettario. Saperne di pi con il ricettario Forzare HTTP/HTTPS Blacklist di utenti per indirizzo IP Access Control List (ACL) Come aggiungere la funzionalit ricordami al login

2.1.13 Cache HTTP


Le applicazioni web sono dinamiche. Non importa quanto efciente possa essere la propria applicazione, ogni richiesta conterr sempre overhead rispetto a quando si serve un le statico. Per la maggior parte delle applicazioni, questo non un problema. Symfony2 molto veloce e, a meno che non si stia facendo qualcosa di veramente molto pesante, ogni richiesta sar gestita rapidamente, senza stressare troppo il server. Man mano che il proprio sito cresce, per, quelloverhead pu diventare un problema. Il processo normalmente seguito a ogni richiesta andrebbe fatto una volta sola. Questo proprio lo scopo che si pregge la cache. La cache sulle spalle dei giganti Il modo pi efcace per migliorare le prestazioni di unapplicazione mettere in cache lintero output di una pagina e quindi aggirare interamente lapplicazione a ogni richiesta successiva. Ovviamente, questo non sempre possibile per siti altamente dinamici, oppure s? In questo capitolo, mostreremo come funziona il sistema di cache di Symfony2 e perch pensiamo che sia il miglior approccio possibile. Il sistema di cache di Symfony2 diverso, perch si appoggia sulla semplicit e sulla potenza della cache HTTP, denita nelle speciche HTTP. Invence di inventare un altro metodo di cache, Symfony2 abbraccia lo standard che denisce la comunicazione di base sul web. Una volta capiti i fondamenti dei modelli di validazione e scadenza della cache HTTP, si sar in grado di padroneggiare il sistema di cache di Symfony2. Per poter imparare come funziona la cache in Symfony2, procederemo in quattro passi: Passo 1: Un gateway cache, o reverse proxy, un livello indipendente che si situa davanti alla propria applicazione. Il reverse proxy mette in cache le risposte non appena sono restituite dalla propria applicazione e risponde alle richieste con risposte in cache, prima che arrivino alla propria applicazione. Symfony2 fornisce il suo reverse proxy, ma se ne pu usare uno qualsiasi.

208

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Passo 2: Gli header di cache HTTP sono usati per comunicare col gateway cache e con ogni altra cache tra la propria applicazione e il client. Symfony2 fornisce impostazioni predenite appropriate e una potente interfaccia per interagire con gli header di cache. Passo 3: La scadenza e la validazione HTTP sono due modelli usati per determinare se il contenuto in cache fresco (pu essere riusato dalla cache) o vecchio (andrebbe rigenerato dallapplicazione): Passo 4: Gli Edge Side Include (ESI) consentono alla cache HTTP di essere usata per mettere in cache frammenti di pagine (anche frammenti annidati) in modo indipendente. Con ESI, si pu anche mettere in cache una pagina intera per 60 minuti, ma una barra laterale interna per soli 5 minuti. Poich la cache con HTTP non esclusiva di Symfony2, esistono gi molti articoli a riguardo. Se si nuovi con la cache HTTP, raccomandiamo caldamente larticolo di Ryan Tomayko Things Caches Do (http://tomayko.com/writings/things-caches-do). Unaltra risorsa importante il Cache Tutorial (http://www.mnot.net/cache_docs/) di Mark Nottingham. Cache con gateway cache Quando si usa la cache con HTTP, la cache completamente separata dalla propria applicazione e risiede in mezzo tra la propria applicazione e il client che effettua la richiesta. Il compito della cache accettare le richieste dal client e passarle alla propria applicazione. La cache ricever anche risposte dalla propria applicazione e le girer al client. La cache un uomo in mezzo nella comunicazione richiestarisposta tra il client e la propria applicazione. Lungo la via, la cache memorizzer ogni risposta ritenuta cacheable (vedere Introduzione alla cache HTTP). Se la stessa risorsa viene richiesta nuovamente, la cache invia la risposta in cache al client, ignorando completamente la propria applicazione. Questo tipo di cache nota come HTTP gateway cache e ne esistono diverse, come Varnish (http://www.varnishcache.org/), Squid in modalit reverse proxy (http://wiki.squid-cache.org/SquidFaq/ReverseProxy) e il reverse proxy di Symfony2.
Tipi di cache

Ma il gateway cache non lunico tipo di cache. Infatti, gli header HTTP di cache inviati dalla propria applicazioni sono analizzati e interpretati da tre diversi tipi di cache: Cache del browser: Ogni browser ha la sua cache locale, usata principalmente quando si clicca sul pulsante indietro per immagini e altre risorse. La cache del browser una cache privata, perch le risorse in cache non sono condivise con nessun altro. Proxy cache: Un proxy una cache condivisa, perch molte persone possono stare dietro a un singolo proxy. Solitamente si trova nelle grandi aziende e negli ISP, per ridurre la latenza e il trafco di rete. Gateway cache: Come il proxy, anche questa una cache condivisa, ma dalla parte del server. Installata dai sistemisti di rete, rende i siti pi scalabili, afdabili e performanti. Suggerimento: Le gateway cache sono a volte chiamate reverse proxy cache, cache surrogate o anche acceleratori HTTP.

Nota: I signicati di cache privata e condivisa saranno pi chiari quando si parler di mettere in cache risposte che contengono contenuti specici per un singolo utente (p.e. informazioni sullaccount).

2.1. Libro

209

Documentazione Symfony, Release 2.0

Ogni risposta dalla propria applicazione probabilmente attraverser una o pi cache dei primi due tipi. Queste cache sono fuori dal nostro controllo, ma seguono le indicazioni di cache HTTP impostate nella risposta.
Il reverse proxy di Symfony2

Symfony2 ha un suo reverse proxy (detto anche gateway cache) scritto in PHP. Abilitandolo, le risposte in cache dalla propria applicazione inizieranno a essere messe in cache. Linstallazione altrettanto facile. Ogni una applicazione Symfony2 ha la cache gi congurata in AppCache, che estende AppKernel. Il kernel della cache il reverse proxy. Per abilitare la cache, modicare il codice di un front controller, per usare il kernel della cache:
// web/app.php require_once __DIR__./../app/bootstrap.php.cache; require_once __DIR__./../app/AppKernel.php; require_once __DIR__./../app/AppCache.php; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel(prod, false); $kernel->loadClassCache(); // wrap the default AppKernel with the AppCache one $kernel = new AppCache($kernel); $kernel->handle(Request::createFromGlobals())->send();

Il kernel della cache agir immediatamente da reverse proxy, mettendo in cache le risposte della propria applicazione e restituendole al client. Suggerimento: Il kernel della cache ha uno speciale metodo getLog(), che restituisce una rappresentazione in stringa di ci che avviene a livello di cache. Nellambiente di sviluppo, lo si pu usare per il debug e la verica della strategia di cache:
error_log($kernel->getLog());

Loggetto AppCache una una congurazione predenita adeguata, ma pu essere regolato tramite un insieme di opzioni impostabili sovrascrivendo il metodo getOptions():
// app/AppCache.php use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; class AppCache extends HttpCache { protected function getOptions() { return array( debug default_ttl private_headers allow_reload allow_revalidate stale_while_revalidate stale_if_error );

=> => => => => => =>

false, 0, array(Authorization, Cookie), false, false, 2, 60,

210

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

} }

Suggerimento: A meno che non sia sovrascritta in getOptions(), lopzione debug sar impostata automaticamente al valore di debug di AppKernel circostante. Ecco una lista delle opzioni principali: default_ttl: Il numero di secondi per cui un elemento in cache va considerato fresco, quando nessuna informazione esplicita sulla freschezza viene fornita in una risposta. Header espliciti Cache-Control o Expires sovrascrivono questo valore (predenito: 0); private_headers: Insieme di header di richiesta che fanno scattare il comportamento privato Cache-Control sulle risposte che non stabiliscono esplicitamente il loro stato di public o private, tramite una direttiva Cache-Control. (predenito: Authorization e Cookie); allow_reload: Specica se il client possa forzare un ricaricamento della cache includendo una direttiva Cache-Control no-cache nella richiesta. Impostare a true per aderire alla RFC 2616 (predenito: false); allow_revalidate: Specica se il client possa forzare una rivalidazione della cache includendo una direttiva Cache-Control max-age=0 nella richiesta. Impostare a true per aderire alla RFC 2616 (predenito: false); stale_while_revalidate: Specica il numero predenito di secondi (la granularit il secondo, perch la precisione del TTL della risposta un secondo) durante il quale la cache pu restituire immediatamente una risposta vecchia mentre si rivalida in background (predenito: 2); questa impostazione sovrascritta dallestensione stale-while-revalidate Cache-Control di HTTP (vedere RFC 5861); stale_if_error: Specica il numero predenito di secondi (la granularit il secondo) durante il quale la cache pu servire una risposta vecchia quando si incontra un errore (predenito: 60). Questa impostazione sovrascritta dallestensione stale-if-error Cache-Control di HTTP (vedere RFC 5861). Se debug true, Symfony2 aggiunge automaticamente un header X-Symfony-Cache alla risposta, con dentro informazioni utili su hit e miss della cache. Cambiare da un reverse proxy a un altro Il reverse proxy di Symfony2 un grande strumento da usare durante lo sviluppo del proprio sito oppure quando il deploy del proprio sito su un host condiviso, dove non si pu installare altro che codice PHP. Ma essendo scritto in PHP, non pu essere veloce quando un proxy scritto in C. Per questo raccomandiamo caldamente di usare Varnish o Squid sul proprio server di produzione, se possibile. La buona notizia che il cambio da un proxy a un altro facile e trasparente, non implicando alcuna modica al codice della propria applicazione. Si pu iniziare semplicemente con il reverse proxy di Symfony2 e aggiornare successivamente a Varnish, quando il trafco aumenta. Per maggiori informazioni sulluso di Varnish con Symfony2, vedere la ricetta Usare Varnish.

Nota: Le prestazioni del reverse proxy di Symfony2 non dipendono dalla complessit dellapplicazione. Questo perch il kernel dellapplicazione parte solo quando ha una richiesta a cui deve essere rigirato.

2.1. Libro

211

Documentazione Symfony, Release 2.0

Introduzione alla cache HTTP Per sfruttare i livelli di cache disponibili, la propria applicazione deve poter comunicare quale risposta pu essere messa in cache e le regole che stabiliscono quando e come tale cache debba essere considerata vecchia. Lo si pu fare impostando gli header di cache HTTP nella risposta. Suggerimento: Si tenga a mente che HTTP non altro che il linguaggio (un semplice linguaggio testuale) usato dai client web (p.e. i browser) e i server web per comunicare tra loro. Quando parliamo di cache HTTP, parliamo della parte di tale linguaggio che consente a client e server di scambiarsi informazioni riguardo alla cache. HTTP specica quattro header di cache per la risposta di cui ci occupiamo: Cache-Control Expires ETag Last-Modified Lheader pi importante e versatile lheader Cache-Control, che in realt un insieme di varie informazioni sulla cache. Nota: Ciascun header sar spiegato in dettaglio nella sezione Scadenza e validazione HTTP.

Lheader Cache-Control

Lheader Cache-Control unico, perch non contiene una, ma vari pezzi di informazione sulla possibilit di una risposta di essere messa in cache. Ogni pezzo di informazione separato da una virgola: Cache-Control: private, max-age=0, must-revalidate Cache-Control: max-age=3600, must-revalidate Symfony fornisce unastrazione sullheader Cache-Control, per rendere la sua creazione pi gestibile:
$response = new Response(); // segna la risposta come pubblica o privata $response->setPublic(); $response->setPrivate(); // imposta max age privata o condivisa $response->setMaxAge(600); $response->setSharedMaxAge(600); // imposta una direttiva personalizzata Cache-Control $response->headers->addCacheControlDirective(must-revalidate, true);

Risposte pubbliche e risposte private

Sia la gateway cache che la proxy cache sono considerate cache condivise, perch il contenuto della cache condiviso da pi di un utente. Se una risposta specica per un utente venisse per errore inserita in una cache condivisa, potrebbe successivamente essere restituita a diversi altri utenti. Si immagini se delle informazioni su un account venissero messe in cache e poi restituite a ogni utente successivo che richiede la sua pagina dellaccount!

212

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Per gestire questa situazione, ogni risposta pu essere impostata a pubblica o privata: pubblica: Indica che la risposta pu essere messa in cache sia da che private che da cache condivise; privata: Indica che tutta la risposta, o una sua parte, per un singolo utente e quindi non deve essere messa in una cache condivisa. Symfony conservativo e ha come predenita una risposta privata. Per sfruttare le cache condivise (come il reverse proxy di Symfony2), la risposta deve essere impostata esplicitamente come pubblica.
Metodi sicuri

La cache HTTP funziona solo per metodi HTTP sicuri (come GET e HEAD). Essere sicuri vuol dire che lo stato dellapplicazione sul server non cambia mai quando si serve la richiesta (si pu, certamente, memorizzare uninformazione sui log, mettere in cache dati, eccetera). Questo ha due conseguenze molto ragionevoli: Non si dovrebbe mai cambiare lo stato della propria applicazione quando si risponde a una richiesta GET o HEAD. Anche se non si usa una gateway cache, la presenza di proxy cache vuol dire che ogni richiesta GET o HEAD potrebbe arrivare al proprio server, ma potrebbe anche non arrivare. Non aspettarsi la cache dei metodi PUT, POST o DELETE. Questi metodi sono fatti per essere usati quando si cambia lo stato della propria applicazione (p.e. si cancella un post di un blog). Metterli in cache impedirebbe ad alcune richieste di arrivare alla propria applicazione o di modicarla.
Regole e valori predeniti della cache

HTTP 1.1 consente per impostazione predenita la cache di tutto, a meno che non ci sia un header esplicito Cache-Control. In pratica, la maggior parte delle cache non fanno nulla quando la richiesta ha un cookie, un header di autorizzazione, usa un metodo non sicuro (PUT, POST, DELETE) o quando la risposta ha un codice di stato di rinvio. Symfony2 imposta automaticamente un header Cache-Control conservativo, quando nessun header impostato dallo sviluppatore, seguendo queste regole: Se non deinito nessun header di cache (Cache-Control, Expires, ETag o Last-Modified), Cache-Control impostato a no-cache, il che vuol dire che la risposta non sar messa in cache; Se Cache-Control vuoto (ma uno degli altri header di cache presente), il suo valore impostato a private, must-revalidate; Se invece almeno una direttiva Cache-Control impostata e nessuna direttiva public o private stata aggiunta esplicitamente, Symfony2 aggiunge automaticamente la direttiva private (tranne quando impostato s-maxage). Scadenza e validazione HTTP Le speciche HTTP deniscono due modelli di cache: Con il modello a scadenza (http://tools.ietf.org/html/rfc2616#section-13.2), si specica semplicemente quanto a lungo una risposta debba essere considerata fresca, includendo un header Cache-Control e/o uno Expires. Le cache che capiscono la scadenza non faranno di nuovo la stessa richiesta nch la versione in cache non raggiunge la sua scadenza e diventa vecchia. Quando le pagine sono molto dinamiche (cio quando la loro rappresentazione varia spesso), il modello a validazione (http://tools.ietf.org/html/rfc2616#section-13.3) spesso necessario. Con questo modello, la cache memorizza la risposta, ma chiede al serve a ogni richiesta se la risposta in cache sia ancora valida o meno.

2.1. Libro

213

Documentazione Symfony, Release 2.0

Lapplicazione usa un identicatore univoco per la risposta (lheader Etag) e/o un timestamp (come lheader Last-Modified) per vericare se la pagina sia cambiata da quanto stata messa in cache. Lo scopo di entrambi i modelli quello di non generare mai la stessa risposta due volte, appoggiandosi a una cache per memorizzare e restituire risposte fresche. Leggere le speciche HTTP Le speciche HTTP deniscono un linguaggio semplice, ma potente, in cui client e server possono comunicare. Come sviluppatori web, il modello richiesta-risposta delle speciche domina il nostro lavoro. Sfortunatamente, il documento delle speciche, la RFC 2616 (http://tools.ietf.org/html/rfc2616), pu risultare di difcile lettura. C uno sforzo in atto (HTTP Bis (http://tools.ietf.org/wg/httpbis/)) per riscrivere la RFC 2616. Non descrive una nuova versione di HTTP, ma per lo pi chiarisce le speciche HTTP originali. Anche lorganizzazione migliore, essendo le speciche separate in sette parti; tutto ci che riguarda la cache HTTP si trova in due parti dedicate (P4 - Richieste condizionali (http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12) e P6 Cache: Browser e cache intermedie (http://tools.ietf.org/html/draft-ietf-httpbis-p6-cache-12)). Come sviluppatori web, dovremmo leggere tutti le speciche. Possiedono un chiarezza e una potenza, anche dopo oltre dieci anni dalla creazione, inestimabili. Non ci si spaventi dalle apparenze delle speciche, il contenuto molto pi bello della copertina.

Scadenza

Il modello a scadenza il pi efciente e il pi chiaro dei due modelli di cache e andrebbe usato ogni volta che possibile. Quando una risposta messa in cache con una scadenza, la cache memorizzer la risposta e la restituir direttamente, senza arrivare allapplicazione, nch non scade. Il modello a scadenza pu essere implementato con luso di due header HTTP, quasi identici: Expires o Cache-Control.
Scadenza con lheader Expires

Secondo le speciche HTTP, lheader Expires d la data e lora dopo la quale la risposta considerata vecchia. Lheader Expires pu essere impostato con il metodo setExpires() di Response. Accetta unistanza di DateTime come parametro:
$date = new DateTime(); $date->modify(+600 seconds); $response->setExpires($date);

Il risultante header HTTP sar simile a questo:


Expires: Thu, 01 Mar 2011 16:00:00 GMT

Nota: Il metodo setExpires() converte automaticamente la data al fuso orario GMT, come richiesto dalle speciche. Si noti che, nelle versioni di HTTP precedenti alla 1.1, non era richiesto al server di origine di inviare lheader Date. Di conseguenza, la cache (p.e. il browser) potrebbe aver bisogno di appoggiarsi allorologio locale per valuare lheader Expires, rendendo il calcolo del ciclo di vita vulnerabile a difformit di ore. Lheader Expires soffre di unaltra limitazione: le speciche stabiliscono che i server HTTP/1.1 non dovrebbero inviare header Expires oltre un anno nel futuro.

214

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Scadenza con lheader Cache-Control

A causa dei limiti dellheader Expires, la maggior parte delle volte si user al suo posto lheader Cache-Control. Si ricordi che lheader Cache-Control usato per specicare molte differenti direttive di cache. Per la scadenza, ci sono due direttive, max-age e s-maxage. La prima usata da tutte le cache, mentre la seconda viene considerata solo dalla cache condivise:
// Imposta il numero di secondi dopo cui la risposta // non dovrebbe pi essere considerata fresca $response->setMaxAge(600); // Come sopra, ma solo per cache condivise $response->setSharedMaxAge(600);

Lheader Cache-Control avrebbe il seguente formato (potrebbe contenere direttive aggiuntive):


Cache-Control: max-age=600, s-maxage=600

Validazione

Quando una risorsa ha bisogno di essere aggiornata non appena i dati sottostanti subiscono una modica, il modello a scadenza non raggiunge lo scopo. Con il modello a scadenza, allapplicazione non sar chiesto di restituire la risposta aggiornata, nch la cache non diventa vecchia. Il modello a validazione si occupa di questo problema. Con questo modello, la cache continua a memorizzare risposte. La differenza che, per ogni richiesta, la cache chiede allapplicazione se la risposta in cache ancora valida. Se la cache ancora valida, la propria applicazione dovrebbe restituire un codice di stato 304 e nessun contenuto. Questo dice alla cache che va bene restituire la risposta in cache. Con questo modello, principalmente si risparmia banda, perch la rappresentazione non inviata due volte allo stesso client (invece inviata una risposta 304). Ma se si progetta attentamente la propria applicazione, si potrebbe essere in grado di prendere il minimo dei dati necessari per inviare una risposta 304 e risparmiare anche CPU (vedere sotto per un esempio di implementazione). Suggerimento: Il codice di stato 304 signica non modicato. importante, perch questo codice di stato non contiene il vero contenuto richiesto. La risposta invece un semplice e leggero insieme di istruzioni che dicono alla cache che dovrebbe usare la sua versione memorizzata. Come per la scadenza, ci sono due diversi header HTTP che possono essere usati per implementare il modello a validazione: ETag e Last-Modified.
Validazione con header ETag

Lheader ETag un header stringa (chiamato tag entit) che identica univocamente una rappresentazione della risorsa in questione. interamente generato e impostato dalla propria applicazione, quindi si pu dire, per esempio, se la risorsa /about che in cache sia aggiornata con ci che la propria applicazione restituirebbe. Un ETag come unimpronta digitale ed usato per confrontare rapidamente se due diverse versioni di una risorsa siano equivalenti. Come le impronte digitali, ogni ETag deve essere univoco tra tutte le rappresentazioni della stessa risorsa. Vediamo una semplice implementazione, che genera lETag come un md5 del contenuto:
public function indexAction() { $response = $this->render(MyBundle:Main:index.html.twig);

2.1. Libro

215

Documentazione Symfony, Release 2.0

$response->setETag(md5($response->getContent())); $response->isNotModified($this->getRequest()); return $response; }

Il metodo Response::isNotModified() confronta lETag inviato con la Request con quello impostato nella Response. Se i due combaciano, il metodo imposta automaticamente il codice di stato della Response a 304. Questo algoritmo abbastanza semplice e molto generico, ma occorre creare lintera Response prima di poter calcolare lETag, che non ottimale. In altre parole, fa risparmiare banda, ma non cicli di CPU. Nella sezione Ottimizzare il codice con la validazione, mostreremo come si possa usare la validazione in modo pi intelligente, per determinare la validit di una cache senza dover fare tanto lavoro. Suggerimento: Symfony2 supporta anche gli ETag deboli, passando true come secondo parametro del metodo :method:Symfony\\Component\\HttpFoundation\\Response::setETag.

Validazione col metodo Last-Modified

Lheader Last-Modified la seconda forma di validazione. Secondo le speciche HTTP, lheader Last-Modified indica la data e lora in cui il server di origine crede che la rappresentazione sia stata modicata lultima volta. In altre parole, lapplicazione decide se il contenuto in cache sia stato modicato o meno, in base al fatto se sia stato aggiornato o meno da quando la risposta stata messa in cache. Per esempio, si pu usare la data di ultimo aggiornamento per tutti gli oggetti necessari per calcolare la rappresentazione della risorsa come valore dellheader Last-Modified:
public function showAction($articleSlug) { // ... $articleDate = new \DateTime($article->getUpdatedAt()); $authorDate = new \DateTime($author->getUpdatedAt()); $date = $authorDate > $articleDate ? $authorDate : $articleDate; $response->setLastModified($date); $response->isNotModified($this->getRequest()); return $response; }

Il metodo Response::isNotModified() confronta lheader If-Modified-Since inviato dalla richiesta con lheader Last-Modified impostato nella risposta. Se sono equivalenti, la Response sar impostata a un codice di stato 304. Nota: Lheader della richiesta If-Modified-Since equivale allheader Last-Modified dellultima risposta inviata al client per una determinata risorsa. In questo modo client e server comunicano luno con laltro e decidono se la risorsa sia stata aggiornata o meno da quando stata messa in cache.

216

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Ottimizzare il codice con la validazione

Lo scopo principale di ogni strategia di cache alleggerire il carico dellapplicazione. In altre parole, meno la propria applicazione fa per restituire una risposta 304, meglio . Il metodo Response::isNotModified() fa esattamente questo, esponendo uno schema semplice ed efciente:
public function showAction($articleSlug) { // Prende linformazione minima per calcolare // lETag o o il valore di Last-Modified // (in base alla Request, i dati sono recuperati da un // database o da una memoria chiave-valore, per esempio) $article = // ... // crea una Response con un ETag e/o un header Last-Modified $response = new Response(); $response->setETag($article->computeETag()); $response->setLastModified($article->getPublishedAt()); // Verifica che la Response non sia modificata per la Request data if ($response->isNotModified($this->getRequest())) { // restituisce subito la Response 304 return $response; } else { // qui fa pi lavoro, come recuperare altri dati $comments = // ... // o rende un template con la $response gi iniziata return $this->render( MyBundle:MyController:article.html.twig, array(article => $article, comments => $comments), $response ); } }

Quando la Response non stata modicata, isNotModified() imposta automaticamente il codice di stato della risposta a 304, rimuove il contenuto e rimuove alcuni header che non devono essere presenti in una risposta 304 (vedere :method:Symfony\\Component\\HttpFoundation\\Response::setNotModied).
Variare la risposta

Finora abbiamo ipotizzato che ogni URI avesse esattamente una singola rappresentazione della risorsa interessata. Per impostazione predenita, la cache HTTP usa lURI della risorsa come chiave. Se due persone richiedono lo stesso URI di una risorsa che si pu mettere in cache, la seconda persona ricever la versione in cache. A volte questo non basta e diverse versioni dello stesso URI hanno bisogno di stare in cache in base a uno pi header di richiesta. Per esempio, se si comprimono le pagine per i client che supportano per la compressione, ogni URI ha due rappresentazioni: una per i client col supporto e laltra per i client senza supporto. Questo viene determinato dal valore dellheader di richiesta Accept-Encoding. In questo caso, occorre mettere in cache sia una versione compressa che una non compressa della risposta di un particolare URI e restituirle in base al valore Accept-Encoding della richiesta. Lo si pu fare usando lheader di risposta Vary, che una lista separata da virgole dei diversi header i cui valori causano rappresentazioni diverse della risorsa richiesta:

2.1. Libro

217

Documentazione Symfony, Release 2.0

Vary: Accept-Encoding, User-Agent

Suggerimento: Questo particolare header Vary fa mettere in cache versioni diverse di ogni risorsa in base allURI, al valore di Accept-Encoding e allheader di richiesta User-Agent. Loggetto Response offre uninterfaccia pulita per la gestione dellheader Vary:
// imposta un header Vary $response->setVary(Accept-Encoding); // imposta diversi header Vary $response->setVary(array(Accept-Encoding, User-Agent));

Il metodo setVary() accetta un nome di header o un array di nomi di header per i quali la risposta varia.
Scadenza e validazione

Si pu ovviamente usare sia la validazione che la scadenza nella stessa Response. Poich la scadenza vince sulla validazione, si pu beneciare dei vantaggi di entrambe. In altre parole, usando sia la scadenza che la validazione, si pu istruire la cache per servire il contenuto in cache, controllando ogni tanto (la scadenza) per vericare che il contenuto sia ancora valido.
Altri metodi della risposta

La classe Response fornisce molti altri metodi per la cache. Ecco alcuni dei pi utili:
// Segna la risposta come vecchia $response->expire(); // Forza la risposta a restituire un 304 senza contenuti $response->setNotModified();

Inoltre, la maggior parte degli header HTTP relativi alla cache pu essere impostata tramite il singolo metodo setCache():
// Imposta le opzioni della cache in una sola chiamata $response->setCache(array( etag => $etag, last_modified => $date, max_age => 10, s_maxage => 10, public => true, // private => true, ));

Usare Edge Side Includes Le gateway cache sono un grande modo per rendere il proprio sito pi prestante. Ma hanno una limitazione: possono mettere in cache solo pagine intere. Se non si possono mettere in cache pagine intere o se le pagine hanno pi parti dinamiche, non vanno bene. Fortunatamente, Symfony2 fornisce una soluzione a questi casi, basata su una tecnologia chiamata ESI (http://www.w3.org/TR/esi-lang), o Edge Side Includes. Akama ha scritto le speciche quasi dieci anni fa, consentendo a determinate parti di una pagina di avere differenti strategie di cache rispetto alla pagina principale.

218

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Le speciche ESI descrivono dei tag che si possono inserire nelle proprie pagine, per comunicare col gateway cache. Lunico tag implementato in Symfony2 include, poich lunico utile nel contesto di Akama:
<html> <body> Del contenuto <!-- Inserisce qui il contenuto di unaltra pagina --> <esi:include src="http://..." /> Dellaltro contenuto </body> </html>

Nota: Si noti nellesempio che ogni tag ESI ha un URL pienamente qualicato. Un tag ESI rappresenta un frammento di pagina che pu essere recuperato tramite lURL fornito. Quando gestisce una richiesta, il gateway cache recupera lintera pagina dalla sua cache oppure la richiede dallapplicazione di backend. Se la risposta contiene uno o pi tag ESI, questi vengono processati nello stesso modo. In altre parole, la gateway cache o recupera il frammento della pagina inclusa dalla sua cache oppure richiede il frammento di pagina allapplicazione di backend. Quando tutti i tag ESI sono stati risolti, il gateway cache li fonde nella pagina principale e invia il contenuto nale al client. Tutto questo avviene in modo trasparente a livello di gateway cache (quindi fuori dalla propria applicazione). Come vedremo, se si scegli di avvalersi dei tag ESI, Symfony2 rende quasi senza sforzo il processo di inclusione.
Usare ESI in Symfony2

Per usare ESI, assicurarsi prima di tutto di abilitarlo nella congurazione dellapplicazione: YAML
# app/config/config.yml framework: # ... esi: { enabled: true }

XML
<!-- app/config/config.xml --> <framework:config ...> <!-- ... --> <framework:esi enabled="true" /> </framework:config>

PHP
// app/config/config.php $container->loadFromExtension(framework, array( // ... esi => array(enabled => true), ));

Supponiamo ora di avere una pagina relativamente statica, tranne per un elenco di news in fondo al contenuto. Con ESI, si pu mettere in cache lelenco di news indipendentemente dal resto della pagina.

2.1. Libro

219

Documentazione Symfony, Release 2.0

public function indexAction() { $response = $this->render(MyBundle:MyController:index.html.twig); $response->setSharedMaxAge(600); return $response; }

In questo esempio, abbiamo dato alla cache della pagina intera un tempo di vita di dieci minuti. Successivamente, includiamo lelenco di news nel template, includendolo in unazione. Possiamo farlo grazie allhelper render (vedere Inserire controllori per maggiori dettagli). Poich il contenuto incluso proviene da unaltra pagina (o da un altro controllore), Symfony2 usa lhelper render per congurare i tag ESI: Twig
{% render ...:news with {}, {standalone: true} %}

PHP
<?php echo $view[actions]->render(...:news, array(), array(standalone => true)) ?>

Impostando standalone a true, si dice a Symfony2 che lazione andrebbe resa come tag ESI. Ci si potrebbe chiedere perch usare un helper invece di usare direttamente il tag ESI. Il motivo che luso di un helper consente allapplicazione di funzionare anche se non c nessun gateway cache installato. Vediamo come funziona. Quando standalone false (il valore predenito), Symfony2 fonde il contenuto della pagina in quella principale, prima di inviare la risposta al client. Ma quando standalone true e se Symfony2 individua che sta parlando a una gateway cache che supporti ESI, genera un tag include di ESI. Se invece non c una gateway cache con supporto a ESI, Symfony2 fonde direttamente il contenuto della pagina inclusa dentro la pagina principale, come se standalone fosse stato impostato a false. Nota: Symfony2 individua se una gateway cache supporta ESI tramite unaltra specica di Akama, che supportata nativamente dal reverse proxy di Symfony2. Lazione inclusa ora pu specicare le sue regole di cache, del tutto indipendentemente dalla pagina principale.
public function newsAction() { // ... $response->setSharedMaxAge(60); }

Con ESI, la cache dellintera pagina sar valida per 600 secondi, mentre il componente delle news avr una cache che dura per soli 60 secondi. Un requisito di ESI, tuttavia, che lazione inclusa sia accessibile tramite un URL, in modo che il gateway cache possa recuperarla indipendentemente dal resto della pagina. Ovviamente, un URL non pu essere accessibile se non ha una rotta che punti a esso. Symfony2 si occupa di questo tramite una rotta e un controllore generici. Per poter far funzionare i tag include di ESI, occorre denire la rotta _internal: YAML
# app/config/routing.yml _internal: resource: "@FrameworkBundle/Resources/config/routing/internal.xml" prefix: /_internal

220

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

XML
<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout

<import resource="@FrameworkBundle/Resources/config/routing/internal.xml" prefix="/_internal </routes>

PHP
// app/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route;

$collection->addCollection($loader->import(@FrameworkBundle/Resources/config/routing/internal.x return $collection;

Suggerimento: Poich questa rotta consente laccesso a tutte le azioni tramite URL, si potrebbe volerla proteggere usando il rewall di Symfony2 (consentendo laccesso al range di IP del proprio reverse proxy). Vedere la sezione Sicurezza tramite IP del Capitolo sulla sicurezza per maggiori informazioni. Un grosso vantaggio di questa strategia di cache che si pu rendere la propria applicazione tanto dinamica quanto necessario e, allo stesso tempo, mantenere gli accessi al minimo. Nota: Una volta iniziato a usare ESI, si ricordi di usare sempre la direttiva s-maxage al posto di max-age. Poich il browser riceve la risorsa aggregata, non ha visibilit sui sotto-componenti, quindi obbedir alla direttiva max-age e metter in cache lintera pagina. E questo non quello che vogliamo. Lhelper render supporta due utili opzioni: alt: usato come attributo alt nel tag ESI, che consente di specicare un URL alternativo da usare, nel caso in cui src non venga trovato; ignore_errors: se impostato a true, un attributo onerror sar aggiunto a ESI con il valore di continue, a indicare che, in caso di fallimento, la gateway cache semplicemente rimuover il tag ESI senza produrre errori. Invalidazione della cache Ci sono solo due cose difcili in informatica: invalidazione della cache e nomi delle cose. Phil Karlton Non si dovrebbe mai aver bisogno di invalidare i dati in cache, perch dellinvalidazione si occupano gi nativamente i modelli di cache HTTP. Se si usa la validazione, non si avr mai bisogno di invalidare nulla, per denizione; se si usa la scadenza e si ha lesigenza di invalidare una risorsa, vuol dire che si impostata una data di scadenza troppo in l nel futuro. Nota: Essendo linvalidazione un argomento specico di ogni reverse proxy, se non ci si preoccupa dellinvalidazione, si pu cambiare reverse proxy senza cambiare alcuna parte del codice della propria applicazione.

2.1. Libro

221

Documentazione Symfony, Release 2.0

In realt, ogni reverse proxy fornisce dei modi per pulire i dati in cache, ma andrebbero evitati, per quanto possibile. Il modo pi standard pulire la cache per un dato URL richiedendolo con il metodo speciale HTTP PURGE. Ecco come si pu congurare il reverse proxy di Symfony2 per supportare il metodo HTTP PURGE:
// app/AppCache.php class AppCache extends Cache { protected function invalidate(Request $request) { if (PURGE !== $request->getMethod()) { return parent::invalidate($request); } $response = new Response(); if (!$this->getStore()->purge($request->getUri())) { $response->setStatusCode(404, Not purged); } else { $response->setStatusCode(200, Purged); } return $response; } }

Attenzione: Occorre proteggere in qualche modo il metodo HTTP PURGE, per evitare che qualcuno pulisca casualmente i dati in cache.

Riepilogo Symfony2 stato progettato per seguire le regole sperimentate della strada: HTTP. La cache non fa eccezione. Padroneggiare il sistema della cache di Symfony2 vuol dire acquisire familiarit con i modelli di cache HTTP e usarli in modo efcace. Vuol dire anche che, invece di basarsi solo su documentazione ed esempi di Symfony2, si ha accesso al mondo della conoscenza relativo alla cache HTTP e a gateway cache come Varnish. Imparare di pi con le ricette Come usare Varnish per accelerare il proprio sito

2.1.14 Traduzioni
Il termine internazionalizzazione si riferisce al processo di astrazione delle stringhe e altri pezzi specici dellapplicazione che variano in base al locale, in uno strato dove possono essere tradotti e convertiti in base alle impostazioni internazionali dellutente (ad esempio lingua e paese). Per il testo, questo signica che ognuno viene avvolto con una funzione capace di tradurre il testo (o messaggio) nella lingua dellutente:
// il testo verr *sempre* stampato in inglese echo Hello World;

// il testo pu essere tradotto nella lingua dellutente finale o per impostazione predefinita in ing echo $translator->trans(Hello World);

Nota: Il termine locale si riferisce allincirca al linguaggio dellutente e al paese. Pu essere qualsiasi stringa che lapplicazione utilizza poi per gestire le traduzioni e altre differenze di formati (ad esempio il formato di valuta). Si 222 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

consiglia di utilizzare il codice di lingua ISO639-1, un carattere di sottolineatura (_), poi il codice di paese ISO3166 (per esempio fr_FR per francese / Francia). In questo capitolo, si imparer a preparare unapplicazione per supportare pi locale e poi a creare le traduzioni per pi locale. Nel complesso, il processo ha diverse fasi comuni: 1. Abilitare e congurare il componente Translation di Symfony; 2. Astrarre le stringhe (i. messaggi) avvolgendoli nelle chiamate al Translator; 3. Creare risorse di traduzione per ogni lingua supportata che traducano tutti i messaggio dellapplicazione; 4. Determinare, impostare e gestire le impostazioni locali nella sessione. Congurazione Le traduzioni sono gestire da un servizio Translator, che utilizza i locale dellutente per cercare e restituire i messaggi tradotti. Prima di utilizzarlo, abilitare il Translator nella congurazione: YAML
# app/config/config.yml framework: translator: { fallback: en }

XML
<!-- app/config/config.xml --> <framework:config> <framework:translator fallback="en" /> </framework:config>

PHP
// app/config/config.php $container->loadFromExtension(framework, array( translator => array(fallback => en), ));

Lopzione fallback denisce il locale da utilizzare quando una traduzione non esiste nel locale dellutente. Suggerimento: Quando una traduzione non esiste per un locale, il traduttore prima prova a trovare la traduzione per la lingua (ad esempio fr se il locale fr_FR). Se non c, cerca una traduzione utilizzando il locale di ripiego. Il locale usato nelle traduzioni quello memorizzato nella sessione. Traduzione di base La traduzione del testo fatta attraverso il servizio translator (Symfony\Component\Translation\Translator). Per tradurre un blocco di testo (chiamato messaggio), usare il metodo :method:Symfony\\Component\\Translation\\Translator::trans. Supponiamo, ad esempio, che stiamo traducendo un semplice messaggio allinterno del controllore:
public function indexAction() { $t = $this->get(translator)->trans(Symfony2 is great);

2.1. Libro

223

Documentazione Symfony, Release 2.0

return new Response($t); }

Quando questo codice viene eseguito, Symfony2 tenter di tradurre il messaggio Symfony2 is great basandosi sul locale dellutente. Perch questo funzioni, bisogna dire a Symfony2 come tradurre il messaggio tramite una risorsa di traduzione, che una raccolta di traduzioni dei messaggi per un dato locale. Questo dizionario delle traduzioni pu essere creato in diversi formati, ma XLIFF il formato raccomandato: XML
<!-- messages.fr.xliff --> <?xml version="1.0"?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="file.ext"> <body> <trans-unit id="1"> <source>Symfony2 is great</source> <target>Jaime Symfony2</target> </trans-unit> </body> </file> </xliff>

PHP
// messages.fr.php return array( Symfony2 is great => J\aime Symfony2, );

YAML
# messages.fr.yml Symfony2 is great: Jaime Symfony2

Ora, se la lingua del locale dellutente il francese (per esempio fr_FR o fr_BE), il messaggio sar tradotto in Jaime Symfony2.
Il processo di traduzione

Per tradurre il messaggio, Symfony2 utilizza un semplice processo: Viene determinato il locale dellutente corrente, che memorizzato nella sessione; Un catalogo di messaggi tradotti viene caricato dalle risorse di traduzione denite per il locale (ad es. fr_FR). Vengono anche caricati i messaggi dal locale predenito e aggiunti al catalogo, se non esistono gi. Il risultato nale un grande dizionario di traduzioni. Vedere i Cataloghi di messaggi per maggiori dettagli; Se il messaggio si trova nel catalogo, viene restituita la traduzione. Se no, il traduttore restituisce il messaggio originale. Quando si usa il metodo trans(), Symfony2 cerca la stringa esatta allinterno del catalogo dei messaggi e la restituisce (se esiste).
Segnaposto per i messaggi

A volte, un messaggio contiene una variabile deve essere tradotta:

224

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

public function indexAction($name) { $t = $this->get(translator)->trans(Hello .$name); return new Response($t); }

Tuttavia, la creazione di una traduzione per questa stringa impossibile, poich il traduttore prover a cercare il messaggio esatto, includendo le parti con le variabili (per esempio Ciao Ryan o Ciao Fabien). Invece di scrivere una traduzione per ogni possibile iterazione della variabile $name, si pu sostituire la variabile con un segnaposto:
public function indexAction($name) { $t = $this->get(translator)->trans(Hello %name%, array(%name% => $name)); new Response($t); }

Symfony2 cercher ora una traduzione del messaggio raw (Hello %name%) e poi sostituir i segnaposto con i loro valori. La creazione di una traduzione fatta esattamente come prima: XML
<!-- messages.fr.xliff --> <?xml version="1.0"?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="file.ext"> <body> <trans-unit id="1"> <source>Hello %name%</source> <target>Bonjour %name%</target> </trans-unit> </body> </file> </xliff>

PHP
// messages.fr.php return array( Hello %name% => Bonjour %name%, );

YAML
# messages.fr.yml Hello %name%: Hello %name%

Nota: Il segnaposto pu assumere qualsiasi forma visto che il messaggio ricostruito utilizzando la funzione strtr (http://www.php.net/manual/en/function.strtr.php) di PHP. Tuttavia, la notazione %var% richiesta quando si traduce utilizzando i template Twig e in generale una convenzione che consigliato seguire. Come si visto, la creazione di una traduzione un processo in due fasi: 1. Astrarre il messaggio che si deve tradurre, processandolo tramite il Translator. 2. Creare una traduzione per il messaggio in ogni locale che si desideri supportare. Il secondo passo si esegue creando cataloghi di messaggi, che deniscono le traduzioni per ogni diverso locale.

2.1. Libro

225

Documentazione Symfony, Release 2.0

Cataloghi di messaggi Quando un messaggio tradotto, Symfony2 compila un catalogo di messaggi per il locale dellutente e guarda in esso per cercare la traduzione di un messaggio. Un catalogo di messaggi come un dizionario di traduzioni per uno specico locale. Ad esempio, il catalogo per il locale fr_FR potrebbe contenere la seguente traduzione: Symfony2 is Great => Jaime Symfony2 compito dello sviluppatore (o traduttore) di una applicazione internazionalizzata creare queste traduzioni. Le traduzioni sono memorizzate sul lesystem e vengono trovate da Symfony grazie ad alcune convenzioni. Suggerimento: Ogni volta che si crea una nuova risorsa di traduzione (o si installa un pacchetto che include una risorsa di traduzione), assicurarsi di cancellare la cache in modo che Symfony possa scoprire la nuova risorsa di traduzione:
php app/console cache:clear

Sedi per le traduzioni e convenzioni sui nomi

Symfony2 cerca i le dei messaggi (ad esempio le traduzioni) in due sedi: Per i messaggi trovati in un bundle, i corrispondenti le con i messaggi dovrebbero trovarsi nella cartella Resources/translations/ del bundle; Per sovrascrivere eventuali traduzioni del bundle, posizionare i le con i messaggi nella cartella app/Resources/translations. importante anche il nome del le con le traduzioni, perch Symfony2 utilizza una convenzione per determinare i dettagli sulle traduzioni. Ogni le con i messaggi deve essere nominato secondo il seguente schema: dominio.locale.caricatore: dominio: Un modo opzionale per organizzare i messaggi in gruppi (ad esempio admin, navigation o il predenito messages) - vedere Uso dei domini per i messaggi; locale: Il locale per cui sono state scritte le traduzioni (ad esempio en_GB, en, ecc.); caricatore: Come Symfony2 dovrebbe caricare e analizzare il le (ad esempio xliff, php o yml). Il caricatore pu essere il nome di un qualunque caricatore registrato. Per impostazione predenita, Symfony fornisce i seguenti caricatori: xliff: le XLIFF; php: le PHP; yml: le YAML. La scelta di quali caricatori utilizzare interamente a carico dello sviluppatore ed una questione di gusti. Nota: anche possibile memorizzare le traduzioni in una siasi altro mezzo, fornendo una classe personalizzata che Symfony\Component\Translation\Loader\LoaderInterface. base dati o in qualimplementa linterfaccia

Creazione delle traduzioni

La creazione di le di traduzione una parte importante della localizzazione (spesso abbreviata in L10n (http://it.wikipedia.org/wiki/Internazionalizzazione_e_localizzazione)). Ogni le costituito da una serie di coppie id226 Capitolo 2. Libro

Documentazione Symfony, Release 2.0

traduzione per il dato dominio e locale. Lid lidenticativo di una traduzione individuale e pu essere il messaggio nel locale principale (ad es. Symfony is great) dellapplicazione o un identicatore univoci (ad es. symfony2.great - vedere la barra laterale di seguito): XML
<!-- src/Acme/DemoBundle/Resources/translations/messages.fr.xliff --> <?xml version="1.0"?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="file.ext"> <body> <trans-unit id="1"> <source>Symfony2 is great</source> <target>Jaime Symfony2</target> </trans-unit> <trans-unit id="2"> <source>symfony2.great</source> <target>Jaime Symfony2</target> </trans-unit> </body> </file> </xliff>

PHP
// src/Acme/DemoBundle/Resources/translations/messages.fr.php return array( Symfony2 is great => J\aime Symfony2, symfony2.great => J\aime Symfony2, );

YAML
# src/Acme/DemoBundle/Resources/translations/messages.fr.yml Symfony2 is great: Jaime Symfony2 symfony2.great: Jaime Symfony2

Symfony2 trover questi le e li utilizzer quando dovr tradurre Symfony2 is great o symfony2.great in un locale di lingua francese (ad es. fr_FR o fr_BE).

2.1. Libro

227

Documentazione Symfony, Release 2.0

Utilizzare messaggi reali o parole chiave Questo esempio mostra le due diverse losoe nella creazione di messaggi che dovranno essere tradotti:
$t = $translator->trans(Symfony2 is great); $t = $translator->trans(symfony2.great);

Nel primo metodo, i messaggi vengono scritti nella lingua del locale predenito (in inglese in questo caso). Questo messaggio viene quindi utilizzato come id durante la creazione delle traduzioni. Nel secondo metodo, i messaggi sono in realt parole chiave che trasmettono lidea del messaggio.Il messaggio chiave quindi utilizzato come id per eventuali traduzioni. In questo caso, deve essere fatta anche la traduzione per il locale predenito (ad esempio per tradurre symfony2.great in Symfony2 is great). Il secondo metodo utile perch non sar necessario cambiare la chiave del messaggio in ogni le di traduzione se decidiamo che il messaggio debba essere modicato in Symfony2 is really great nel locale predenito. La scelta del metodo da utilizzare personale, ma il formato chiave spesso raccomandato. Inoltre, i formati di le php e yaml supportano gli id nidicati, per evitare di ripetersi se si utilizzano parole chiave al posto di testo reale per gli id: YAML
symfony2: is: great: Symfony2 is great amazing: Symfony2 is amazing has: bundles: Symfony2 has bundles user: login: Login

PHP
return array( symfony2 => array( is => array( great => Symfony2 is great, amazing => Symfony2 is amazing, ), has => array( bundles => Symfony2 has bundles, ), ), user => array( login => Login, ), );

I livelli multipli vengono appiattiti in singole coppie id/traduzione tramite laggiunta di un punto (.) tra ogni livello, quindi gli esempi di cui sopra sono equivalenti al seguente: YAML
symfony2.is.great: Symfony2 is great symfony2.is.amazing: Symfony2 is amazing symfony2.has.bundles: Symfony2 has bundles user.login: Login

PHP
return array( symfony2.is.great => Symfony2 is great, symfony2.is.amazing => Symfony2 is amazing, symfony2.has.bundles => Symfony2 has bundles, user.login => Login, );

228

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Uso dei domini per i messaggi Come abbiamo visto, i le dei messaggi sono organizzati nei diversi locale che vanno a tradurre. I le dei messaggi possono anche essere organizzati in domini. Quando si creano i le dei messaggi, il dominio la prima parte del nome del le. Il dominio predenito messages. Per esempio, supponiamo che, per organizzarle al meglio, le traduzioni siano state divise in tre diversi domini: messages, admin e navigation. La traduzione francese avrebbe i seguenti le per i messaggi: messages.fr.xliff admin.fr.xliff navigation.fr.xliff Quando si traducono stringhe che non sono nel dominio predenito (messages), necessario specicare il dominio come terzo parametro di trans():
$this->get(translator)->trans(Symfony2 is great, array(), admin);

Symfony2 cercher ora il messaggio del locale dellutente nel dominio admin. Gestione del locale dellutente Il locale dellutente corrente memorizzato nella sessione ed accessibile tramite il servizio session:
$locale = $request->getLocale(); $request->setLocale(en_US);

Fallback e locale predenito

Se il locale non stato impostato in modo esplicito nella sessione, sar utilizzato dal Translator il parametro di congurazione fallback_locale. Il valore predenito del parametro en (vedere Congurazione). In alternativa, possibile garantire che un locale impostato sulla sessione dellutente denendo un default_locale per il servizio di sessione: YAML
# app/config/config.yml framework: session: { default_locale: en }

XML
<!-- app/config/config.xml --> <framework:config> <framework:session default-locale="en" /> </framework:config>

PHP
// app/config/config.php $container->loadFromExtension(framework, array( session => array(default_locale => en), ));

2.1. Libro

229

Documentazione Symfony, Release 2.0

Il locale e gli URL

Dal momento che il locale dellutente memorizzato nella sessione, si pu essere tentati di utilizzare lo stesso URL per visualizzare una risorsa in pi lingue in base al locale dellutente. Per esempio, http://www.example.com/contact pu mostrare contenuti in inglese per un utente e in francese per un altro. Purtroppo questo viola una fondamentale regola del web: un particolare URL deve restituire la stessa risorsa indipendentemente dallutente. Inoltre, quale versione del contenuto dovrebbe essere indicizzata dai motori di ricerca? Una politica migliore quella di includere il locale nellURL. Questo completamente dal sistema delle rotte utilizzando il parametro speciale _locale: YAML
contact: pattern: /{_locale}/contact defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en } requirements: _locale: en|fr|de

XML
<route id="contact" pattern="/{_locale}/contact"> <default key="_controller">AcmeDemoBundle:Contact:index</default> <default key="_locale">en</default> <requirement key="_locale">en|fr|de</requirement> </route>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(contact, new Route(/{_locale}/contact, array( _controller => AcmeDemoBundle:Contact:index, _locale => en, ), array( _locale => en|fr|de ))); return $collection;

Quando si utilizza il parametro speciale _locale in una rotta, il locale corrispondente verr automaticamente impostato sulla sessione dellutente. In altre parole, se un utente visita lURI /fr/contact, il locale fr viene impostato automaticamente come locale per la sessione dellutente. ora possibile utilizzare il locale dellutente per creare rotte ad altre pagine tradotte nellapplicazione. Pluralizzazione La pluralizzazione dei messaggi un argomento un po difcile, perch le regole possono essere complesse. Per esempio, questa la rappresentazione matematica delle regole di pluralizzazione russe:
(($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) &&

Come si pu vedere, in russo si possono avere tre diverse forme plurali, ciascuna dato un indice di 0, 1 o 2. Per ciascuna forma il plurale diverso e quindi anche la traduzione diversa.

230

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Quando una traduzione ha forme diverse a causa della pluralizzazione, possibile fornire tutte le forme come una stringa separata da un pipe (|):
There is one apple|There are %count% apples

Per tradurre i messaggi pluralizzati, utilizzare il metodo :method:Symfony\\Component\\Translation\\Translator::transChoice:


$t = $this->get(translator)->transChoice( There is one apple|There are %count% apples, 10, array(%count% => 10) );

Il secondo parametro (10 in questo esempio), il numero di oggetti che vengono descritti ed usato per determinare quale traduzione da usare e anche per popolare il segnaposto %count%. In base al numero dato, il traduttore sceglie la giusta forma plurale. In inglese, la maggior parte delle parole hanno una forma singolare quando c esattamente un oggetto e una forma plurale per tutti gli altri numeri (0, 2, 3...). Quindi, se count 1, il traduttore utilizzer la prima stringa (There is one apple) come traduzione. Altrimenti user There are %count% apples. Ecco la traduzione francese:
Il y a %count% pomme|Il y a %count% pommes

Anche se la stringa simile ( fatta di due sotto-stringhe separate da un carattere pipe), le regole francesi sono differenti: la prima forma (non plurale) viene utilizzata quando count 0 o 1. Cos, il traduttore utilizzer automaticamente la prima stringa (Il y a %count% pomme) quando count 0 o 1. Ogni locale ha una propria serie di regole, con alcuni che hanno ben sei differenti forme plurali con regole complesse che descrivono quali numeri mappano le forme plurali. Le regole sono abbastanza semplici per linglese e il francese, ma per il russo, si potrebbe aver bisogno di un aiuto per sapere quali regole corrispondono alle stringhe. Per aiutare i traduttori, possibile opzionalmente etichettare ogni stringa:
one: There is one apple|some: There are %count% apples none_or_one: Il y a %count% pomme|some: Il y a %count% pommes

Le etichette sono solo aiuti per i traduttori e non inuenzano la logica usata per determinare quale plurale da usare. Le etichette possono essere una qualunque stringa che termina con due punti(:). Le etichette inoltre non hanno bisogno di essere le stesse nel messaggio originale e in quello tradotto.
Intervallo di pluralizzazione esplicito

Il modo pi semplice per pluralizzare un messaggio quello di lasciare che Symfony2 utilizzi la sua logica interna per scegliere quale stringa utilizzare sulla base di un dato numero. A volte c bisogno di pi controllo o si vuole una traduzione diversa per casi specici (per 0, o quando il conteggio negativo, ad esempio). In tali casi, possibile utilizzare espliciti intervalli matematici:

{0} There is no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There are man

Gli intervalli seguono la notazione ISO 31-11 (http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation). La suddetta stringa specica quattro diversi intervalli: esattamente 0, esattamente 1, 2-19 e 20 e superiori. inoltre possibile combinare le regole matematiche e le regole standard. In questo caso, se il numero non corrisponde ad un intervallo specico, le regole standard hanno effetto dopo aver rimosso le regole esplicite:

2.1. Libro

231

Documentazione Symfony, Release 2.0

{0} There is no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %count% ap

Ad esempio, per 1 mela, verr usata la regola standard C una mela. Per 2-19 mele, verr utilizzata la seconda regola standard Ci sono %count% mele. Symfony\Component\Translation\Interval pu rappresentare un insieme nito di numeri:
{1,2,3,4}

O numeri tra due numeri:


[1, +Inf[ ]-1,2[

Il delimitatore di sinistra pu essere [ (incluso) o ] (escluso). Il delimitatore di destra pu essere [ (escluso) o ] (incluso). Oltre ai numeri, si pu usare -Inf e +Inf per linnito. Traduzioni nei template La maggior parte delle volte, la traduzione avviene nei template. Symfony2 fornisce un supporto nativo sia per i template Twig che per i template PHP.
Template Twig

Symfony2 fornisce dei tag specici per Twig (trans e transchoice) per aiutare nella traduzione di messaggi con blocchi statici di testo:
{% trans %}Hello %name%{% endtrans %} {% transchoice count %} {0} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples {% endtranschoice %}

Il tag transchoice ottiene automaticamente la variabile %count% dal contesto corrente e la passa al traduttore. Questo meccanismo funziona solo quando si utilizza un segnaposto che segue lo schema %var%. Suggerimento: Se in una stringa necessario usare il carattere percentuale (%), escapizzarlo raddoppiandolo: {% trans %}Percent: %percent%%%{% endtrans %} inoltre possibile specicare il dominio del messaggio e passare alcune variabili aggiuntive:
{% trans with {%name%: Fabien} from "app" %}Hello %name%{% endtrans %} {% trans with {%name%: Fabien} from "app" into "fr" %}Hello %name%{% endtrans %} {% transchoice count with {%name%: Fabien} from "app" %} {0} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples {% endtranschoice %}

I ltri trans e transchoice possono essere usati per tradurre variabili di testo ed espressioni complesse:
{{ message|trans }} {{ message|transchoice(5) }} {{ message|trans({%name%: Fabien}, "app") }}

232

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

{{ message|transchoice(5, {%name%: Fabien}, app) }}

Suggerimento: Utilizzare i tag di traduzione o i ltri ha lo stesso effetto, ma con una sottile differenza: lescape automatico delloutput applicato solo alle variabili tradotte utilizzando un ltro. In altre parole, se necessario essere sicuri che la variabile tradotta non venga escapizzata, necessario applicare il ltro raw dopo il ltro di traduzione:
{# il testo tradotto tra i tag non mai sotto escape #} {% trans %} <h3>foo</h3> {% endtrans %} {% set message = <h3>foo</h3> %} {# una variabile tradotta tramite filtro sotto escape per impostazione predefinita #} {{ message|trans|raw }} {# le stringhe statiche non sono mai sotto escape #} {{ <h3>foo</h3>|trans }}

Template PHP

Il servizio di traduzione accessibile nei template PHP attraverso lhelper translator:


<?php echo $view[translator]->trans(Symfony2 is great) ?> <?php echo $view[translator]->transChoice( {0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples, 10, array(%count% => 10) ) ?>

Forzare il locale della traduzione Quando si traduce un messaggio, Symfony2 utilizza il lodale della sessione utente o il locale fallback se necessario. anche possibile specicare manualmente il locale da usare per la traduzione:
$this->get(translator)->trans( Symfony2 is great, array(), messages, fr_FR, ); $this->get(translator)->trans( {0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples, 10, array(%count% => 10), messages, fr_FR, );

2.1. Libro

233

Documentazione Symfony, Release 2.0

Tradurre contenuti da un database La traduzione del contenuto di un database dovrebbero essere gestite da Doctrine attraverso lEstensione Translatable (https://github.com/l3pp4rd/DoctrineExtensions). Per maggiori informazioni, vedere la documentazione di questa libreria. Riepilogo Con il componente Translation di Symfony2, la creazione e linternazionalizzazione di applicazioni non pi un processo doloroso e si riduce solo a pochi semplici passi:

Astrarre i messaggi dellapplicazione avvolgendoli utilizzando i metodi :method:Symfony\\Component\\Translation\\Translator::trans o :method:Symfony\\Component\\Translation\\Translato Tradurre ogni messaggio in pi locale creando dei le con i messaggi per la traduzione. Symfony2 scopre ed elabora ogni le perch i suoi nomi seguono una specica convenzione; Gestire il locale dellutente, che memorizzato nella sessione.

2.1.15 Contenitore di servizi


Una moderna applicazione PHP piena di oggetti. Un oggetto pu facilitare la consegna dei messaggi di posta elettronica, mentre un altro pu consentire di salvare le informazioni in una base dati. Nellapplicazione, possibile creare un oggetto che gestisce linventario dei prodotti, o un altro oggetto che elabora i dati da unAPI di terze parti. Il punto che una moderna applicazione fa molte cose ed organizzata in molti oggetti che gestiscono ogni attivit. In questo capitolo si parler di un oggetto speciale PHP presente in Symfony2 che aiuta a istanziare, organizzare e recuperare i tanti oggetti della propria applicazione. Questo oggetto, chiamato contenitore di servizi, permetter di standardizzare e centralizzare il modo in cui sono costruiti gli oggetti nellapplicazione. Il contenitore rende la vita pi facile, super veloce ed evidenzia unarchitettura che promuove codice riusabile e disaccoppiato. E poich tutte le classi del nucleo di Symfony2 utilizzano il contenitore, si apprender come estendere, congurare e usare qualsiasi oggetto in Symfony2. In gran parte, il contenitore dei servizi il pi grande contributore riguardo la velocit e lestensibilit di Symfony2. Inne, la congurazione e lutilizzo del contenitore di servizi semplice. Entro la ne di questo capitolo, si sar in grado di creare i propri oggetti attraverso il contenitore e personalizzare gli oggetti da un bundle di terze parti. Si inizier a scrivere codice che pi riutilizzabile, testabile e disaccoppiato, semplicemente perch il contenitore di servizi consente di scrivere facilmente del buon codice. Cos un servizio? In parole povere, un servizio un qualsiasi oggetto PHP che esegue una sorta di compito globale. un nome volutamente generico utilizzato in informatica per descrivere un oggetto che stato creato per uno scopo specico (ad esempio spedire email). Ogni servizio utilizzato in tutta lapplicazione ogni volta che si ha bisogno delle funzionalit speciche che fornisce. Non bisogna fare nulla di speciale per creare un servizio: sufciente scrivere una classe PHP con del codice che realizza un compito specico. Congratulazioni, si appena creato un servizio! Nota: Come regola generale, un oggetto PHP un servizio se viene utilizzato a livello globale nellapplicazione. Un singolo servizio Mailer usato globalmente per inviare messaggi email mentre i molti oggetti Message che spedisce non sono servizi. Allo stesso modo, un oggetto Product non un servizio, ma un oggetto che persiste oggetti Product su una base dati un servizio.

234

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Qual il discorso allora? Il vantaggio dei servizi che si comincia a pensare di separare ogni pezzo di funzionalit dellapplicazione in una serie di servizi. Dal momento che ogni servizio fa solo un lavoro, si pu facilmente accedere a ogni servizio e utilizzare le sue funzionalit ovunque ce ne sia bisogno. Ogni servizio pu anche essere pi facilmente testato e congurato essendo separato dalle altre funzionalit dellapplicazione. Questa idea si chiama architettura orientata ai servizi (http://it.wikipedia.org/wiki/Service-oriented_architecture) e non riguarda solo Symfony2 o il PHP. Strutturare la propria applicazione con una serie di classi indipendenti di servizi una nota best practice della programmazione a oggetti. Queste conoscenze sono fondamentali per essere un buon sviluppatore in quasi tutti i linguaggi. Cos un contenitore di servizi? Un contenitore di servizi (o contenitore di dependency injection) semplicemente un oggetto PHP che gestisce listanza di servizi (cio gli oggetti). Per esempio, supponiamo di avere una semplice classe PHP che spedisce messaggi email. Senza un contenitore di servizi, bisogna creare manualmente loggetto ogni volta che se ne ha bisogno:
use Acme\HelloBundle\Mailer; $mailer = new Mailer(sendmail); $mailer->send(ryan@foobar.net, ... );

Questo abbastanza facile. La classe immaginaria Mailer permette di congurare il metodo utilizzato per inviare i messaggi email (per esempio sendmail, smtp, ecc). Ma cosa succederebbe se volessimo utilizzare il servizio mailer da qualche altra parte? Certamente non si vorrebbe ripetere la congurazione del mailer ogni volta che si ha bisogno delloggetto Mailer. Cosa succederebbe se avessimo bisogno di cambiare il transport da sendmail a smtp in ogni punto dellapplicazione? Avremo bisogno di cercare ogni posto in cui si crea un servizio Mailer e cambiarlo. Creare/Congurare servizi nel contenitore Una soluzione migliore quella di lasciare che il contenitore di servizi crei loggetto Mailer per noi. Afnch questo funzioni, bisogna insegnare al contenitore come creare il servizio Mailer. Questo viene fatto tramite la congurazione, che pu essere specicata in YAML, XML o PHP: YAML
# app/config/config.yml services: my_mailer: class: Acme\HelloBundle\Mailer arguments: [sendmail]

XML
<!-- app/config/config.xml --> <services> <service id="my_mailer" class="Acme\HelloBundle\Mailer"> <argument>sendmail</argument> </service> </services>

PHP
// app/config/config.php use Symfony\Component\DependencyInjection\Definition; $container->setDefinition(my_mailer, new Definition(

2.1. Libro

235

Documentazione Symfony, Release 2.0

Acme\HelloBundle\Mailer, array(sendmail) ));

Nota: Durante linizializzazione di Symfony2, viene costruito il contenitore di servizi utilizzando la congurazione dellapplicazione (per impostazione predenita app/config/config.yml). Il le esatto che viene caricato indicato dal metodo AppKernel::registerContainerConfiguration(), che carica un le di congurazione specico per lambiente (ad esempio config_dev.yml per lambiente dev o config_prod.yml per prod). Unistanza delloggetto Acme\HelloBundle\Mailer ora disponibile tramite il contenitore di servizio. Il contenitore disponibile in qualsiasi normale controllore di Symfony2 in cui possibile accedere ai servizi del contenitore attraverso il metodo scorciatoia get():
class HelloController extends Controller { // ... public function sendEmailAction() { // ... $mailer = $this->get(my_mailer); $mailer->send(ryan@foobar.net, ... ); } }

Quando si chiede il servizio my_mailer del contenitore, il contenitore costruisce loggetto e lo restituisce. Questo un altro grande vantaggio che si ha utilizzando il contenitore di servizi. Questo signica che un servizio non mai costruito no a che non ce n bisogno. Se si denisce un servizio e non lo si usa mai su una richiesta, il servizio non verr mai creato. Ci consente di risparmiare memoria e aumentare la velocit dellapplicazione. Questo signica anche che c un calo di prestazioni basso o inesistente quando si deniscono molti servizi. I servizi che non vengono mai utilizzati non sono mai costruite. Come bonus aggiuntivo, il servizio Mailer creato una sola volta e ogni volta che si chiede per il servizio viene restituita la stessa istanza. Questo quasi sempre il comportamento di cui si ha bisogno ( pi essibile e potente), ma si imparer pi avanti come congurare un servizio che ha istanze multiple. I parametri del servizio La creazione di nuovi servizi (cio oggetti) attraverso il contenitore abbastanza semplice. Con i parametri si possono denire servizi pi organizzati e essibili: YAML
# app/config/config.yml parameters: my_mailer.class: my_mailer.transport: services: my_mailer: class: arguments:

Acme\HelloBundle\Mailer sendmail

%my_mailer.class% [%my_mailer.transport%]

XML

236

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<!-- app/config/config.xml --> <parameters> <parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter> <parameter key="my_mailer.transport">sendmail</parameter> </parameters> <services> <service id="my_mailer" class="%my_mailer.class%"> <argument>%my_mailer.transport%</argument> </service> </services>

PHP
// app/config/config.php use Symfony\Component\DependencyInjection\Definition; $container->setParameter(my_mailer.class, Acme\HelloBundle\Mailer); $container->setParameter(my_mailer.transport, sendmail); $container->setDefinition(my_mailer, new Definition( %my_mailer.class%, array(%my_mailer.transport%) ));

Il risultato nale esattamente lo stesso di prima, la differenza solo nel come stato denito il servizio. Circondando le stringhe my_mailer.class e my_mailer.transport con il segno di percentuale (%), il contenitore sa di dover cercare per parametri con questi nomi. Quando il contenitore costruito, cerca il valore di ogni parametro e lo usa nella denizione del servizio. Lo scopo dei parametri quello di inserire informazioni dei servizi. Naturalmente non c nulla di sbagliato a denire il servizio senza luso di parametri. I parametri, tuttavia, hanno diversi vantaggi: separazione e organizzazione di tutte le opzioni del servizio sotto ununica chiave parameters; i valori dei parametri possono essere utilizzati in molteplici denizioni di servizi; la creazione di un servizio in un bundle (lo mostreremo a breve), usando i parametri consente al servizio di essere facilmente personalizzabile nellapplicazione. La scelta di usare o non usare i parametri personale. I bundle di alta qualit di terze parti li utilizzeranno sempre, perch rendono i servizi memorizzati nel contenitore pi congurabili. Per i servizi della propria applicazione, tuttavia, potrebbe non essere necessaria la essibilit dei parametri.
Parametri array

I parametri non devono necessariamente essere semplici stringhe, possono anche essere array. Per il formato YAML, occorre usare lattributo type=collection per tutti i parametri che sono array. YAML
# app/config/config.yml parameters: my_mailer.gateways: - mail1 - mail2 - mail3 my_multilang.language_fallback: en:

2.1. Libro

237

Documentazione Symfony, Release 2.0

- en - fr fr: - fr - en

XML
<!-- app/config/config.xml --> <parameters> <parameter key="my_mailer.gateways" type="collection"> <parameter>mail1</parameter> <parameter>mail2</parameter> <parameter>mail3</parameter> </parameter> <parameter key="my_multilang.language_fallback" type="collection"> <parameter key="en" type="collection"> <parameter>en</parameter> <parameter>fr</parameter> </parameter> <parameter key="fr" type="collection"> <parameter>fr</parameter> <parameter>en</parameter> </parameter> </parameter> </parameters>

PHP
// app/config/config.php use Symfony\Component\DependencyInjection\Definition; $container->setParameter(my_mailer.gateways, array(mail1, mail2, mail3)); $container->setParameter(my_multilang.language_fallback, array(en => array(en, fr), fr => array(fr, en), ));

Importare altre risorse di congurazione del contenitore

Suggerimento: In questa sezione, si far riferimento ai le di congurazione del servizio come risorse. Questo per sottolineare il fatto che, mentre la maggior parte delle risorse di congurazione saranno le (ad esempio YAML, XML, PHP), Symfony2 cos essibile che la congurazione potrebbe essere caricata da qualunque parte (per esempio in una base dati o tramite un servizio web esterno). Il contenitore dei servizi costruito utilizzando una singola risorsa di congurazione (per impostazione predenita app/config/config.yml). Tutte le altre congurazioni di servizi (comprese le congurazioni del nucleo di Symfony2 e dei bundle di terze parti) devono essere importate da dentro questo le in un modo o nellaltro. Questo d una assoluta essibilit sui servizi dellapplicazione. La congurazione esterna di servizi pu essere importata in due modi differenti. Il primo, quello che verr utilizzato nelle applicazioni: la direttiva imports. Nella sezione seguente, si introdurr il secondo metodo, che il metodo pi essibile e privilegiato per importare la congurazione di servizi in bundle di terze parti.

238

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Importare la congurazione con imports

Finora, si messo la denizione di contenitore del servizio my_mailer direttamente nel le di congurazione dellapplicazione (ad esempio app/config/config.yml). Naturalmente, poich la classe Mailer stessa vive allinterno di AcmeHelloBundle, ha pi senso mettere la denizione my_mailer del contenitore dentro il bundle stesso. In primo luogo, spostare la denizione my_mailer del contenitore, in un nuovo le risorse del contenitore in AcmeHelloBundle. Se le cartelle Resources o Resources/config non esistono, crearle. YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: my_mailer.class: Acme\HelloBundle\Mailer my_mailer.transport: sendmail services: my_mailer: class: arguments:

%my_mailer.class% [%my_mailer.transport%]

XML
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter> <parameter key="my_mailer.transport">sendmail</parameter> </parameters> <services> <service id="my_mailer" class="%my_mailer.class%"> <argument>%my_mailer.transport%</argument> </service> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; $container->setParameter(my_mailer.class, Acme\HelloBundle\Mailer); $container->setParameter(my_mailer.transport, sendmail); $container->setDefinition(my_mailer, new Definition( %my_mailer.class%, array(%my_mailer.transport%) ));

Non cambiata la denizione, solo la sua posizione. Naturalmente il servizio contenitore non conosce il nuovo le di risorse. Fortunatamente, si pu facilmente importare il le risorse utilizzando la chiave imports nella congurazione dellapplicazione. YAML
# app/config/config.yml imports: - { resource: @AcmeHelloBundle/Resources/config/services.yml }

XML

2.1. Libro

239

Documentazione Symfony, Release 2.0

<!-- app/config/config.xml --> <imports> <import resource="@AcmeHelloBundle/Resources/config/services.xml"/> </imports>

PHP
// app/config/config.php $this->import(@AcmeHelloBundle/Resources/config/services.php);

La direttiva imports consente allapplicazione di includere risorse di congurazione per il contenitore di servizi da qualsiasi altro posto (in genere da bundle). La locazione resource, per i le, il percorso assoluto al le risorse. La speciale sintassi @AcmeHello risolve il percorso della cartella del bundle AcmeHelloBundle. Questo aiuta a specicare il percorso alla risorsa senza preoccuparsi in seguito, se si sposta AcmeHelloBundle in una cartella diversa.
Importare la congurazione attraverso estensioni del contenitore

Quando si sviluppa in Symfony2, si usa spesso la direttiva imports per importare la congurazione del contenitore dai bundle che sono stati creati appositamente per lapplicazione. Le congurazioni dei contenitori di bundle di terze parti, includendo i servizi del nucleo di Symfony2, di solito sono caricati utilizzando un altro metodo che pi essibile e facile da congurare nellapplicazione. Ecco come funziona. Internamente, ogni bundle denisce i propri servizi in modo molto simile a come si visto nora. Un bundle utilizza uno o pi le di congurazione delle risorse (di solito XML) per specicare i parametri e i servizi del bundle. Tuttavia, invece di importare ciascuna di queste risorse direttamente dalla congurazione dellapplicazione utilizzando la direttiva imports, si pu semplicemente richiamare una estensione del contenitore di servizi allinterno del bundle che fa il lavoro per noi. Unestensione del contenitore dei servizi una classe PHP creata dallautore del bundle con lo scopo di realizzare due cose: importare tutte le risorse del contenitore dei servizi necessarie per congurare i servizi per il bundle; fornire una semplice congurazione semantica in modo che il bundle possa essere congurato senza interagire con i parametri piatti della congurazione del contenitore dei servizi del bundle. In altre parole, una estensione dei contenitore dei servizi congura i servizi per il bundle per voi. E, come si vedr tra poco, lestensione fornisce una interfaccia sensibile e ad alto livello per congurare il bundle. Si prenda il FrameworkBundle, il bundle del nucleo del framework Symfony2, come esempio. La presenza del seguente codice nella congurazione dellapplicazione invoca lestensione del contenitore dei servizi allinterno del FrameworkBundle: YAML
# app/config/config.yml framework: secret: xxxxxxxxxx charset: UTF-8 form: true csrf_protection: true router: { resource: "%kernel.root_dir%/config/routing.yml" } # ...

XML
<!-- app/config/config.xml --> <framework:config charset="UTF-8" secret="xxxxxxxxxx"> <framework:form /> <framework:csrf-protection />

240

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

<framework:router resource="%kernel.root_dir%/config/routing.xml" /> <!-- ... --> </framework>

PHP
// app/config/config.php $container->loadFromExtension(framework, array( secret => xxxxxxxxxx, charset => UTF-8, form => array(), csrf-protection => array(), router => array(resource => %kernel.root_dir%/config/routing.php), // ... ));

Quando viene analizzata la congurazione, il contenitore cerca unestensione che sia in grado di gestire la direttiva di congurazione framework. Lestensione in questione, che vive nel FrameworkBundle, viene invocata e la congurazione del servizio per il FrameworkBundle viene caricata. Se si rimuove del tutto la chiave framework dal le di congurazione dellapplicazione, i servizi del nucleo di Symfony2 non vengono caricati. Il punto che tutto sotto controllo: il framework Symfony2 non contiene nessuna magia e non esegue nessuna azione su cui non si abbia il controllo. Naturalmente possibile fare molto di pi della semplice attivazione dellestensione del contenitore dei servizi del FrameworkBundle. Ogni estensione consente facilmente di personalizzare il bundle, senza preoccuparsi di come i servizi interni siano deniti. In questo caso, lestensione consente di personalizzare la congurazione di charset, error_handler, csrf_protection, router e di molte altre. Internamente, il FrameworkBundle usa le opzioni qui specicate per denire e congurare i servizi a esso specici. Il bundle si occupa di creare tutte i necessari parameters e services per il contenitore dei servizi, pur consentendo di personalizzare facilmente gran parte della congurazione. Come bonus aggiuntivo, la maggior parte delle estensioni dei contenitori di servizi sono anche sufcientemente intelligenti da eseguire la validazione, noticando le opzioni mancanti o con un tipo di dato sbagliato. Durante linstallazione o la congurazione di un bundle, consultare la documentazione del bundle per vedere come devono essere installati e congurati i suoi servizi. Le opzioni disponibili per i bundle del nucleo si possono trovare allinterno della guida di riferimento. Nota: Nativamente, il contenitore dei servizi riconosce solo le direttive parameters, services e imports. Ogni altra direttiva gestita dallestensione del contenitore dei servizi. Se si vogliono esporre in modo amichevole le congurazioni dei propri bundle, leggere la ricetta Come esporre una congurazione semantica per un bundle. Referenziare (iniettare) servizi Finora, il servizio my_mailer semplice: accetta un solo parametro nel suo costruttore, che facilmente congurabile. Come si vedr, la potenza reale del contenitore viene fuori quando necessario creare un servizio che dipende da uno o pi altri servizi nel contenitore. Cominciamo con un esempio. Supponiamo di avere un nuovo servizio, NewsletterManager, che aiuta a gestire la preparazione e la spedizione di un messaggio email a un insieme di indirizzi. Naturalmente il servizio my_mailer gi capace a inviare messaggi email, quindi verr usato allinterno di NewsletterManager per gestire la spedizione effettiva dei messaggi. Questa classe potrebbe essere qualcosa del genere:

2.1. Libro

241

Documentazione Symfony, Release 2.0

namespace Acme\HelloBundle\Newsletter; use Acme\HelloBundle\Mailer; class NewsletterManager { protected $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } // ... }

Senza utilizzare il contenitore di servizi, si pu creare abbastanza facilmente un nuovo NewsletterManager dentro a un controllore:
public function sendNewsletterAction() { $mailer = $this->get(my_mailer); $newsletter = new Acme\HelloBundle\Newsletter\NewsletterManager($mailer); // ... }

Questo approccio va bene, ma cosa succede se pi avanti si decide che la classe NewsletterManager ha bisogno di un secondo o terzo parametro nel costruttore? Che cosa succede se si decide di rifattorizzare il codice e rinominare la classe? In entrambi i casi si avr bisogno di cercare ogni posto in cui viene istanziata NewsletterManager e fare le modiche. Naturalmente, il contenitore dei servizi fornisce una soluzione molto migliore: YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager services: my_mailer: # ... newsletter_manager: class: %newsletter_manager.class% arguments: [@my_mailer]

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</par </parameters> <services> <service id="my_mailer" ... > <!-- ... --> </service> <service id="newsletter_manager" class="%newsletter_manager.class%"> <argument type="service" id="my_mailer"/>

242

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

</service> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference;

// ... $container->setParameter(newsletter_manager.class, Acme\HelloBundle\Newsletter\NewsletterMana $container->setDefinition(my_mailer, ... ); $container->setDefinition(newsletter_manager, new Definition( %newsletter_manager.class%, array(new Reference(my_mailer)) ));

In YAML, la sintassi speciale @my_mailer dice al contenitore di cercare un servizio chiamato my_mailer e di passare loggetto nel costruttore di NewsletterManager. In questo caso, tuttavia, il servizio specicato my_mailer deve esistere. In caso contrario, verr lanciata uneccezione. possibile contrassegnare le proprie dipendenze come opzionali (sar discusso nella prossima sezione). Lutilizzo di riferimenti uno strumento molto potente che permette di creare classi di servizi indipendenti con dipendenze ben denite. In questo esempio, il servizio newsletter_manager ha bisogno del servizio my_mailer per poter funzionare. Quando si denisce questa dipendenza nel contenitore dei servizi, il contenitore si prende cura di tutto il lavoro di istanziare degli oggetti.
Dipendenze opzionali: iniettare i setter

Iniettare dipendenze nel costruttore un eccellente modo per essere sicuri che la dipendenza sia disponibile per luso. Se per una classe si hanno dipendenze opzionali, allora liniezione dei setter pu essere una scelta migliore. Signica iniettare la dipendenza utilizzando una chiamata di metodo al posto del costruttore. La classe sar simile a questa:
namespace Acme\HelloBundle\Newsletter; use Acme\HelloBundle\Mailer; class NewsletterManager { protected $mailer; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; } // ... }

Iniettare la dipendenza con il metodo setter, necessita solo di un cambio di sintassi: YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager

2.1. Libro

243

Documentazione Symfony, Release 2.0

services: my_mailer: # ... newsletter_manager: class: %newsletter_manager.class% calls: - [ setMailer, [ @my_mailer ] ]

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</par </parameters> <services> <service id="my_mailer" ... > <!-- ... --> </service> <service id="newsletter_manager" class="%newsletter_manager.class%"> <call method="setMailer"> <argument type="service" id="my_mailer" /> </call> </service> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference;

// ... $container->setParameter(newsletter_manager.class, Acme\HelloBundle\Newsletter\NewsletterMana $container->setDefinition(my_mailer, ... ); $container->setDefinition(newsletter_manager, new Definition( %newsletter_manager.class% ))->addMethodCall(setMailer, array( new Reference(my_mailer) ));

Nota: Gli approcci presentati in questa sezione sono chiamati iniezione del costruttore e iniezione del setter. Il contenitore dei servizi di Symfony2 supporta anche iniezione di propriet.

Rendere opzionali i riferimenti A volte, uno dei servizi pu avere una dipendenza opzionale, il che signica che la dipendenza non richiesta al ne di fare funzionare correttamente il servizio. Nellesempio precedente, il servizio my_mailer deve esistere, altrimenti verr lanciata uneccezione. Modicando la denizione del servizio newsletter_manager, possibile rendere questo riferimento opzionale. Il contenitore inietter se esiste e in caso contrario non far nulla: YAML

244

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... services: newsletter_manager: class: %newsletter_manager.class% arguments: [@?my_mailer]

XML
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <services> <service id="my_mailer" ... > <!-- ... --> </service> <service id="newsletter_manager" class="%newsletter_manager.class%"> <argument type="service" id="my_mailer" on-invalid="ignore" /> </service> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerInterface;

// ... $container->setParameter(newsletter_manager.class, Acme\HelloBundle\Newsletter\NewsletterMana $container->setDefinition(my_mailer, ... ); $container->setDefinition(newsletter_manager, new Definition( %newsletter_manager.class%, array(new Reference(my_mailer, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)) ));

In YAML, la speciale sintassi @? dice al contenitore dei servizi che la dipendenza opzionale. Naturalmente, NewsletterManager deve essere scritto per consentire una dipendenza opzionale:
public function __construct(Mailer $mailer = null) { // ... }

Servizi del nucleo di Symfony e di terze parti Dal momento che Symfony2 e tutti i bundle di terze parti congurano e recuperano i loro servizi attraverso il contenitore, si possono accedere facilmente o addirittura usarli nei propri servizi. Per mantenere le cose semplici, Symfony2 per impostazione predenita non richiede che i controllori siano deniti come servizi. Inoltre Symfony2 inietta lintero contenitore dei servizi nel controllore. Ad esempio, per gestire la memorizzazione delle informazioni su una sessione utente, Symfony2 fornisce un servizio session, a cui possibile accedere dentro a un controllore standard, come segue:
public function indexAction($bar) {

2.1. Libro

245

Documentazione Symfony, Release 2.0

$session = $this->get(session); $session->set(foo, $bar); // ... }

In Symfony2, si potranno sempre utilizzare i servizi forniti dal nucleo di Symfony o dai bundle di terze parti per eseguire funzionalit come la resa di template (templating), linvio di email (mailer), o laccesso a informazioni sulla richiesta (request). Questo possiamo considerarlo come un ulteriore passo in avanti con lutilizzo di questi servizi allinterno di servizi che si creato per lapplicazione. Andiamo a modicare NewsletterManager per usare il reale servizio mailer di Symfony2 (al posto del nto my_mailer). Si andr anche a far passare il servizio con il motore dei template al NewsletterManager in modo che possa generare il contenuto dellemail tramite un template:
namespace Acme\HelloBundle\Newsletter; use Symfony\Component\Templating\EngineInterface; class NewsletterManager { protected $mailer; protected $templating; public function __construct(\Swift_Mailer $mailer, EngineInterface $templating) { $this->mailer = $mailer; $this->templating = $templating; } // ... }

La congurazione del contenitore dei servizi semplice: YAML


services: newsletter_manager: class: %newsletter_manager.class% arguments: [@mailer, @templating]

XML
<service id="newsletter_manager" class="%newsletter_manager.class%"> <argument type="service" id="mailer"/> <argument type="service" id="templating"/> </service>

PHP
$container->setDefinition(newsletter_manager, new Definition( %newsletter_manager.class%, array( new Reference(mailer), new Reference(templating) ) ));

246

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Il servizio newsletter_manager ora ha accesso ai servizi del nucleo mailer e templating. Questo un modo comune per creare servizi specici allapplicazione, in grado di sfruttare la potenza di numerosi servizi presenti nel framework. Suggerimento: Assicurarsi che la voce swiftmailer appaia nella congurazione dellapplicazione. Come stato accennato in Importare la congurazione attraverso estensioni del contenitore, la chiave swiftmailer invoca lestensione del servizio da SwiftmailerBundle, il quale registra il servizio mailer.

Congurazioni avanzate del contenitore Come si visto, denire servizi allinterno del contenitore semplice, in genere si ha bisogno della chiave di congurazione service e di alcuni parametri. Tuttavia, il contenitore ha diversi altri strumenti disponibili che aiutano ad aggiungere servizi per funzionalit speciche, creare servizi pi complessi ed eseguire operazioni dopo che il contenitore stato costruito.
Contrassegnare i servizi come pubblici / privati

Quando si deniscono i servizi, solitamente si vuole essere in grado di accedere a queste denizioni allinterno del codice dellapplicazione. Questi servizi sono chiamati public. Per esempio, il servizio doctrine registrato con il contenitore quando si utilizza DoctrineBundle un servizio pubblico dal momento che possibile accedervi tramite:
$doctrine = $container->get(doctrine);

Tuttavia, ci sono casi duso in cui non si vuole che un servizio sia pubblico. Questo capita quando un servizio denito solamente perch potrebbe essere usato come parametro per un altro servizio. Nota: Se si utilizza un servizio privato come parametro per pi di un altro servizio, questo si tradurr nellutilizzo di due istanze diverse perch listanza di un servizio privato fatta in linea (ad esempio new PrivateFooBar()). In poche parole: un servizio dovr essere privato quando non si desidera accedervi direttamente dal codice. Ecco un esempio: YAML
services: foo: class: Acme\HelloBundle\Foo public: false

XML
<service id="foo" class="Acme\HelloBundle\Foo" public="false" />

PHP
$definition = new Definition(Acme\HelloBundle\Foo); $definition->setPublic(false); $container->setDefinition(foo, $definition);

Ora che il servizio privato, non si pu chiamare:


$container->get(foo);

2.1. Libro

247

Documentazione Symfony, Release 2.0

Tuttavia, se un servizio stato contrassegnato come privato, si pu ancora farne lalias (vedere sotto) per accedere a questo servizio (attraverso lalias). Nota: I servizi per impostazione predenita sono pubblici.

Alias

Quando nella propria applicazione si utilizzano bundle del nucleo o bundle di terze parti, si possono utilizzare scorciatoie per accedere ad alcuni servizi. Si pu farlo mettendo un alias e, inoltre, si pu mettere lalias anche su servizi non pubblici. YAML
services: foo: class: Acme\HelloBundle\Foo bar: alias: foo

XML
<service id="foo" class="Acme\HelloBundle\Foo"/> <service id="bar" alias="foo" />

PHP
$definition = new Definition(Acme\HelloBundle\Foo); $container->setDefinition(foo, $definition); $containerBuilder->setAlias(bar, foo);

Questo signica che quando si utilizza il contenitore direttamente, possibile accedere al servizio foo richiedendo il servizio bar in questo modo:
$container->get(bar); // Restituir il servizio foo

Richiedere le

Ci potrebbero essere casi duso in cui necessario includere un altro le subito prima che il servizio stesso venga caricato. Per farlo, possibile utilizzare la direttiva file. YAML
services: foo: class: Acme\HelloBundle\Foo\Bar file: %kernel.root_dir%/src/percorso/del/file/foo.php

XML
<service id="foo" class="Acme\HelloBundle\Foo\Bar"> <file>%kernel.root_dir%/src/percorso/del/file/foo.php</file> </service>

PHP

248

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

$definition = new Definition(Acme\HelloBundle\Foo\Bar); $definition->setFile(%kernel.root_dir%/src/percorso/del/file/foo.php); $container->setDefinition(foo, $definition);

Notare che symfony chiamer internamente la funzione PHP require_once, il che signica che il le verr incluso una sola volta per ogni richiesta.
I tag (tags)

Allo stesso modo con cui il post di un blog su web viene etichettato con cose tipo Symfony o PHP, anche i servizi congurati nel contenitore possono essere etichettati. Nel contenitore dei servizi, un tag implica che si intende utilizzare il servizio per uno scopo specico. Si prenda il seguente esempio: YAML
services: foo.twig.extension: class: Acme\HelloBundle\Extension\FooExtension tags: - { name: twig.extension }

XML
<service id="foo.twig.extension" class="Acme\HelloBundle\Extension\FooExtension"> <tag name="twig.extension" /> </service>

PHP
$definition = new Definition(Acme\HelloBundle\Extension\FooExtension); $definition->addTag(twig.extension); $container->setDefinition(foo.twig.extension, $definition);

Il tag twig.extension un tag speciale che TwigBundle utilizza durante la congurazione. Dando al servizio il tag twig.extension, il bundle sa che il servizio foo.twig.extension dovrebbe essere registrato come estensione Twig. In altre parole, Twig cerca tutti i servizi etichettati con twig.extension e li registra automaticamente come estensioni. I tag, quindi, sono un modo per dire a Symfony2 o a un altro bundle di terze parti che il servizio dovrebbe essere registrato o utilizzato in un qualche modo speciale dal bundle. Quello che segue un elenco dei tag disponibili con i bundle del nucleo di Symfony2. Ognuno di essi ha un differente effetto sul servizio e molti tag richiedono parametri aggiuntivi (oltre al solo name del parametro). assetic.lter assetic.templating.php data_collector form.eld_factory.guesser kernel.cache_warmer kernel.event_listener monolog.logger routing.loader security.listener.factory

2.1. Libro

249

Documentazione Symfony, Release 2.0

security.voter templating.helper twig.extension translation.loader validator.constraint_validator Imparare di pi dal ricettario Usare il factory per creare servizi Gestire le dipendenza comuni con i servizi padre Denire i controllori come servizi

2.1.16 Prestazioni
Symfony2 veloce, senza alcuna modica. Ovviamente, se occorre maggiore velocit, ci sono molti modi per rendere Symfony2 ancora pi veloce. In questo capitolo, saranno esplorati molti dei modi pi comuni e potenti per rendere la propria applicazione Symfony pi veloce. Usare una cache bytecode (p.e. APC) Una delle cose migliori (e pi facili) che si possono fare per migliorare le prestazioni quella di usare una cache bytecode. Lidea di una cache bytecode di rimuove lesigenza di dover ricompilare ogni volta il codice sorgente PHP. Ci sono numerose cache bytecode (http://en.wikipedia.org/wiki/List_of_PHP_accelerators) disponibili, alcune delle quali open source. La pi usata probabilmente APC (http://php.net/manual/en/book.apc.php). Usare una cache bytecode non ha alcun effetto negativo, e Symfony2 stato progettato per avere prestazioni veramente buone in questo tipo di ambiente.
Ulteriori ottimizzazioni

Le cache bytecode solitamente monitorano i cambiamenti dei le sorgente. Questo assicura che, se la sorgente del le cambia, il bytecode sia ricompilato automaticamente. Questo molto conveniente, ma ovviamente aggiunge un overhead. Per questa ragione, alcune cache bytecode offrono unopzione per disabilitare questi controlli. Ovviamente, quando si disabilitano i controlli, sar compito dellamministratore del server assicurarsi che la cache sia svuotata a ogni modica dei le sorgente. Altrimenti, gli aggiornamenti eseguiti non saranno mostrati. Per esempio, per disabilitare questi controlli in APC, aggiungere semplicemente apc.stat=0 al proprio le di congurazione php.ini. Usare un autoloader con caches (p.e. ApcUniversalClassLoader) Per impostazione predenita, Symfony2 standard edition usa UniversalClassLoader nel le autoloader.php (https://github.com/symfony/symfony-standard/blob/master/app/autoload.php). Questo autoloader facile da usare, perch trover automaticamente ogni nuova classe inserita nelle cartella registrate. Sfortunatamente, questo ha un costo, perch il caricatore itera tutti gli spazi dei nomi congurati per trovare un particolare le, richiamando file_exists nch non trova il le cercato.

250

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

La soluzione pi semplice mettere in cache la posizione di ogni classe, dopo che stata trovata per la prima volta. Symfony dispone di una classe di caricamento, ApcUniversalClassLoader, che estende UniversalClassLoader e memorizza le posizioni delle classi in APC. Per usare questo caricatore, basta adattare il le autoloader.php come segue:
// app/autoload.php require __DIR__./../vendor/symfony/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php; use Symfony\Component\ClassLoader\ApcUniversalClassLoader; $loader = new ApcUniversalClassLoader(some caching unique prefix); // ...

Nota: Quando si usa lautoloader APC, se si aggiungono nuove classi, saranno trovate automaticamente e tutto funzioner come prima (cio senza motivi per pulire la cache). Tuttavia, se si cambia la posizione di un particolare spazio dei nomi o presso, occorrer pulire la cache di APC. Altrimenti, lautoloader cercher ancora la classe nella vecchia posizione per tutte le classi in quello spazio dei nomi.

Usare i le di avvio Per assicurare massima essibilit e riutilizzo del codice, le applicazioni Symfony2 sfruttano una variet di classi e componenti di terze parti. Ma il caricamento di tutte queste classi da diversi le a ogni richiesta pu risultate in un overhead. Per ridurre tale overhead, Symfony2 Standard Edition fornisce uno script per generare i cosiddetti le di avvio (https://github.com/sensio/SensioDistributionBundle/blob/master/Resources/bin/build_bootstrap.php), che consistono in denizioni di molte classi in un singolo le. Includendo questo le (che contiene una copia di molte classi del nucleo), Symfony non avr pi bisogno di includere alcuno dei le sorgente contenuti nelle classi stesse. Questo riduce un po la lettura/scrittura su disco. Se si usa Symfony2 Standard Edition, probabilmente si usa gi un le di avvio. Per assicurarsene, aprire il proprio front controller (solitamente app.php) e vericare che sia presente la seguente riga:
require_once __DIR__./../app/bootstrap.php.cache;

Si noti che ci sono due svantaggi nelluso di un le di avvio: il le deve essere rigenerato ogni volta che cambia una delle sorgenti originali (p.e. quando si aggiorna il sorgente di Symfony2 o le librerie dei venditori); durante il debug, occorre inserire i breakpoint nel le di avvio. Se si usa Symfony2 Standard Edition, il le di avvio ricostruito automaticamente dopo laggiornamento delle librerie dei venditori, tramite il comando php bin/vendors install.
File di avvio e cache bytecode

Anche usando una cache bytecode, le prestazioni aumenteranno con luso di un le di avvio, perch ci saranno meno le da monitorare per i cambiamenti. Certamente, se questa caratteristica disabilitata nella cache bytecode (p.e. con apc.stat=0 in APC), non c pi ragione di usare un le di avvio.

2.1.17 Interno
Se si vuole capire come funziona Symfony2 ed estenderlo, in questa sezione si potranno trovare spiegazioni approfondite dellinterno di Symfony2.

2.1. Libro

251

Documentazione Symfony, Release 2.0

Nota: La lettura di questa sezione necessaria solo per capire come funziona Symfony2 dietro le quinte oppure se si vuole estendere Symfony2.

Panoramica Il codice di Symfony2 composto da diversi livelli indipendenti. Ogni livello costruito sulla base del precedente. Suggerimento: Lauto-caricamento non viene gestito direttamente dal framework, ma indipendentemente, con laiuto della classe Symfony\Component\ClassLoader\UniversalClassLoader e del le src/autoload.php. Leggere il capitolo dedicato per maggiori informazioni.

Il componente HttpFoundation

Il livello pi profondo il componente :namespace:Symfony\\Component\\HttpFoundation. HttpFoundation fornisce gli oggetti principali necessari per trattare con HTTP. unastrazione orientata gli oggetti di alcune funzioni e variabili native di PHP: La classe Symfony\Component\HttpFoundation\Request astrae le variabili globali principali di PHP, come $_GET, $_POST, $_COOKIE, $_FILES e $_SERVER; La classe Symfony\Component\HttpFoundation\Response astrae alcune funzioni PHP, come header(), setcookie() ed echo; La classe Symfony\Component\HttpFoundation\Session e linterfaccia Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface astraggono le funzioni di gestione della sessione session_*().
Il componente HttpKernel

Sopra HttpFoundation c il componente :namespace:Symfony\\Component\\HttpKernel. HttpKernel gestisce la parte dinamica di HTTP e incapsula in modo leggero le classi Request e Response, per standardizzare il modo in cui sono gestite le richieste. Fornisce anche dei punti di estensione e degli strumenti che lo rendono il punto di partenza ideale per creare un framework web senza troppe sovrastrutture. Opzionalmente, aggiunge anche congurabilit ed estensibilit, grazie al componente Dependency Injection e a un potente sistema di plugin (bundle). Vedi anche: Approfondimento sul componente Dependency Injection e sui Bundle.
Il bundle FrameworkBundle

Il bundle :namespace:Symfony\\Bundle\\FrameworkBundle il bundle che lega insieme i componenti e le librerie principali, per fare un framework MVC leggero e veloce. Dispone in una congurazione predenita adeguata e di convenzioni che facilitano la curva di apprendimento.

252

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Kernel La classe Symfony\Component\HttpKernel\HttpKernel la classe centrale di Symfony2 ed responsabile della gestione delle richieste del client. Il suo scopo principale convertire un oggetto Symfony\Component\HttpFoundation\Request in un oggetto Symfony\Component\HttpFoundation\Response. Ogni kernel di Symfony2 implementa Symfony\Component\HttpKernel\HttpKernelInterface:
function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)

Controllori

Per convertire una Request in una Response, il kernel si appoggia a un controllore. Un controllore pu essere qualsiasi funzione o metodo PHP valido. Il kernel delega la scelta di quale controllore debba essere eseguito a unimplementazione Symfony\Component\HttpKernel\Controller\ControllerResolverInterface:
public function getController(Request $request); public function getArguments(Request $request, $controller);

di

Il metodo :method:Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController restituisce il controllore (una funzione PHP) associato alla Request data. Limplementazionoe predenita (Symfony\Component\HttpKernel\Controller\ControllerResolver) cerca un attributo _controller della richiesta, che rappresenta il nome del controllore (una stringa classe::metodo, come Bundle\BlogBundle\PostController:indexAction).

Suggerimento: Limplementazione predenita usa Symfony\Bundle\FrameworkBundle\EventListener\RouterListe per denire lattributo _controller della richista (vedere Evento kernel.request). Il metodo :method:Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments restituisce un array di parametri da passare al controllore. Limplementazione predenita risolve automaticamente i parametri, basandosi sugli attributi di Request. Parametri del controllore dai parametri della richiesta Per ciascun parametro, Symfony2 prova a prendere il valore dellattributo della richiesta che abbia lo stesso nome. Se non denito, viene usato il valore del parametro predenito, se specicato:
// Symfony2 cerca un attributo id (obbligatorio) // e uno admin (facoltativo) public function showAction($id, $admin = true) { // ... }

Gestione delle richieste

Il metodo handle() prende una Request e restituisce sempre una Response. Per convertire Request, handle() si appoggia su Resolver e su una catena ordinata di notiche di eventi (vedere la prossima sezione per maggiori informazioni sugli oggetti Event): 2.1. Libro 253

Documentazione Symfony, Release 2.0

1. Prima di tutto, viene noticato levento kernel.request, se uno degli ascoltatori restituisce una Response, salta direttamente al passo 8; 2. Viene chiamato Resolver, per decidere quale controllore eseguire; 3. Gli ascoltatori dellevento kernel.controller possono ora manipolare il controllore, nel modo che preferiscono (cambiarlo, avvolgerlo, ecc.); 4. Il kernel verica che il controllore sia effettivamente un metodo valido; 5. Viene chiamato Resolver, per decidere i parametri da passare al controllore; 6. Il kernel richiama il controllore; 7. Se il controllore non restituisce una Response, gli ascoltatori dellevento kernel.view possono convertire il valore restituito dal controllore in una Response; 8. Gli ascoltatori dellevento kernel.response possono manipolare la Response (sia il contenuto che gli header); 9. Viene restituita la risposta. Se viene lanciata uneccezione durante il processo, viene noticato levento kernel.exception e gli ascoltatori possono convertire leccezione in una risposta. Se funziona, viene noticato levento kernel.response, altrimenti leccezione viene lanciata nuovamente. Se non si vuole che le eccezioni siano catturate (per esempio per richieste incluse), disabilitare levento kernel.exception, passando false come terzo parametro del metodo handle().
Richieste interne

In qualsiasi momento, durante la gestione della richiesta (quella principale), si pu gestire una sotto-richiesta. Si pu passare il tipo di richiesta al metodo handle(), come secondo parametro: HttpKernelInterface::MASTER_REQUEST; HttpKernelInterface::SUB_REQUEST. Il tipo passato a tutti gli eventi e gli ascoltatori possono agire di conseguenza (alcuni processi possono avvenire solo sulla richiesta principale).
Eventi

Ogni evento lanciato dal kernel una sotto-classe di Symfony\Component\HttpKernel\Event\KernelEvent. Questo vuol dire che ogni evento ha accesso alle stesse informazioni di base: getRequestType() - restituisce il tipo della richiesta (HttpKernelInterface::MASTER_REQUEST o HttpKernelInterface::SUB_REQUEST); getKernel() - restituisce il kernel che gestisce la richiesta; getRequest() - restituisce la Request attualmente in gestione. getRequestType() Il metodo getRequestType() consente di sapere il tipo di richiesta. Per esempio, se un ascoltatore deve essere attivo solo per richieste principali, aggiungere il seguente codice allinizio del proprio metodo ascoltatore:

254

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

use Symfony\Component\HttpKernel\HttpKernelInterface; if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { // restituire immediatamente return; }

Suggerimento: Se non si ha familiarit con il distributore di eventi di Symfony2, leggere prima la sezione Eventi.

Evento kernel.request Classe evento: Symfony\Component\HttpKernel\Event\GetResponseEvent Lo scopo di questo evento e di restituire subito un oggetto Response oppure impostare delle variabili in modo che il controllore sia richiamato dopo levento. Qualsiasi ascoltatore pu restituire un oggetto Response, tramite il metodo setResponse() sullevento. In questo caso, tutti gli altri ascoltatori non saranno richiamati. Questo evento usato da FrameworkBundle per popolare lattributo _controller della Request, tramite Symfony\Bundle\FrameworkBundle\EventListener\RouterListener. RequestListener usa un oggetto Symfony\Component\Routing\RouterInterface per corrispondere alla Request e determinare il nome del controllore (memorizzato nellattributo _controller di Request). Evento kernel.controller Classe evento: Symfony\Component\HttpKernel\Event\FilterControllerEvent Questo evento non usato da FrameworkBundle, ma pu essere un punto di ingresso usato per modicare il controllore da eseguire:
use Symfony\Component\HttpKernel\Event\FilterControllerEvent; public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController(); // ... // il controllore pu essere cambiato da qualsiasi funzione PHP $event->setController($controller); }

Evento kernel.view Classe evento: Symfony\Component\HttpKernel\Event\GetResponseForControllerResu Questo evento non usato da FrameworkBundle, ma pu essere usato per implementare un sotto-sistema di viste. Questo evento chiamato solo se il controllore non restituisce un oggetto Response. Lo scopo dellevento di consentire a qualcun altro di restituire un valore da convertire in una Response. Il valore restituito dal controllore accessibile tramite il metodo getControllerResult:
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Symfony\Component\HttpFoundation\Response; public function onKernelView(GetResponseForControllerResultEvent $event) { $val = $event->getReturnValue(); $response = new Response(); // personalizzare in qualche modo la risposta dal valore restituito $event->setResponse($response); }

2.1. Libro

255

Documentazione Symfony, Release 2.0

Evento kernel.response Classe evento: Symfony\Component\HttpKernel\Event\FilterResponseEvent Lo scopo di questo evento di consentire ad altri sistemi di modicare o sostituire loggetto Response dopo la sua creazione:
public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); // .. modificare loggetto Response }

FrameworkBundle registra diversi ascoltatori: Symfony\Component\HttpKernel\EventListener\ProfilerListener: raccoglie dati per la richiesta corrente; Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener: serisce la barra di web debug; Symfony\Component\HttpKernel\EventListener\ResponseListener: Content-Type della risposta, in base al formato della richiesta; aggiusta inil

Symfony\Component\HttpKernel\EventListener\EsiListener: aggiunge un header HTTP Surrogate-Control quando si deve cercare dei tag ESI nella risposta.

Evento kernel.exception Classe evento: Symfony\Component\HttpKernel\Event\GetResponseForException FrameworkBundle registra un Symfony\Component\HttpKernel\EventListener\ExceptionListener, che gira la Request a un controllore dato (il valore del parametro exception_listener.controller, che deve essere nel formato classe::metodo). Un ascoltatore di questo evento pu creare e impostare un oggetto Response, creare e impostare un nuovo oggetto Exception, oppure non fare nulla:
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpFoundation\Response; public function onKernelException(GetResponseForExceptionEvent $event) { $exception = $event->getException(); $response = new Response(); // prepara loggetto Response in base alleccezione catturata $event->setResponse($response); // in alternativa si pu impostare una nuova eccezione // $exception = new \Exception(Una qualche ecccezione speciale); // $event->setException($exception); }

Il distributore di eventi Il codice orientato agli oggetti riuscito ad assicurare lestensibilit del codice. Creando classi con responsabilit ben denite, il codice diventa pi essibile e lo sviluppatore pu estendere le classi con delle sotto-classi, per modicare il loro comportamento. Ma se si vogliono condividere le modiche con altri sviluppatori che hanno fatto a loro volta delle sotto-classi, lereditariet inizia a diventare un problema. Consideriamo un esempio dal mondo reale, in cui si vuole fornire un sistema di plugin per il proprio progetto. Un plugin dovrebbe essere in grado di aggiungere metodi o di fare qualcosa prima o dopo che un altro metodo venga

256

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

eseguito, senza interferire con altri plugin. Questo non un problema facile da risolvere con lereditariet singola, mentre lereditariet multipla (ove possibile in PHP) ha i suoi difetti. Il distributore di eventi di Symfony2 implementa il pattern Observer (http://it.wikipedia.org/wiki/Observer_pattern) in un modo semplice ed efcace, per rendere possibili tutte queste cose e per rendere i propri progetti veramente estensibili. Prendiamo un semplice esempio dal componente HttpKernel di Symfony2 (https://github.com/symfony/HttpKernel). Una volta che un oggetto Response stato creato, potrebbe essere utile consentire ad altri elementi del sistema di modicarlo (p.e. aggiungere degli header per la cache) prima che sia effettivamente usato. Per poterlo fare, il kernel di Symfony2 lancia un evento, kernel.response. Ecco come funziona: Un ascoltatore (un oggetto PHP) dice alloggetto distributore centrale che vuole ascoltare levento kernel.response; A un certo punto, il kernel di Symfony2 dice alloggetto distributore di distribuire levento kernel.response, passando con esso un oggetto Event, che ha accesso alloggetto Response; Il distributore notica a (cio chiamat un metodo su) tutti gli ascoltatori dellevento kernel.response, consentendo a ciascuno di essi di effettuare modiche sulloggetto Response.
Eventi

Quando un evento viene distribuito, identicato da un nome univoco (p.e. kernel.response), a cui un numero qualsiasi di ascoltatori pu ascoltare. Inoltre, unistanza di Symfony\Component\EventDispatcher\Event viene creata e passata a tutti gli ascoltatori. Come vedremo pi avanti, loggetto stesso Event spesso contiene dati sullevento distribuito. Convenzioni sui nomi Il nome univoco di un evento pu essere una stringa qualsiasi, ma segue facoltativamente alcune piccole convenzioni sui nomi: usa solo lettere minuscole, numeri, punti (.) e sotto-linee (_); ha un presso con uno spazio dei nomi, seguito da un punto (p.e. kernel.); nisce con un verbo che indichi lazione che sta per essere eseguita (p.e. request). Ecco alcuni esempi di buoni nomi di eventi: kernel.response form.pre_set_data Nomi di eventi e oggetti evento Quando il distributore notica agli ascoltatori, passa un oggetto Event a questi ultimi. La classe base Event molto semplice: contiene un metodo per bloccare la propagazione degli eventi, non molto di pi. Spesso, occorre passare i dati su uno specico evento insieme alloggetto Event, in modo che gli ascoltatori abbiano le informazioni necessarie. Nel caso dellevento kernel.response, loggetto Event creato e passato a ciascun ascoltatore in realt di tipo Symfony\Component\HttpKernel\Event\FilterResponseEvent, una sotto-classe delloggetto base Event. Questa classe contiene metodi come getResponse e setResponse, che consentono agli ascoltatori di ottenere o anche sostituire loggetto Response. La morale della storia questa: quando si crea un ascoltatore di un evento, loggetto Event passato allascoltatore potrebbe essere una speciale sotto-classe, che possiede ulteriori metodi per recuperare informazioni dallevento e per rispondere a esso.

2.1. Libro

257

Documentazione Symfony, Release 2.0

Il distributore

Il distributore loggetto centrale del sistema di distribuzione degli eventi. In generale, viene creato un solo distributore di eventi, che mantiene un registro di ascoltatori. Quando un evento viene distribuito tramite il distributore, esso notica a tutti gli ascoltatori registrati con tale evento.
use Symfony\Component\EventDispatcher\EventDispatcher; $dispatcher = new EventDispatcher();

Connettere gli ascoltatori

Per trarre vantaggio da un evento esistente, occorre connettere un ascoltatore al distributore, in modo che possa essere noticato quando levento viene distribuito. Un chiamata al metodo addListener() del distributore associa una funzione PHP a un evento:
$listener = new AcmeListener(); $dispatcher->addListener(pippo.azione, array($listener, allAzionePippo));

Il metodo addListener() accetta no a tre parametri: Il nome dellevento (stringa) che questo ascoltatore vuole ascoltare; Una funzione PHP, che sar noticata quando viene lanciato un evento che sta ascoltando; Un intero, opzionale, di priorit (pi alto equivale a pi importante), che determina quando un ascoltatore viene avvisato, rispetto ad altri ascoltatori (il valore predenito 0). Se due ascoltatori hanno la stessa priorit, sono eseguito nello stesso ordine con cui sono stati aggiunti al distributore. Nota: Una funzione PHP (http://php.net/manual/en/language.pseudo-types.php#language.types.callback) una variabile PHP che pu essere usata dalla funzione call_user_func() e che restituisce true, se passata alla funzione is_callable(). Pu essere unistanza di una \Closure, una stringa che rappresenta una funzione oppure un array che rappresenta un metodo di un oggetto o di una classe. Finora, abbiamo visto come oggetti PHP possano essere registrati come ascoltatori. Si possono anche registrare Closure (http://php.net/manual/en/functions.anonymous.php) PHP come ascoltatori di eventi:
use Symfony\Component\EventDispatcher\Event; $dispatcher->addListener(pippo.azione, function (Event $event) { // sar eseguito quando levento pippo.azione viene distribuito });

Una volta che un ascoltatore registrato con il distributore, esso aspetta no a che levento non noticato. Nellesempio visto sopra, quando levento pippo.azione distribuito, il distributore richiama il metodo AcmeListener::allAzionePippo e passa loggetto Event come unico parametro:
use Symfony\Component\EventDispatcher\Event; class AcmeListener { // ... public function allAzionePippo(Event $event) { // fare qualcosa

258

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

} }

Suggerimento: Se si usa il framework MVC di Symfony2 MVC, gli ascoltatori possono essere registrati tramite la congurazione. Come bonus aggiuntivo, gli oggetti ascoltatori sono istanziati solo alloccorrenza. In alcuni casi, una sotto-classe speciale Event, specica dellevento dato, viene passata allascoltatore. Questo d accesso allascoltatore a informazioni speciali sullevento. Leggere la documentazione o limplementazione di ogni evento per determinare lesatta istanza di Symfony\Component\EventDispatcher\Event passata. Per esempio, levento kernel.event passa unistanza di Symfony\Component\HttpKernel\Event\FilterResponseEvent:
use Symfony\Component\HttpKernel\Event\FilterResponseEvent public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); $request = $event->getRequest(); // ... }

Creare e distribuire un evento

Oltre a registrare ascoltatori su eventi esistenti, si possono creare e lanciare eventi propri. Questo utile quando si creano librerie di terze parti e anche quando si vogliono mantenere diversi componenti personalizzati nel proprio sistema essibili e disaccoppiati. La classe statica Events Si supponga di voler creare un nuovo evento, chiamato negozio.ordine, distribuito ogni volta che un ordine viene creato dentro la propria applicazione. Per mantenere le cose organizzate, iniziamo a creare una classe StoreEvents allinterno della propria applicazione, che serve a denire e documentare il proprio evento:
namespace Acme\StoreBundle; final class StoreEvents { /** * Levento negozio.ordine lanciato ogni volta che un ordine viene creato * nel sistema. * * Lascoltatore dellevento riceve unistanza di Acme\StoreBundle\Event\FilterOrderEvent. * * * @var string */ const onStoreOrder = negozio.ordine; }

Si noti che la class in realt non fa nulla. Lo scopo della classe StoreEvents solo quello di essere un posto in cui le informazioni sugli eventi comuni possano essere centralizzate. Si noti che anche che una classe speciale FilterOrderEvent sar passata a ogni ascoltatore di questo evento.

2.1. Libro

259

Documentazione Symfony, Release 2.0

Creare un oggetto evento Pi avanti, quando si distribuir questo nuovo evento, si creer unistanza di Event e la si passer al distributore. Il distributore quindi passa questa stessa istanza a ciascuno degli ascoltatori dellevento. Se non si ha bisogno di passare informazioni agli ascoltatori, si pu usare la classe predenita Symfony\Component\EventDispatcher\Event. Tuttavia, la maggior parte delle volte, si avr bisogno di passare informazioni sullevento a ogni ascoltatore. Per poterlo fare, si creer una nuova classe, che estende Symfony\Component\EventDispatcher\Event. In questo esempio, ogni ascoltatore avr bisogno di accedere a un qualche oggetto Order. Creare una classe Event che lo renda possibile:
namespace Acme\StoreBundle\Event; use Symfony\Component\EventDispatcher\Event; use Acme\StoreBundle\Order; class FilterOrderEvent extends Event { protected $order; public function __construct(Order $order) { $this->order = $order; } public function getOrder() { return $this->order; } }

Ogni ascoltatore ora ha accesso alloggetto Order, tramite il metodo getOrder. Distribuire levento Il metodo :method:Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch notica a tutti gli ascoltatori levento dato. Accetta due parametri: il nome dellevento da distribuire e listanza di Event da passare a ogni ascoltatore di tale evento:
use Acme\StoreBundle\StoreEvents; use Acme\StoreBundle\Order; use Acme\StoreBundle\Event\FilterOrderEvent; // lordine viene in qualche modo creato o recuperato $order = new Order(); // ... // creare FilterOrderEvent e distribuirlo $event = new FilterOrderEvent($order); $dispatcher->dispatch(StoreEvents::onStoreOrder, $event);

Si noti che loggetto speciale FilterOrderEvent creato e passato al metodo dispatch. Ora ogni ascoltatore dellevento negozio.ordino ricever FilterOrderEvent e avr accesso alloggetto Order, tramite il metodo getOrder:
// una qualche classe ascoltatore che stata registrata per onStoreOrder use Acme\StoreBundle\Event\FilterOrderEvent; public function onStoreOrder(FilterOrderEvent $event) { $order = $event->getOrder();

260

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

// fare qualcosa con lordine }

Passare loggetto distributore di eventi

Se si d unocchiata alla classe EventDispatcher, si noter che non agisce come un singleton (non c un metodo statico getInstance()). Questa cosa voluta, perch si potrebbe avere necessit di diversi distributori di eventi contemporanei in una singola richiesta PHP. Ma vuol dire anche che serve un modo per passare il distributore agli oggetti che hanno bisogno di connettersi o noticare eventi. Il modo migliore iniettare loggetto distributore di eventi nei propri oggetti, quindi usare la dependency injection. Si pu usare una constructor injection:
class Foo { protected $dispatcher = null; public function __construct(EventDispatcher $dispatcher) { $this->dispatcher = $dispatcher; } }

Oppure una setter injection:


class Foo { protected $dispatcher = null; public function setEventDispatcher(EventDispatcher $dispatcher) { $this->dispatcher = $dispatcher; } }

La scelta tra i due alla ne una questione di gusti. Alcuni preferiscono la constructor injection, perch gli oggetti sono inizializzati in pieno al momento della costruzione. Ma, quando si ha una lunga lista di dipendenza, usare la setter injection pu essere il modo migliore, specialmente per le dipendenze opzionali. Suggerimento: Se si usa la dependency injection come negli esempi sopra, si pu usare il componente Dependency Injection di Symfony2 (https://github.com/symfony/DependencyInjection) per gestire questi oggetti in modo elegante.
# src/Acme/HelloBundle/Resources/config/services.yml services: foo_service: class: Acme/HelloBundle/Foo/FooService arguments: [@event_dispatcher]

Usare i sottoscrittori

Il modo pi comune per ascoltare un evento registrare un ascoltatore con il distributore. Questo ascoltatore pu ascoltare uno o pi eventi e viene noticato ogni volta che tali eventi sono distribuiti.

2.1. Libro

261

Documentazione Symfony, Release 2.0

Un altro modo per ascoltare gli eventi tramite un sottoscrittore. Un sottoscrittore di eventi una classe PHP che in grado di dire al distributore esattamente quale evento dovrebbe sottoscrivere. Implementa linterfaccia Symfony\Component\EventDispatcher\EventSubscriberInterface, che richiede un unico metodo statico, chiamato getSubscribedEvents. Si consideri il seguente esempio di un sottoscrittore, che sottoscrive gli eventi kernel.response e negozio.ordine:
namespace Acme\StoreBundle\Event; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; class StoreSubscriber implements EventSubscriberInterface { static public function getSubscribedEvents() { return array( kernel.response => onKernelResponse, negozio.ordine => onStoreOrder, ); } public function onKernelResponse(FilterResponseEvent $event) { // ... } public function onStoreOrder(FilterOrderEvent $event) { // ... } }

molto simile a una classe ascoltatore, tranne che la classe stessa pu dire al distributore quali eventi dovrebbe ascoltare. Per registrare un sottoscrittore con il distributore, usare il metodo :method:Symfony\\Component\\EventDispatcher\\EventDispatcher::addSubscriber :
use Acme\StoreBundle\Event\StoreSubscriber; $subscriber = new StoreSubscriber(); $dispatcher->addSubscriber($subscriber);

Il distributore registrer automaticamente il sottoscrittore per ciascun evento restituito dal metodo getSubscribedEvents. Questo metodo restituisce un array indicizzata per nomi di eventi e i cui valori sono o i nomi dei metodi da chiamare o array composti dal nome del metodo e da una priorit.
Bloccare il usso e la propagazione degli eventi

In alcuni casi, potrebbe aver senso che un ascoltatore prevenga il richiamo di qualsiasi altro ascoltatore. In altre parole, lascoltatore deve poter essere in grado di dire al distributore di bloccare ogni propagazione dellevento a futuri ascoltatori (cio di non noticare pi altri ascoltatori). Lo si pu fare da dentro un ascoltatore, tramite il metodo :method:Symfony\\Component\\EventDispatcher\\Event::stopPropagation:
use Acme\StoreBundle\Event\FilterOrderEvent; public function onStoreOrder(FilterOrderEvent $event) { // ...

262

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

$event->stopPropagation(); }

Ora, tutti gli ascoltatori di negozio.ordine che non sono ancora stati richiamati non saranno richiamati. Proler Se abilitato, il proler di Symfony2 raccoglie informazioni utili su ogni richiesta fatta alla propria applicazione e le memorizza per analisi successive. Luso del proler in ambienti di sviluppo aiuta il debug del proprio codice e a migliorare le prestazioni. Lo si pu usare anche in ambienti di produzione, per approfondire i problemi che si presentano. Raramente si avr a che fare direttamente con il proler, visto che Symfony2 fornisce strumenti di visualizzazione, come la barra di web debug e il proler web. Se si usa Symfony2 Standard Edition, il proler, la barra di web debug e il proler web sono gi congurati con impostazioni appropriate. Nota: Il proler raccoglie informazioni per tutte le richieste (richieste semplici, rinvii, eccezioni, richieste Ajax, richieste ESI) e per tutti i metodi e formati HTTP. Questo vuol dire che per un singolo URL si possono avere diversi dati di prole associati (uno per ogni coppia richiesta/risposta esterna).

Visualizzare i dati di prole

Usare la barra di web debug In ambiente di sviluppo, la barra di web debug disponibile in fondo a ogni pagina. Essa mostra un buon riassunto dei dati di prole, che danno accesso immediato a moltissime informazioni utili, quando qualcosa non funziona come ci si aspetta. Se il riassunto fornito dalla barra di web debug non basta, cliccare sul collegamento del token (una stringa di 13 caratteri casuali) per accedere al proler web. Nota: Se il token non cliccabile, vuol dire che le rotte del proler non sono state registrate (vedere sotto per le informazioni sulla congurazione).

Analizzare i dati di prole con il proler web Il proler web uno strumento di visualizzazione per i dati di prole, che pu essere usato in sviluppo per il debug del codice e laumento delle prestazioni. Ma lo si pu anche usare per approfondire problemi occorsi in produzione. Espone tutte le informazioni raccolte dal proler in uninterfaccia web. Accedere alle informazioni di prole Non occorre usare il visualizzatore predenito per accedere alle informazioni di prole. Ma come si possono recuperare informazioni di prole per una specica richiesta, dopo che accaduta? Quando il proler memorizza i dati su una richiesta, vi associa anche un token. Questo token disponibile nellheader HTTP X-Debug-Token della risposta:
$profile = $container->get(profiler)->loadProfileFromResponse($response); $profile = $container->get(profiler)->loadProfile($token);

Suggerimento: Quando il proler abiliato, ma non lo la barra di web debug, oppure quando si vuole il token di una richiesta Ajax, usare uno strumento come Firebug per ottenere il valore dellheader HTTP X-Debug-Token. Usare il metodo find() per accedere ai token, in base a determinati criteri:

2.1. Libro

263

Documentazione Symfony, Release 2.0

// gli ultimi 10 token $tokens = $container->get(profiler)->find(, , 10); // gli ultimi 10 token per URL che contengono /admin/ $tokens = $container->get(profiler)->find(, /admin/, 10); // gli ultimi 10 token per richieste locali $tokens = $container->get(profiler)->find(127.0.0.1, , 10);

Se si vogliono manipolare i dati di prole su macchine diverse da quella che ha generato le informazioni, usare i metodi export() e import():
// sulla macchina di produzione $profile = $container->get(profiler)->loadProfile($token); $data = $profiler->export($profile); // sulla macchina di sviluppo $profiler->import($data);

Congurazione La congurazione predenita di Symfony2 ha delle impostazioni adeguate per il proler, la barra di web debug e il proler web. Ecco per esempio la congurazione per lambiente di sviluppo: YAML
# load the profiler framework: profiler: { only_exceptions: false } # enable the web profiler web_profiler: toolbar: true intercept_redirects: true verbose: true

XML

<!-- xmlns:webprofiler="http://symfony.com/schema/dic/webprofiler" --> <!-- xsi:schemaLocation="http://symfony.com/schema/dic/webprofiler http://symfony.com/schema/dic <!-- load the profiler --> <framework:config> <framework:profiler only-exceptions="false" /> </framework:config> <!-- enable the web profiler --> <webprofiler:config toolbar="true" intercept-redirects="true" verbose="true" />

PHP
// carica il profiler $container->loadFromExtension(framework, array( profiler => array(only-exceptions => false), )); // abilita il profiler web

264

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

$container->loadFromExtension(web_profiler, array( toolbar => true, intercept-redirects => true, verbose => true, ));

Quando only-exceptions impostato a true, il proler raccoglie dati solo quando lapplicazione solleva uneccezione. Quando intercept-redirects impostata true, il proler web intercetta i rinvii e d lopportunit di guardare i dati raccolti, prima di seguire il rinvio. Quando verbose impostato a true, la barra di web debug mostra diverse informazioni. Limpostazione verbose a false nasconde alcune informazioni secondarie, per rendere la barra pi corta. Se si abilita il proler web, occorre anche montare le rotte del proler: YAML
_profiler: resource: @WebProfilerBundle/Resources/config/routing/profiler.xml prefix: /_profiler

XML
<import resource="@WebProfilerBundle/Resources/config/routing/profiler.xml" prefix="/_profiler"

PHP

$collection->addCollection($loader->import("@WebProfilerBundle/Resources/config/routing/profiler

Poich il proler aggiunge un po di sovraccarico, probabilmente lo si abiliter solo in alcune circostanze in ambiente di produzione. Limpostazione only-exceptions limita il prole alle pagine 500, ma che succede se si vogliono pi informazioni quando il client ha uno specico indirizzo IP, oppure per una parte limitata del sito? Si pu usare un matcher della richiesta: YAML
# abilita il profiler solo per richieste provenienti dalla rete 192.168.0.0 framework: profiler: matcher: { ip: 192.168.0.0/24 } # abilita il profiler solo per gli URL /admin framework: profiler: matcher: { path: "^/admin/" } # combina le regole framework: profiler: matcher: { ip: 192.168.0.0/24, path: "^/admin/" } # usa un matcher personalizzato, definito nel servizio "custom_matcher" framework: profiler: matcher: { service: custom_matcher }

XML

2.1. Libro

265

Documentazione Symfony, Release 2.0

<!-- abilita il profiler solo per richieste provenienti dalla rete 192.168.0.0 --> <framework:config> <framework:profiler> <framework:matcher ip="192.168.0.0/24" /> </framework:profiler> </framework:config> <!-- abilita il profiler solo per gli URL /admin --> <framework:config> <framework:profiler> <framework:matcher path="^/admin/" /> </framework:profiler> </framework:config> <!-- combina le regole --> <framework:config> <framework:profiler> <framework:matcher ip="192.168.0.0/24" path="^/admin/" /> </framework:profiler> </framework:config> <!-- usa un matcher personalizzato, definito nel servizio "custom_matcher" --> <framework:config> <framework:profiler> <framework:matcher service="custom_matcher" /> </framework:profiler> </framework:config>

PHP
// abilita il profiler solo per richieste provenienti dalla rete 192.168.0.0 $container->loadFromExtension(framework, array( profiler => array( matcher => array(ip => 192.168.0.0/24), ), )); // abilita il profiler solo per gli URL /admin $container->loadFromExtension(framework, array( profiler => array( matcher => array(path => ^/admin/), ), )); // combina le regole $container->loadFromExtension(framework, array( profiler => array( matcher => array(ip => 192.168.0.0/24, path => ^/admin/), ), )); # usa un matcher personalizzato, definito nel servizio "custom_matcher" $container->loadFromExtension(framework, array( profiler => array( matcher => array(service => custom_matcher), ), ));

266

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

Imparare di pi dal ricettario Come usare il prolatore nei test funzionali Come creare un raccoglitore di dati personalizzato Come estendere una classe senza usare lereditariet Come personalizzare il comportamento di un metodo senza usare lereditariet

2.1.18 LAPI stabile di Symfony2


LAPI stabile di Symfony2 un sottoinsieme di tutti i metodi pubblici di Symfony2 (componenti e bundle del nucleo) che condividono le seguenti propriet: Lo spazio dei nomi e il nome della classe non cambieranno; Il nome del metodo non cambier; La rma del metodo (i tipi dei parametri e del valore restituito) non cambier; La semantica di quello che fa il metodo non cambier; Tuttavia potrebbe cambiare limplementazione. Lunico caso valido per una modica dellAPI stabile la soluzone di una questione di sicurezza. LAPI stabile basata su una lista, con il tag @api. Quindi, tutto ci che non possiede esplicitamente il tag non fa parte dellAPI stabile. Suggerimento: Ogni bundle di terze parti dovrebbe a sua volta pubblicare la sua API stabile. A partire da Symfony 2.0, i seguenti componenti hanno un tag API pubblico: BrowserKit ClassLoader Console CssSelector DependencyInjection DomCrawler EventDispatcher Finder HttpFoundation HttpKernel Locale Process Routing Templating Translation Validator

2.1. Libro

267

Documentazione Symfony, Release 2.0

Yaml Symfony2 e fondamenti di HTTP Symfony2 contro PHP puro Installare e congurare Symfony Creare pagine in Symfony2 Il controllore Le rotte Creare e usare i template Database e Doctrine (Il modello) Test Validazione Form Sicurezza Cache HTTP Traduzioni Contenitore di servizi Prestazioni Interno LAPI stabile di Symfony2 Symfony2 e fondamenti di HTTP Symfony2 contro PHP puro Installare e congurare Symfony Creare pagine in Symfony2 Il controllore Le rotte Creare e usare i template Database e Doctrine (Il modello) Test Validazione Form Sicurezza Cache HTTP Traduzioni Contenitore di servizi Prestazioni Interno

268

Capitolo 2. Libro

Documentazione Symfony, Release 2.0

LAPI stabile di Symfony2

2.1. Libro

269

Documentazione Symfony, Release 2.0

270

Capitolo 2. Libro

CAPITOLO 3

Ricettario

3.1 Ricettario
3.1.1 Come creare e memorizzare un progetto Symfony2 in git
Suggerimento: Sebbene questa guida riguardi nello specico git, gli stessi principi valgono in generale se si memorizza un progetto in Subversion. Una volta letto Creare pagine in Symfony2 e preso familiarit con luso di Symfony, si vorr certamente iniziare un proprio progetto. In questa ricetta si imparer il modo migliore per iniziare un nuovo progetto Symfony2, memorizzato usando il sistema di controllo dei sorgenti git (http://git-scm.com/). Preparazione del progetto Per iniziare, occorre scaricare Symfony e inizializzare il repository locale: 1. Scaricare Symfony2 Standard Edition (http://symfony.com/download) senza venditori. 2. Scompattare la distribuzione. Questo creer una cartella chiamata Symfony con la struttura del nuovo progetto, i le di congurazione, ecc. Si pu rinominare la cartella a piacere. 3. Creare un nuovo le chiamato .gitignore nella radice del nuovo progetto (ovvero vicino al le deps) e copiarvi le righe seguenti. I le corrispondenti a questi schemi saranno ignorati da git:
/web/bundles/ /app/bootstrap* /app/cache/* /app/logs/* /vendor/ /app/config/parameters.ini

4. Copiare app/config/parameters.yml in app/config/parameters.yml.dist. Il le parameters.yml ignorato da git (vedi sopra), quindi le impostazioni speciche della macchina, come le password del database, non saranno inviate. Creando il le parameters.yml.dist, i nuovi sviluppatori potranno clonare rapidamente il progetto, copiando questo le in parameters.yml e personalizzandolo. 5. Inizializzare il proprio repository git:

271

Documentazione Symfony, Release 2.0

$ git init

6. Aggiungere tutti i le in git:


$ git add .

7. Creare un commit iniziale con il nuovo progetto:


$ git commit -m "Commit iniziale"

8. Inne, scaricare tutte le librerie dei venditori:


$ php bin/vendors install

A questo punto, si ha un progetto Symfony2 pienamente funzionante e correttamente copiato su git. Si pu iniziare subito a sviluppare, inviando i commit delle modiche al proprio repository git. Suggerimento: Dopo aver eseguito il comando:
$ php bin/vendors install

il progetto conterr la cronologia completa di tutt i bundle e le librerie denite nel le deps. Potrebbero essere anche 100 MB! Si possono cancellare le cartelle della cronologia di git con il comando seguente:
$ find vendor -name .git -type d | xargs rm -rf

Il comando cancella tutte le cartelle .git contenute nella cartella vendor. Se successivamente si vogliono aggiornare i bundle deniti nel le deps, occorrer installarli nuovamente:
$ php bin/vendors install --reinstall

Si pu continuare a seguire il capitolo Creare pagine in Symfony2 per imparare di pi su come congurare e sviluppare la propria applicazione. Suggerimento: Symfony2 Standard Edition distribuito con alcuni esempi di funzionamento. Per rimuovere il codice di esempio, seguire le istruzioni nel le Readme di Standard Edition (https://github.com/symfony/symfonystandard/blob/master/README.md).

Gestire le librerie dei venditori con bin/vendors e deps ### Come funziona? Ogni progetto Symfony usa un gruppo di librerie di venditori. In un modo o nellaltro, lo scopo scaricare tali le nella propria cartella vendor/ e, idealmente, avere un modo tranquillo per gestire lesatta versione necessaria per ciascuno. Per impostazione predenita, tali librerie sono scaricate eseguendo uno script scaricatore php bin/vendors install. Questo script legge dal le deps nella radice del proprio progetto. Questo uno script in formato ini, che contiene una lista di ogni libreria necessaria, la cartella in cui ognuna va scaricata e (opzionalmente) la versione da scaricare. Lo script bin/vendors usa git per scaricare, solamente perch queste librerie esterne solitamente sono memorizzate tramite git. Lo script bin/vendors legge anche il le deps.lock, che consente di bloccare ogni libreria a un preciso hash di commit. importante capire che queste librerie di venditori non sono in realt parte del proprio repository. Sono invece dei semplici le non tracciati, che sono scaricati dallo script bin/vendors nella cartella vendor/. Ma, poich

272

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

ogni informazione necessaria a scaricare tali le nei le deps e deps.lock (che sono memorizzati nel proprio repository), ogni altro sviluppatore pu usare il progetto, eseguendo php bin/vendors install e scaricando lo stesso preciso insieme di librerie di venditori. Questo vuol dire che si pu controllare con precisione ogni libreria di venditore, senza dover in realt inserirle nel proprio repository. Quindi, ogni volta che uno sviluppatore usa il progetto, deve eseguire lo script php bin/vendors install, per assicurarsi di avere tutt le librerie necessarie. Aggiornare Symfony Poich Symfony non altro che un gruppo di librerie di terze parti e le librerie di terze parti sono interamente controllate tramite deps e deps.lock, aggiornare Symfony vuol dire semplicemente aggiornare questi due le, per far corrispondere il loro stato a quello dellultima Standard Edition di Symfony. Ovviamente, se sono state aggiunte nuove voci a deps o deps.lock, assicurarsi di sostituire solo le parti originali (cio assicurarsi di non cancellare alcuna delle proprie voci). Attenzione: C anche un comando php bin/vendors update, ma non ha niente a che fare con laggiornamento del progetto e solitamente non sar necessario usarlo. Questo comando usato per congelare le versioni di tutte le librerie dei venditori, aggiornandole alle versioni specicate in deps e registrandole nel le deps.lock. ### Modicare le librerie dei venditori A volte, si ha bisogno di un ramo specico, oppure di un tag o di un commit di una libreria. Lo si pu impostare direttamente nel le deps:
[AcmeAwesomeBundle] git=http://github.com/johndoe/Acme/AwesomeBundle.git target=/bundles/Acme/AwesomeBundle version=una-versione-aggiornata

Lopzionegit imposta lURL della libreria. Pu essere anche un altro protocollo, come http:// o anche git://. Lopzione target specica dove star il repository: i semplici bundle di Symfony andrebbero sotto la cartella vendor/bundles/Acme, altre librerie di terze parti solitamente vanno in vendor/la-mia-libreria. La cartella di destinazione predenita su questultima opzione, se non specicata. Lopzione version consente di imposare una versione specica. Si pu usare un tag (version=origin/0.42) o un ramo (refs/remotes/origin/awesome-branch). valore predenito origin/HEAD. ### Flusso di aggiornamento Quando si esegue php bin/vendors install, per ogni libreria, lo script verica prima se esiste la cartella di installazione. Se non esiste (e SOLO se non esiste), esegue un git clone. Quindi, esegue un git fetch origin e un git reset --hard la-mia-versione. Questo vuole dire che il repository sar clonato una sola volta. Se si vuole eseguire una modica sui remote di git, si DEVE cancellare lintera cartella di destionazione, non solo il suo contenuto.
Venditori e sotto-moduli

Il

Invece di usare il sistema basato su deps e bin/vendors per gestire le librerie dei venditori, si potrebbe invece voler usare i sotto-moduli di git (http://book.git-scm.com/5_submodules.html). Non c nulla di sbagliato in questo

3.1. Ricettario

273

Documentazione Symfony, Release 2.0

approccio, ma il sistema deps la via ufciale per risolvere questo problema e i sotto-moduli di git possono a volte creare delle difcolt. Memorizzare il progetto su un server remoto Si ora in possesso di un progetto Symfony2 pienamente funzionante e copiato in git. Tuttavia, spesso si vuole memorizzare il proprio progetto un server remoto, sia per motivi di backup, sia per fare in modo che altri sviluppatori possano collaborare al progetto. Il modo pi facile per memorizzare il proprio progetto su un server remoto lutilizzo di GitHub (https://github.com/). I repository pubblici sono gratuiti, mentre per quelli privati necessario pagare mensilmente. In alternativa, si pu ospitare un proprio repository git su un qualsiasi server, creando un repository privato (http://progit.org/book/ch4-4.html) e usando quello. Una libreria che pu aiutare in tal senso Gitolite (https://github.com/sitaramc/gitolite).

3.1.2 Come creare e memorizzare un progetto Symfony2 in Subversion


Suggerimento: Questa voce specica per Subversion e si basa sui principi di Come creare e memorizzare un progetto Symfony2 in git. Una volta letto Creare pagine in Symfony2 e aver preso familiarit con luso di Symfony, si senza dubbio pronti per iniziare il proprio progetto. Il metodo preferito per gestire progetti Symfony2 luso di git (http://git-scm.com/), ma qualcuno preferisce usare Subversion (http://subversion.apache.org/), che va totalmente bene! In questa ricetta, vedremo come gestire il proprio progetto usando svn (http://subversion.apache.org/), in modo simile a quanto si farebbe con git (http://git-scm.com/). Suggerimento: Questo un metodo per memorizzare il proprio progetto Symfony2 in un repository Subversion. Ci sono molti modi di farlo e questo semplicemente uno che funziona.

Il repository Subversion Per questa ricetta, supporremo che lo schema del repository segua la struttura standard, molto diffusa:
mio_progetto/ branches/ tags/ trunk/

Suggerimento: La maggior parte degli host con subversion dovrebbero seguire questa pratica. Questo lo schema raccomandato in Controllo di versione con Subversion (http://svnbook.red-bean.com/) e quello usato da quasi tutti gli host gratuiti (vedere Soluzioni di hosting subversion).

Preparazione del progetto Per iniziare, occorre scaricare Symfony2 e preparare Subversion: 1. Scaricare Symfony2 Standard Edition (http://symfony.com/download), con o senza venditori.

274

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

2. Scompattare la distribuzione. Questo creer una cartella chiamata Symfony, con la struttura del nuovo progetto, i le di congurazione, ecc. Rinominarla con il nome che si desidera. 3. Eseguire il checkout del repository Subversion che ospiter questo progetto. Supponiamo che sia ospitato su Google code (http://code.google.com/hosting/) e che si chiami mioprogetto:
$ svn checkout http://mioprogetto.googlecode.com/svn/trunk mioprogetto

4. Copiare i le del progetto Symfony2 nella cartella di subversion:


$ mv Symfony/* mioprogetto/

5. Impostiamo ora le regole di ignore. Non tutto andrebbe memorizzato nel repository subversion. Alcuni le (come la cache) sono generati e altri (come la congurazione del database) devono essere personalizzati su ciascuna macchina. Ci implica luso della propriet svn:ignore, che consente di ignorare specici le.
$ cd mioprogetto/ $ svn add --depth=empty app app/cache app/logs app/config web $ $ $ $ $ svn svn svn svn svn propset propset propset propset propset svn:ignore svn:ignore svn:ignore svn:ignore svn:ignore "vendor" . "bootstrap*" app/ "parameters.ini" app/config/ "*" app/cache/ "*" app/logs/

$ svn propset svn:ignore "bundles" web

$ svn ci -m "commit della lista di ignore di Symfony (vendor, app/bootstrap*, app/config/paramet

6. Tutti gli altri le possono essere aggiunti al progetto:


$ svn add --force . $ svn ci -m "aggiunta Symfony Standard 2.X.Y"

7. Copiare app/config/parameters.ini su app/config/parameters.ini.dist. Il le parameters.ini ignorato da svn (vedere sopra) in modo che le impostazioni delle singole macchine, come le password del database, non siano inserite. Creando il le parameters.ini.dist, i nuovi sviluppatori possono prendere subito il progetto, copiare questo le in parameters.ini, personalizzarlo e iniziare a sviluppare. 8. Inne, scaricare tutte le librerie dei venditori:
$ php bin/vendors install

Suggerimento: git (http://git-scm.com/) deve essere installato per poter eseguire bin/vendors, essendo il protocollo usato per recuperare le librerie. Questo vuol dire che git usato solo come strumento per poter scaricare le librerie nella cartella vendor/. A questo punto, si ha un progetto Symfony2 pienamente funzionante, memorizzato nel proprio repository Subversion. Si pu iniziare lo sviluppo, con i commit verso il repository. Si pu continuare a seguire il capitolo Creare pagine in Symfony2 per imparare di pi su come congurare e sviluppare la propria applicazione. Suggerimento: La Standard Edition di Symfony2 ha alcune funzionalit di esempio. Per rimuovere il codice di esempio, seguire le istruzioni nel Readme della Standard Edition (https://github.com/symfony/symfonystandard/blob/master/README.md).

3.1. Ricettario

275

Documentazione Symfony, Release 2.0

Gestire le librerie dei venditori con bin/vendors e deps ### Come funziona? Ogni progetto Symfony usa un gruppo di librerie di venditori. In un modo o nellaltro, lo scopo scaricare tali le nella propria cartella vendor/ e, idealmente, avere un modo tranquillo per gestire lesatta versione necessaria per ciascuno. Per impostazione predenita, tali librerie sono scaricate eseguendo uno script scaricatore php bin/vendors install. Questo script legge dal le deps nella radice del proprio progetto. Questo uno script in formato ini, che contiene una lista di ogni libreria necessaria, la cartella in cui ognuna va scaricata e (opzionalmente) la versione da scaricare. Lo script bin/vendors usa git per scaricare, solamente perch queste librerie esterne solitamente sono memorizzate tramite git. Lo script bin/vendors legge anche il le deps.lock, che consente di bloccare ogni libreria a un preciso hash di commit. importante capire che queste librerie di venditori non sono in realt parte del proprio repository. Sono invece dei semplici le non tracciati, che sono scaricati dallo script bin/vendors nella cartella vendor/. Ma, poich ogni informazione necessaria a scaricare tali le nei le deps e deps.lock (che sono memorizzati nel proprio repository), ogni altro sviluppatore pu usare il progetto, eseguendo php bin/vendors install e scaricando lo stesso preciso insieme di librerie di venditori. Questo vuol dire che si pu controllare con precisione ogni libreria di venditore, senza dover in realt inserirle nel proprio repository. Quindi, ogni volta che uno sviluppatore usa il progetto, deve eseguire lo script php bin/vendors install, per assicurarsi di avere tutt le librerie necessarie. Aggiornare Symfony Poich Symfony non altro che un gruppo di librerie di terze parti e le librerie di terze parti sono interamente controllate tramite deps e deps.lock, aggiornare Symfony vuol dire semplicemente aggiornare questi due le, per far corrispondere il loro stato a quello dellultima Standard Edition di Symfony. Ovviamente, se sono state aggiunte nuove voci a deps o deps.lock, assicurarsi di sostituire solo le parti originali (cio assicurarsi di non cancellare alcuna delle proprie voci). Attenzione: C anche un comando php bin/vendors update, ma non ha niente a che fare con laggiornamento del progetto e solitamente non sar necessario usarlo. Questo comando usato per congelare le versioni di tutte le librerie dei venditori, aggiornandole alle versioni specicate in deps e registrandole nel le deps.lock. ### Modicare le librerie dei venditori A volte, si ha bisogno di un ramo specico, oppure di un tag o di un commit di una libreria. Lo si pu impostare direttamente nel le deps:
[AcmeAwesomeBundle] git=http://github.com/johndoe/Acme/AwesomeBundle.git target=/bundles/Acme/AwesomeBundle version=una-versione-aggiornata

Lopzionegit imposta lURL della libreria. Pu essere anche un altro protocollo, come http:// o anche git://. Lopzione target specica dove star il repository: i semplici bundle di Symfony andrebbero sotto la cartella vendor/bundles/Acme, altre librerie di terze parti solitamente vanno in vendor/la-mia-libreria. La cartella di destinazione predenita su questultima opzione, se non specicata. Lopzione version consente di imposare una versione specica. Si pu usare un tag (version=origin/0.42) o un ramo (refs/remotes/origin/awesome-branch). valore predenito origin/HEAD. Il

276

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

### Flusso di aggiornamento Quando si esegue php bin/vendors install, per ogni libreria, lo script verica prima se esiste la cartella di installazione. Se non esiste (e SOLO se non esiste), esegue un git clone. Quindi, esegue un git fetch origin e un git reset --hard la-mia-versione. Questo vuole dire che il repository sar clonato una sola volta. Se si vuole eseguire una modica sui remote di git, si DEVE cancellare lintera cartella di destionazione, non solo il suo contenuto. Soluzioni di hosting subversion La differenza maggiore tra git (http://git-scm.com/) e svn (http://subversion.apache.org/) che Subversion necessita di un repository centrale per funzionare. Ci sono diverse soluzioni: Hosting autonomo: creare il proprio repository e accedervi tramite lesystem o tramite rete. Per maggiori informazioni, leggere Controllo di versione con Subversion (http://svnbook.red-bean.com/). Hosting di terze parti: ci sono molte buone soluzioni di hosting gratuito a disposizione, come GitHub (http://github.com/), Google code (http://code.google.com/hosting/), SourceForge (http://sourceforge.net/) o Gna (http://gna.org/). Alcune di queste offrono anche hosting git.

3.1.3 Come personalizzare le pagine di errore


Quando in Symfony2 viene lanciata una qualsiasi eccezione, leccezione viene catturata allinterno della classe Kernel ed eventualmente inoltrata a un controllore speciale, TwigBundle:Exception:show per la gestione. Questo controllore, che vive allinterno del core TwigBundle, determina quale template di errore visualizzare e il codice di stato che dovrebbe essere impostato per la data eccezione. Le pagine di errore possono essere personalizzate in due diversi modi, a seconda di quanto controllo si vuole avere: 1. Personalizzare i template di errore delle diverse pagine di errore (spiegato qua sotto); 2. Sostituire il controllore predenito delle eccezioni TwigBundle::Exception:show con il proprio controllore e gestirlo come si vuole (vedere exception_controller nella guida di riferimento di Twig); Suggerimento: La personalizzazione della gestione delle eccezioni in realt molto pi potente di quanto scritto qua. Viene lanciato un evento interno, kernel.exception, che permette un controllo completo sulla gestione delle eccezioni. Per maggiori informazioni, vedere Evento kernel.exception. Tutti i template degli errori sono presenti allinterno di TwigBundle. Per sovrascrivere i template, si pu semplicemente utilizzare il metodo standard per sovrascrivere i template che esistono allinterno di un bundle. Per maggiori informazioni, vedere Sovrascrivere template dei bundle. Ad esempio, per sovrascrivere il template di errore predenito che mostrato allutente nale, creare un nuovo template posizionato in app/Resources/TwigBundle/views/Exception/error.html.twig:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Si verificato un errore: {{ status_text }}</title> </head> <body> <h1>Oops! Si verificato un errore</h1> <h2>Il server ha restituito un "{{ status_code }} {{ status_text }}".</h2>

3.1. Ricettario

277

Documentazione Symfony, Release 2.0

</body> </html>

Suggerimento: Non bisogna preoccuparsi, se non hai familiarit con Twig. Twig un semplice, potente e opzionale motore per i template che si integra con Symfony2. Per maggiori informazioni su Twig vedere Creare e usare i template. In aggiunta alla pagina di errore standard HTML, Symfony fornisce una pagina di errore predenita per molti dei formati di risposta pi comuni, tra cui JSON (error.json.twig), XML, (error.xml.twig) e anche Javascript (error.js.twig), per citarne alcuni. Per sovrascrivere uno di questi template, basta creare un nuovo le con lo stesso nome nella cartella app/Resources/TwigBundle/views/Exception. Questo il metodo standard per sovrascrivere qualunque template posizionato dentro a un bundle. Personalizzazione della pagina 404 e di altre pagine di errore anche possibile personalizzare specializzare specici template di errore in base al codice di stato. Per esempio, creare un template app/Resources/TwigBundle/views/Exception/error404.html.twig per visualizzare una pagina speciale per gli errori 404 (pagina non trovata). Symfony utilizza il seguente algoritmo per determinare quale template deve usare: Prima, cerca un template per il dato formato e codice di stato (tipo error404.json.twig); Se non esiste, cerca un per il dato formato (tipo error.json.twig); Se non esiste, si ricade nel template HTML (tipo error.html.twig). Suggerimento: Per vedere lelenco completo dei template di errore predeniti, vedere la cartella Resources/views/Exception del TwigBundle. In una installazione standard di Symfony2, il TwigBundle pu essere trovato in vendor/symfony/src/Symfony/Bundle/TwigBundle. Spesso, il modo pi semplice per personalizzare una pagina di errore quello di copiarlo da TwigBundle in app/Resources/TwigBundle/views/Exception e poi modicarlo.

Nota: Le pagine amichevoli di debug delle eccezione mostrate allo sviluppatore possono anche loro essere personalizzate nello stesso modo creando template come exception.html.twig per la pagina di eccezione standard in HTML o exception.json.twig per la pagina di eccezione JSON.

3.1.4 Denire i controllori come servizi


Nel libro, abbiamo imparato quanto facile usare un controllore quando estende la classe base Symfony\Bundle\FrameworkBundle\Controller\Controller. Oltre a questo metodo, i controllori possono anche essere specicati come servizi. Per fare rifermento a un controllore denito come servizio, usare la notazione con un solo due punti (:). Per esempio, si supponga di aver denito un servizio chiamato mio controllore e che si voglia rimandare a un metodo chiamato indexAction() allinterno di tale servizio:
$this->forward(mio_controllore:indexAction, array(pippo => $pluto));

Occorre usare la stessa notazione, quando si denisce il valore _controller della rotta:

278

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

mio_controllore: pattern: / defaults: { _controller: mio_controllore:indexAction }

Per usare un controllore in questo modo, deve essere denito nella congurazione del contenitore di servizi. Per ulteriori informazioni, si veda il capitolo Contenitore di servizi. Quando si usa un controllore denito come servizio, esso probabilmente non estender la classe base Controller. Invece di appoggiarsi ai metodi scorciatoia di tale classe, si interagir direttamente coi servizi necessari. Fortunatamente, questo di solito abbastanza facile e la classe Controller una grande risorsa per sapere come eseguire i compiti pi comuni. Nota: Specicare un controllore come servizio richiede un po pi di lavoro. Il vantaggio principale che lintero controllore o qualsiasi servizio passato al controllore possono essere modicati tramite la congurazione del contenitore di servizi. Questo particolarmente utile quando si sviluppa un bundle open source o un bundle che sar usato in progetti diversi. Quindi, anche non specicando i propri controllori come servizi, probabilmente si vedr questo aspetto in diversi bundle open source di Symfony2.

3.1.5 Come forzare le rotte per utilizzare sempre HTTPS


A volte, si desidera proteggere alcune rotte ed essere sicuri che siano sempre accessibili solo tramite il protocollo HTTPS. Il componente Routing consente di forzare lo schema HTTP attraverso il requisito _scheme: YAML
secure: pattern: /secure defaults: { _controller: AcmeDemoBundle:Main:secure } requirements: _scheme: https

XML
<?xml version="1.0" encoding="UTF-8" ?>

<routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="secure" pattern="/secure"> <default key="_controller">AcmeDemoBundle:Main:secure</default> <requirement key="_scheme">https</requirement> </route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(secure, new Route(/secure, array( _controller => AcmeDemoBundle:Main:secure, ), array( _scheme => https, )));

3.1. Ricettario

279

Documentazione Symfony, Release 2.0

return $collection;

La congurazione sopra forza la rotta secure a utilizzare sempre HTTPS. Quando si genera lURL secure e se lo schema corrente HTTP, Symfony generer automaticamente un URL assoluto con HTTPS come schema:
# Se lo schema corrente HTTPS {{ path(secure) }} # generates /secure # Se lo schema corrente HTTP {{ path(secure) }} # generates https://example.com/secure

Lesigenza anche quella di forzare le richieste in arrivo. Se si tenta di accedere al percorso /secure con HTTP, si verr automaticamente rinviati allo stesso URL, ma con lo schema HTTPS. Lesempio precedente utilizza https per _scheme, ma si pu anche forzare un URL per usare sempre http. Nota: La componente di sicurezza fornisce un altro modo per forzare lo schema HTTP, tramite limpostazione requires_channel. Questo metodo alternativo pi adatto per proteggere unarea del sito web (tutti gli URL sotto /admin) o quando si vuole proteggere URL deniti in un bundle di terze parti.

3.1.6 Come permettere un carattere / in un parametro di rotta


A volte necessario comporre URL con parametri che possono contenere una barra /. Per esempio, prendiamo la classica rotta /hello/{name}. Per impostazione predenita, /hello/Fabien corrisponder a questa rotta, ma non /hello/Fabien/Kris. Questo dovuto al fatto che Symfony utilizza questo carattere come separatore tra le parti delle rotte. Questa guida spiega come modicare una rotta in modo che /hello/Fabien/Kris corrisponda alla rotta /hello/{name}, dove {name} vale Fabien/Kris. Congurare la rotta Per impostazione predenita, il componente delle rotte di symfony richiede che i parametri corrispondano alla seguente espressione regolare: [^/]+. Questo signica che tutti i caratteri sono permessi eccetto /. Bisogna consentire esplicitamente che il carattere / possa far parte del parametro specicando una espressione regolare pi permissiva. YAML
_hello: pattern: /hello/{name} defaults: { _controller: AcmeDemoBundle:Demo:hello } requirements: name: ".+"

XML
<?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

280

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/rout <route id="_hello" pattern="/hello/{name}"> <default key="_controller">AcmeDemoBundle:Demo:hello</default> <requirement key="name">.+</requirement> </route> </routes>

PHP
use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection = new RouteCollection(); $collection->add(_hello, new Route(/hello/{name}, array( _controller => AcmeDemoBundle:Demo:hello, ), array( name => .+, ))); return $collection;

Annotations
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class DemoController { /** * @Route("/hello/{name}", name="_hello", requirements={"name" = ".+"}) */ public function helloAction($name) { // ... } }

Questo tutto! Ora, il parametro {name} pu contenere il carattere /.

3.1.7 Come usare Assetic per la gestione delle risorse


Assetic unisce due idee principali: risorse e ltri. Le risorse sono le come CSS, JavaScript e le di immagini. I ltri sono cose che possono essere applicate a questi le prima di essere serviti al browser. Questo permette una separazione tra i le delle risorse memorizzati nellapplicazione e i le effettivamente presentati allutente. Senza Assetic, basta servire direttamente i le che sono memorizzati nellapplicazione: Twig
<script src="{{ asset(js/script.js) }}" type="text/javascript" />

PHP
<script src="<?php echo $view[assets]->getUrl(js/script.js) ?>" type="text/javascript" />

Ma con Assetic, possibile manipolare queste risorse nel modo che si preferisce (o caricarle da qualunque parte) prima di servirli. Questo signica che si pu:

3.1. Ricettario

281

Documentazione Symfony, Release 2.0

Minimizzare e combinare tutti i le CSS e JS Eseguire tutti (o solo alcuni) dei le CSS o JS attraverso una sorta di compilatore, come LESS, SASS o CoffeeScript Eseguire ottimizzazioni delle immagini Risorse Lutilizzo di Assetic consente molti vantaggi rispetto a servire direttamente i le. I le non devono essere memorizzati dove vengono serviti e possono provenire da varie fonti come quelle allinterno di un bundle: Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/*)) as $url): ?> <script type="text/javascript" src="<?php echo $view->escape($url) ?>"></script> <?php endforeach; ?>

Suggerimento: Per i fogli di stile CSS, possibile utilizzare le stesse metodologie viste in questo articolo, ma con il tag stylesheets: Twig
{% stylesheets @AcmeFooBundle/Resources/public/css/* %} <link rel="stylesheet" href="{{ asset_url }}" /> {% endstylesheets %}

PHP
<?php foreach ($view[assetic]->stylesheets( array(@AcmeFooBundle/Resources/public/css/*)) as $url): ?> <link rel="stylesheet" href="<?php echo $view->escape($url) ?>" /> <?php endforeach; ?>

In questo esempio, tutti i le nella cartella Resources/public/js/ di AcmeFooBundle verranno caricati e serviti da una posizione diversa. Il tag effettivamente reso potrebbe assomigliare a:
<script src="/app_dev.php/js/abcd123.js"></script>

Nota: Questo un punto fondamentale: una volta che si lascia gestire le risorse ad Assetic, i le vengono serviti da una posizione diversa. Questo pu causare problemi con i le CSS che fanno riferimento a immagini tramite il loro percorso relativo. Comunque, il problema pu essere risolto utilizzando il ltro cssrewrite, che aggiorna i percorsi nei le CSS per riettere la loro nuova posizione.

282

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Combinare le risorse

anche possibile combinare pi le in uno. Questo aiuta a ridurre il numero delle richieste HTTP, una cosa molto utile per le prestazioni front end. Permette anche di mantenere i le pi facilmente, dividendoli in gruppi maggiormente gestibili. Questo pu contribuire alla riusabilit in quanto si possono facilmente dividere le specici del progetto da quelli che possono essere utilizzati in altre applicazioni, ma servendoli ancora come un unico le: Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* @AcmeBarBundle/Resources/public/js/form.js @AcmeBarBundle/Resources/public/js/calendar.js %} <script src="{{ asset_url }}"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/*, @AcmeBarBundle/Resources/public/js/form.js, @AcmeBarBundle/Resources/public/js/calendar.js)) as $url): ?> <script src="<?php echo $view->escape($url) ?>"></script> <?php endforeach; ?>

Nellambiente dev, ciascun le ancora servito individualmente, in modo che sia possibile eseguire il debug dei problemi pi facilmente. Tuttavia, nellambiente prod, questo verr reso come un unico tag script. Suggerimento: Se si nuovi con Assetic e si prova a utilizzare la propria applicazione nellambiente prod (utilizzando il controllore app.php), probabilmente si vedr che mancano tutti i CSS e JS. Non bisogna preoccuparsi! Accade di proposito. Per informazioni dettagliate sullutilizzo di Assetic in ambiente prod, vedere Copiare i le delle risorse. La combinazione dei le non si applica solo ai propri le. Si pu anche utilizzare Assetic per combinare risorse di terze parti (come jQuery) con i propri, in un singolo le: Twig
{% javascripts @AcmeFooBundle/Resources/public/js/thirdparty/jquery.js @AcmeFooBundle/Resources/public/js/* %} <script src="{{ asset_url }}"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js, @AcmeFooBundle/Resources/public/js/*)) as $url): ?> <script src="<?php echo $view->escape($url) ?>"></script> <?php endforeach; ?>

3.1. Ricettario

283

Documentazione Symfony, Release 2.0

Filtri Una volta che vengono gestite da Assetic, possibile applicare i ltri alle proprie risorse prima che siano servite. Questi includono ltri che comprimono loutput delle proprie risorse per ottenere le di dimensioni inferiori (e migliore ottimizzazione nel frontend). Altri ltri possono compilare i le JavaScript da le CoffeeScript e processare SASS in CSS. Assetic ha una lunga lista di ltri disponibili. Molti ltri non fanno direttamente il lavoro, ma usano librerie di terze parti per fare il lavoro pesante. Questo signica che spesso si avr la necessit di installare una libreria di terze parti per usare un ltro. Il grande vantaggio di usare Assetic per invocare queste librerie (invece di utilizzarle direttamente) che invece di doverle eseguire manualmente dopo aver lavorato sui le, sar Assetic a prendersene cura, rimuovendo del tutto questo punto dal processo di sviluppo e di pubblicazione. Per usare un ltro, necessario specicarlo nella congurazione di Assetic. Laggiunta di un ltro qui non signica che venga utilizzato: signica solo che disponibile per luso. Per esempio, per usare il compressore JavaScript YUI bisogna aggiungere la congurazione seguente: YAML
# app/config/config.yml assetic: filters: yui_js: jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="yui_js" jar="%kernel.root_dir%/Resources/java/yuicompressor.jar" /> </assetic:config>

PHP
// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( yui_js => array( jar => %kernel.root_dir%/Resources/java/yuicompressor.jar, ), ), ));

Ora, per utilizzare effettivamente il ltro su un gruppo di le JavaScript, bisogna aggiungerlo nel template: Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* filter=yui_js %} <script src="{{ asset_url }}"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/*),

284

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

array(yui_js)) as $url): ?> <script src="<?php echo $view->escape($url) ?>"></script> <?php endforeach; ?>

Una guida pi dettagliata sulla congurazione e lutilizzo dei ltri di Assetic, oltre a dettagli della modalit di debug di Assetic, si trova in Minimizzare i le JavaScript e i fogli di stile con YUI Compressor. Controllare lURL utilizzato Se lo si desidera, possibile controllare gli URL che produce Assetic. Questo fatto dal template ed relativo alla radice del documento pubblico: Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* output=js/compiled/main.js %} <script src="{{ asset_url }}"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/*), array(), array(output => js/compiled/main.js) ) as $url): ?> <script src="<?php echo $view->escape($url) ?>"></script> <?php endforeach; ?>

Nota: Symfony contiene anche un metodo per accelerare la cache, in cui lURL nale generato da Assetic contiene un parametro di query che pu essere incrementato tramite la congurazione di ogni pubblicazione. Per ulteriori informazioni, vedere lopzione di congurazione assets_version.

Copiare i le delle risorse Nellambiente dev, Assetic genera persorsi a le CSS e JavaScript che non esistono sicamente sul computer. Ma vengono resi comunque perch un controllore interno di Symfony apre i le e restituisce indietro il contenuto (dopo aver eseguito eventuali ltri). Questo tipo di pubblicazione dinamica delle risorse che sono state elaborate, ottima perch signica che si pu immediatamente vedere il nuovo stato di tutti i le delle risorse modicate. anche un male, perch pu essere molto lento. Se si stanno usando molti ltri, potrebbe essere addirittura frustrante. Fortunatamente, Assetic fornisce un modo per copiare le proprie risorse in le reali, anzich farli generare dinamicamente.
Copiare i le delle risorse nellambiente prod

Nellambiente prod, i le JS e CSS sono rappresentati da un unico tag. In altre parole, invece di vedere ogni le JavaScript che che si sta includendo nei sorgenti, probabile che si veda qualcosa di questo tipo:

3.1. Ricettario

285

Documentazione Symfony, Release 2.0

<script src="/app_dev.php/js/abcd123.js"></script>

Questo le in realt non esiste, n viene reso dinamicamente da Symfony (visto che i le di risorse sono nellambiente dev). Lasciare generare a Symfony questi le dinamicamente in un ambiente di produzione sarebbe troppo lento. Invece, ogni volta che si utilizza lapplicazione nellambiente prod (e quindi, ogni volta che si fa un nuovo rilascio), necessario eseguire il seguente task:
php app/console assetic:dump --env=prod --no-debug

Questo generer sicamente e scriver ogni le di cui si ha bisogno (ad esempio /js/abcd123.js). Se si aggiorna una qualsiasi delle risorse, sar necessario eseguirlo di nuovo per rigenerare il le.
Copiare i le delle risorse nellambiente dev

Per impostazione predenita, ogni percorso generato della risorsa nellambiente dev gestito dinamicamente da Symfony. Questo non ha alcun svantaggio ( possibile visualizzare immediatamente le modiche), salvo che le risorse verranno caricate sensibilmente lente. Se si ritiene che le risorse vengano caricate troppo lentamente, seguire questa guida. In primo luogo, dire a Symfony di smettere di cercare di elaborare questi le in modo dinamico. Fare la seguente modica nel le config_dev.yml: YAML
# app/config/config_dev.yml assetic: use_controller: false

XML
<!-- app/config/config_dev.xml --> <assetic:config use-controller="false" />

PHP
// app/config/config_dev.php $container->loadFromExtension(assetic, array( use_controller => false, ));

Poi, dato che Symfony non gener pi queste risorse dinamicamente, bisogner copiarle manualmente. Per fare ci, eseguire il seguente comando:
php app/console assetic:dump

Questo scrive sicamente tutti i le delle risorse necessari per lambiente dev. Il grande svantaggio che necessario eseguire questa operazione ogni volta che si aggiorna una risorsa. Per fortuna, passando lopzione --watch, il comando rigenerer automaticamente le risorse che sono cambiate:
php app/console assetic:dump --watch

Dal momento che lesecuzione di questo comando nellambiente dev pu generare molti le, di solito una buona idea far puntare i le con le risorse generate in una cartella separata (ad esempio /js/compiled), per mantenere ordinate le cose: Twig

286

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

{% javascripts @AcmeFooBundle/Resources/public/js/* output=js/compiled/main.js %} <script src="{{ asset_url }}"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/*), array(), array(output => js/compiled/main.js) ) as $url): ?> <script src="<?php echo $view->escape($url) ?>"></script> <?php endforeach; ?>

3.1.8 Minimizzare i le JavaScript e i fogli di stile con YUI Compressor


Yahoo! mette a disposizione un eccellente strumento per minimizzare i le JavaScipt e i fogli di stile, che cos possono viaggiare pi velocemente sulla rete: lo YUI Compressor (http://developer.yahoo.com/yui/compressor/). Grazie ad Assetic utilizzare questo strumento semplicissimo. Scaricare il JAR di YUI Compressor LYUI Compressor scritto in Java e viene distribuito in formato JAR. Si dovr scaricare il le JAR (http://yuilibrary.com/downloads/#yuicompressor) e salvarlo in app/Resources/java/yuicompressor.jar. Congurare i ltri per YUI necessario congurare due ltri Assetic allinterno dellapplicazione. Uno per minimizzare i le JavaScript e uno per minimizzare i fogli di stile con YUI Compressor: YAML
# app/config/config.yml assetic: filters: yui_css: jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar" yui_js: jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="yui_css" jar="%kernel.root_dir%/Resources/java/yuicompressor.jar" /> <assetic:filter name="yui_js" jar="%kernel.root_dir%/Resources/java/yuicompressor.jar" /> </assetic:config>

3.1. Ricettario

287

Documentazione Symfony, Release 2.0

PHP
// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( yui_css => array( jar => %kernel.root_dir%/Resources/java/yuicompressor.jar, ), yui_js => array( jar => %kernel.root_dir%/Resources/java/yuicompressor.jar, ), ), ));

Dallapplicazione si ha ora accesso a due nuovi ltri di Assetic: yui_css e yui_js. Questi ltri utilizzeranno YUI Compressor per minimizzare, rispettivamente, i fogli di stile e i le JavaScript. Minimizzare le risorse YUI Compressor stato congurato, ma, prima di poter vedere i risultati, necessario applicare i ltri alle risorse. Visto che le risorse fanno parte del livello della vista, questo lavoro dovr essere svolto nei template: Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* filter=yui_js %} <script src="{{ asset_url }}"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/*), array(yui_js)) as $url): ?> <script src="<?php echo $view->escape($url) ?>"></script> <?php endforeach; ?>

Nota: Il precedente esempio presuppone che ci sia un bundle chiamato AcmeFooBundle e che i le JavaScript si trovino nella cartella Resources/public/js allinterno del bundle. comunque possibile includere le JavaScript che si trovino in posizioni differenti. Con laggiunta del ltro yui_js dellesempio precedente, i le minimizzati viaggeranno molto pi velocemente sulla rete. Lo stesso procedimento pu essere ripetuto per minimizzare i fogli di stile. Twig
{% stylesheets @AcmeFooBundle/Resources/public/css/* filter=yui_css %} <link rel="stylesheet" type="text/css" media="screen" href="{{ asset_url }}" /> {% endstylesheets %}

PHP

<?php foreach ($view[assetic]->stylesheets( array(@AcmeFooBundle/Resources/public/css/*), array(yui_css)) as $url): ?> <link rel="stylesheet" type="text/css" media="screen" href="<?php echo $view->escape($url) ?>" / <?php endforeach; ?>

288

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Disabilitare la minimizzazione in modalit debug I le JavaScript e i fogli di stile minimizzati sono difcili da leggere e ancora pi difcili da correggere. Per questo motivo Assetic permette di disabilitare determinati ltri quando lapplicazione viene eseguita in modalit debug. Mettendo il presso punto interrogativo ? al nome dei ltri, si chiede ad Assetic di applicarli solamente quando la modalit debug inattiva. Twig
{% javascripts @AcmeFooBundle/Resources/public/js/* filter=?yui_js %} <script src="{{ asset_url }}"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/*), array(?yui_js)) as $url): ?> <script src="<?php echo $view->escape($url) ?>"></script> <?php endforeach; ?>

3.1.9 Usare Assetic per lottimizzazione delle immagini con le funzioni di Twig
Tra i vari ltri di Assetic, ve ne sono quattro che possono essere utilizzati per ottimizzare le immagini al volo. Ci permette di avere immagini di dimensioni inferiori, senza ricorrere a un editor graco per ogni modica. Il risultato dei ltri pu essere messo in cache e usato in fase di produzione, in modo da eliminare problemi di prestazioni per lutente nale. Usare Jpegoptim Jpegoptim (http://www.kokkonen.net/tjko/projects.html) uno strumento per ottimizzare i le JPEG. Per poterlo usare, si aggiunge il seguente codice alla congurazione di Assetic: YAML
# app/config/config.yml assetic: filters: jpegoptim: bin: percorso/per/jpegoptim

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="jpegoptim" bin="percorso/per/jpegoptim" /> </assetic:config>

PHP
// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( jpegoptim => array( bin => percorso/per/jpegoptim,

3.1. Ricettario

289

Documentazione Symfony, Release 2.0

), ), ));

Nota: Per poter utilizzare jpegoptim necessario che sia gi installato sul proprio computer. Lopzione bin indica la posizione del programma eseguibile. Sar ora possibile usarlo nei propri template: Twig
{% image @AcmeFooBundle/Resources/public/images/esempio.jpg filter=jpegoptim output=/images/esempio.jpg %} <img src="{{ asset_url }}" alt="Esempio"/> {% endimage %}

PHP
<?php foreach ($view[assetic]->images( array(@AcmeFooBundle/Resources/public/images/esempio.jpg), array(jpegoptim)) as $url): ?> <img src="<?php echo $view->escape($url) ?>" alt="Esempio"/> <?php endforeach; ?>

Rimozione dei dati EXIF

Senza ulteirori opzioni, questo ltro rimuove solamente le meta-informazioni contenute nel le. I dati EXIF e i commenti non vengono eliminati: comunque possibile rimuoverli usando lopzione strip_all: YAML
# app/config/config.yml assetic: filters: jpegoptim: bin: percorso/per/jpegoptim strip_all: true

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="jpegoptim" bin="percorso/per/jpegoptim" strip_all="true" /> </assetic:config>

PHP
// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( jpegoptim => array( bin => percorso/per/jpegoptim, strip_all => true,

290

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

), ), ));

Diminuire la qualit massima

Senza ulteriori opzioni, la qualit dellimmagine JPEG non viene modicata. per possibile ridurre ulteriormente la dimensione del le, congurando il livello di qualit massima per le immagini a un livello inferiore di quello delle immagini stesse. Ovviamente, questo alterer la qualit dellimmagine: YAML
# app/config/config.yml assetic: filters: jpegoptim: bin: percorso/per/jpegoptim max: 70

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="jpegoptim" bin="percorso/per/jpegoptim" max="70" /> </assetic:config>

PHP
// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( jpegoptim => array( bin => percorso/per/jpegoptim, max => 70, ), ), ));

Abbreviare la sintassi: le funzioni di Twig Se si utilizza Twig, possibile inserire tutte queste opzioni con una sintassi pi concisa, abilitando alcune speciali funzioni di Twig. Si inizia modicando la congurazione, come di seguito: YAML
# app/config/config.yml assetic: filters: jpegoptim: bin: percorso/per/jpegoptim twig: functions: jpegoptim: ~

3.1. Ricettario

291

Documentazione Symfony, Release 2.0

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="jpegoptim" bin="percorso/per/jpegoptim" /> <assetic:twig> <assetic:twig_function name="jpegoptim" /> </assetic:twig> </assetic:config>

PHP
// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( jpegoptim => array( bin => percorso/per/jpegoptim, ), ), twig => array( functions => array(jpegoptim), ), ), ));

A questo punto il template di Twig pu essere modicato nel seguente modo:


<img src="{{ jpegoptim(@AcmeFooBundle/Resources/public/images/esempio.jpg) }}" alt="Esempio"/>

possibile specicare la cartella di output nel seguente modo: YAML


# app/config/config.yml assetic: filters: jpegoptim: bin: percorso/per/jpegoptim twig: functions: jpegoptim: { output: images/*.jpg }

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="jpegoptim" bin="percorso/per/jpegoptim" /> <assetic:twig> <assetic:twig_function name="jpegoptim" output="images/*.jpg" /> </assetic:twig> </assetic:config>

PHP 292 Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( jpegoptim => array( bin => percorso/per/jpegoptim, ), ), twig => array( functions => array( jpegoptim => array( output => images/*.jpg ), ), ), ));

3.1.10 Applicare i ltri di Assetic a le con speciche estensioni


I ltri di Assetic possono essere applicati a singoli le, gruppi di le o anche, come vedremo, a le che hanno una specica estensione. Per mostrare lutilizzo di ogni opzione, supponiamo di voler usare il ltro CoffeeScript di Assetic che compila i le CoffeeScript in Javascript. La congurazione prevede semplicemente di denire i percorsi per coffee e per node. I valori predeniti sono /usr/bin/coffee e /usr/bin/node: YAML
# app/config/config.yml assetic: filters: coffee: bin: /usr/bin/coffee node: /usr/bin/node

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="coffee" bin="/usr/bin/coffee" node="/usr/bin/node" /> </assetic:config>

PHP
// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( coffee => array( bin => /usr/bin/coffee, node => /usr/bin/node, ), ), ));

3.1. Ricettario

293

Documentazione Symfony, Release 2.0

Filtrare un singolo le In questo modo sar possibile inserire un singolo le CoffeScript nel template, come se fosse un normale JavaScript: Twig
{% javascripts @AcmeFooBundle/Resources/public/js/esempio.coffee filter=coffee %} <script src="{{ asset_url }}" type="text/javascript"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/esempio.coffee), array(coffee)) as $url): ?> <script src="<?php echo $view->escape($url) ?>" type="text/javascript"></script> <?php endforeach; ?>

Questo tutto quel che serve per compilare il le CoffeeScript e restituirlo come un normale JavaScript. Filtrare le multpili anche possibile combinare diversi le CoffeeScript in un singolo le: Twig
{% javascripts @AcmeFooBundle/Resources/public/js/esempio.coffee @AcmeFooBundle/Resources/public/js/altro.coffee filter=coffee %} <script src="{{ asset_url }}" type="text/javascript"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/esempio.coffee, @AcmeFooBundle/Resources/public/js/altro.coffee), array(coffee)) as $url): ?> <script src="<?php echo $view->escape($url) ?>" type="text/javascript"></script> <?php endforeach; ?>

Tutti i le verranno restituiti e compilati in un unico, regolare le JavaScript. Filtrare in base allestensione del le Uno dei grandi vantaggi nellutilizzo di Assetic quello di ridurre il numero di le di risorse, riducendo cos le richieste HTTP. Per massimizzarne i vantaggi, sarebbe utile combinare insieme tutti i le JavaScript e quelli CoffeeScript in uno unico, visto che verranno tutti serviti come le JavaScript. Sfortunatamente non possibile aggiungere semplicemente un le JavaScript ai le precedenti, per via del fatto che il le JavaScript non supererebbe la compilazione di CoffeeScript. Questo problema pu essere ovviato utilizzando lopzione apply_to nella congurazione, in modo da specicare che il ltro dovr essere applicato solo ai le con una determinata estensione. In questo caso si dovr specicare che il ltro Coffee dovr applicarsi a tutti e soli i le .coffee:

294

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

YAML
# app/config/config.yml assetic: filters: coffee: bin: /usr/bin/coffee node: /usr/bin/node apply_to: "\.coffee$"

XML
<!-- app/config/config.xml --> <assetic:config> <assetic:filter name="coffee" bin="/usr/bin/coffee" node="/usr/bin/node" apply_to="\.coffee$" /> </assetic:config>

PHP
// app/config/config.php $container->loadFromExtension(assetic, array( filters => array( coffee => array( bin => /usr/bin/coffee, node => /usr/bin/node, apply_to => \.coffee$, ), ), ));

In questo modo non pi necessario specicare il ltro coffee nel template. anche possibile elencare i normali le JavaScript, i quali verranno combinati e restituiti come un unico le JavaScript (e in modo tale che i soli le .coffee venagano elaborati dal ltro CoffeeScript): Twig
{% javascripts @AcmeFooBundle/Resources/public/js/esempio.coffee @AcmeFooBundle/Resources/public/js/altro.coffee @AcmeFooBundle/Resources/public/js/regolare.js %} <script src="{{ asset_url }}" type="text/javascript"></script> {% endjavascripts %}

PHP
<?php foreach ($view[assetic]->javascripts( array(@AcmeFooBundle/Resources/public/js/esempio.coffee, @AcmeFooBundle/Resources/public/js/altro.coffee, @AcmeFooBundle/Resources/public/js/regolare.js), as $url): ?> <script src="<?php echo $view->escape($url) ?>" type="text/javascript"></script> <?php endforeach; ?>

3.1. Ricettario

295

Documentazione Symfony, Release 2.0

3.1.11 Come gestire il caricamento di le con Doctrine


La gestione del caricamento dei le tramite le entit di Doctrine non diversa da qualsiasi altro tipo di caricamento. In altre parole si liberi di spostare il le nel controllore dopo aver gestito linvio tramite una form. Per alcuni esempi in merito fare riferimento alla pagina dedicata ai le type. Volendo anche possibile integrare il caricamento del le nel ciclo di vita di unentit (creazione, modica e cancellazione). In questo caso, nel momento in cui lentit viene creata, modicata, o cancellata da Doctrine, il caricamento del le o il processo di rimozione verranno azionati automaticamente (senza dover fare nulla nel controllore); Per far funzionare tutto questo necessario conoscere alcuni dettagli che verranno analizzati in questa sezione del ricettario. Preparazione Innanzitutto creare una semplice classe entit di Doctrine, su cui lavorare:
// src/Acme/DemoBundle/Entity/Document.php namespace Acme\DemoBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity */ class Document { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ public $id; /** * @ORM\Column(type="string", length=255) * @Assert\NotBlank */ public $name; /** * @ORM\Column(type="string", length=255, nullable=true) */ public $path; public function getAbsolutePath() { return null === $this->path ? null : $this->getUploadRootDir()./.$this->path; } public function getWebPath() { return null === $this->path ? null : $this->getUploadDir()./.$this->path; } protected function getUploadRootDir() {

296

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

// il percorso assoluto della cartella dove i documenti caricati verranno salvati return __DIR__./../../../../web/.$this->getUploadDir(); }

protected function getUploadDir() { // get rid of the __DIR__ so it doesnt screw when displaying uploaded doc/image in the view. return uploads/documents; } }

Lentit Document ha un nome che viene associato al le. La propriet path contiene il percorso relativo al le e viene memorizzata sul database. Il metodo getAbsolutePath() un metodo di supporto che restituisce il percorso assoluto al le mentre il getWebPath() un altro metodo di supporto che restituisce il percorso web che pu essere utilizzato nei template per collegare il le caricato. Suggerimento: Se non gi stato fatto, si consiglia la lettura della documentazione relativa ai le type per comprendere meglio come funziona il caricamento di base.

Nota: Se si stanno utilizzando le annotazioni per specicare le regole di validazione (come nellesempio proposto), assicurarsi di abilitare la validazione tramite annotazioni (confrontare congurazione della validazione). Per gestire il le attualmente caricato tramite il form utilizzare un campo file virtuale. Per esempio, se si sta realizzando il form direttamente nel controller, potrebbe essere come il seguente:
public function uploadAction() { // ... $form = $this->createFormBuilder($document) ->add(name) ->add(file) ->getForm() ; // ... }

In seguito, creare la propriet nella classe Document aggiungendo alcune regole di validazione:
// src/Acme/DemoBundle/Entity/Document.php // ... class Document { /** * @Assert\File(maxSize="6000000") */ public $file; // ... }

3.1. Ricettario

297

Documentazione Symfony, Release 2.0

Nota: Grazie al fatto che si utilizza il vincolo File, Symfony2 ipotizzer automaticamente che il campo del form sia un le upload. per questo motivo che non si rende necessario impostarlo esplicitamente al momento di creazione del form precedente (->add(file)). Il controllore seguente mostra come gestire lintero processo:
use Acme\DemoBundle\Entity\Document; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; // ... /** * @Template() */ public function uploadAction() { $document = new Document(); $form = $this->createFormBuilder($document) ->add(name) ->add(file) ->getForm() ; if ($this->getRequest()->getMethod() === POST) { $form->bindRequest($this->getRequest()); if ($form->isValid()) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($document); $em->flush(); $this->redirect($this->generateUrl(...)); } } return array(form => $form->createView()); }

Nota: Realizzando il template non dimenticarsi di impostare lattributo enctype:


<h1>Upload File</h1> <form action="#" method="post" {{ form_enctype(form) }}> {{ form_widget(form) }} <input type="submit" value="Upload Document" /> </form>

Il controllore precedente memorizzer automaticamente lentit Document con il nome inviato, ma non far nulla relativamente al le e la propriet path sar vuota. Un modo semplice per gestire il caricamento del le quello si spostarlo appena prima che lentit venga memorizzata, impostando la propriet path in modo corretto. Iniziare invocando un nuovo metodo upload(), che si creer tra poco per gestire il caricamento del le, nella classe Document:
if ($form->isValid()) { $em = $this->getDoctrine()->getEntityManager();

298

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

$document->upload(); $em->persist($document); $em->flush(); $this->redirect(...); }

Il metodo upload() sfrutter loggetto Symfony\Component\HttpFoundation\File\UploadedFile che quanto viene restituito dopo linvio di un campo di tipo file:
public function upload() { // la propriet file pu essere vuota se il campo non obbligatorio if (null === $this->file) { return; } // si utilizza il nome originale del file ma consigliabile // un processo di sanitizzazione almeno per evitare problemi di sicurezza // move accetta come parametri la cartella di destinazione e il nome del file di destinazione $this->file->move($this->getUploadRootDir(), $this->file->getClientOriginalName()); // impostare la propriet del percorso al nome del file dove stato salvato il file $this->path = $this->file->getClientOriginalName(); // impostare a null la propriet file dato che non pi necessaria $this->file = null; }

Utilizzare i callback del ciclo di vita delle entit Anche se limplementazione funziona, essa presenta un grave difetto: cosa succede se si verica un problema mentre lentit viene memorizzata? Il le potrebbe gi essere stato spostato nella sua posizione nale anche se la propriet path dellentit non fosse stata impostata correttamente. Per evitare questo tipo di problemi, necessario modicare limplementazione in modo tale da rendere atomiche le azioni del database e dello spostamento del le: se si vericasse un problema durante la memorizzazione dellentit, o se il le non potesse essere spostato, allora non dovrebbe succedere niente. Per fare questo, necessario spostare il le nello stesso momento in cui Doctrine memorizza lentit sul database. Questo pu essere fatto agganciandosi a un callback del ciclo di vita dellentit:
/** * @ORM\Entity * @ORM\HasLifecycleCallbacks */ class Document { }

Quindi, rifattorizzare la classe Document, per sfruttare i vantaggi dei callback:


use Symfony\Component\HttpFoundation\File\UploadedFile; /** * @ORM\Entity

3.1. Ricettario

299

Documentazione Symfony, Release 2.0

* @ORM\HasLifecycleCallbacks */ class Document { /** * @ORM\PrePersist() * @ORM\PreUpdate() */ public function preUpload() { if (null !== $this->file) { // fare qualsiasi cosa si voglia per generare un nome univoco $this->path = uniqid()...$this->file->guessExtension(); } } /** * @ORM\PostPersist() * @ORM\PostUpdate() */ public function upload() { if (null === $this->file) { return; } // se si verifica un errore mentre il file viene spostato viene // lanciata automaticamente uneccezione da move(). Questo eviter // la memorizzazione dellentit su database in caso di errore $this->file->move($this->getUploadRootDir(), $this->path); unset($this->file); } /** * @ORM\PostRemove() */ public function removeUpload() { if ($file = $this->getAbsolutePath()) { unlink($file); } } }

La classe ora ha tutto quello che serve: genera un nome di le univoco prima della memorizzazione, sposta il le dopo la memorizzazione, rimuove il le se lentit viene eliminata. Nota: Le callback @ORM\PrePersist() e @ORM\PostPersist() scattano prima e dopo la memorizzazione di unentit sul database. Parallelamente le callback @ORM\PreUpdate() e @ORM\PostUpdate() vengono invocate quanto lentit viene modicata.

300

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Attenzione: I callback PreUpdate e PostUpdate scattano solamente se c una modica a uno dei campi dellentit memorizzata. Questo signica che, se si modica solamente la propriet $file, questi eventi non verranno invocati, dato che la propriet in questione non viene memorizzata direttamente tramite Doctrine. Una soluzione potrebbe essere quella di utilizzare un campo updated memorizzato tramite Doctrine, da modicare manualmente in caso di necessit per la sostituzione del le.

Usare id come nome del le Volendo usare lid come nome del le, limplementazione leggermente diversa, dato che sarebbe necessario memorizzare lestensione nella propriet path, invece che nellattuale nome del le:
use Symfony\Component\HttpFoundation\File\UploadedFile; /** * @ORM\Entity * @ORM\HasLifecycleCallbacks */ class Document { /** * @ORM\PrePersist() * @ORM\PreUpdate() */ public function preUpload() { if (null !== $this->file) { $this->path = $this->file->guessExtension(); } } /** * @ORM\PostPersist() * @ORM\PostUpdate() */ public function upload() { if (null === $this->file) { return; } // qui si deve lanciare uneccezione se il file non pu essere spostato // per fare in modo che lentit non possa essere memorizzata a database $this->file->move($this->getUploadRootDir(), $this->id...$this->file->guessExtension()); unset($this->file); } /** * @ORM\PostRemove() */ public function removeUpload() { if ($file = $this->getAbsolutePath()) { unlink($file); } }

3.1. Ricettario

301

Documentazione Symfony, Release 2.0

public function getAbsolutePath() { return null === $this->path ? null : $this->getUploadRootDir()./.$this->id...$this->path; } }

3.1.12 Estensioni di Doctrine: Timestampable: Sluggable, Translatable, ecc.


Doctrine2 molto essibile e la comunit ha gi creato una serie di utili estensioni di Doctrine, per aiutare nei compiti pi comuni relativi alle entit. In paricolare, il bundle DoctrineExtensionsBundle (https://github.com/stof/StofDoctrineExtensionsBundle) fornisce integrazione con una libreria di estensioni, che offre i comportamenti Sluggable (https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sluggable.md), Translatable (https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/translatable.md), Timestampable (https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/timestampable.md), Loggable (https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/loggable.md) e Tree (https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/tree.md). Si veda il bundle per maggiori dettagli.

3.1.13 Registrare ascoltatori e sottoscrittori di eventi


Doctrine include un ricco sistema di eventi, lanciati quasi ogni volta che accade qualcosa nel sistema. Per lo sviluppatore, signica la possibilit di creare servizi arbitrari e dire a Doctrine di noticare questi oggetti ogni volta che accade una certa azione (p.e. prePersist). Questo pu essere utile, per esempio, per creare un indice di ricerca indipendente ogni volta che un oggetto viene salvato nel database. Doctrine deninsce due tipi di oggetti che possono ascoltare eventi: ascoltatori e sottoscrittori. Sono simili tra loro, ma gli ascoltatori sono leggermente pi semplicati. Per approfondimenti, vedere The Event System (http://www.doctrine-project.org/docs/orm/2.0/en/reference/events.html) sul sito di Doctrine. Congurare ascoltatori e sottoscrittori Per registrare un servizio come ascoltatore o sottoscrittore di eventi, basta assegnarli il tag appropriato. A seconda del caso, si pu agganciare un ascoltatore a ogni connessione DBAL o gestore di entit dellORM, oppure solo a una specica connessione DBAL e a tutti i gestori di entit che usano tale connessione. YAML
doctrine: dbal: default_connection: default connections: default: driver: pdo_sqlite memory: true services: my.listener: class: Acme\SearchBundle\Listener\SearchIndexer tags: - { name: doctrine.event_listener, event: postPersist } my.listener2:

302

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

class: Acme\SearchBundle\Listener\SearchIndexer2 tags: - { name: doctrine.event_listener, event: postPersist, connection: default } my.subscriber: class: Acme\SearchBundle\Listener\SearchIndexerSubscriber tags: - { name: doctrine.event_subscriber, connection: default }

XML
<?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:doctrine="http://symfony.com/schema/dic/doctrine"> <doctrine:config> <doctrine:dbal default-connection="default"> <doctrine:connection driver="pdo_sqlite" memory="true" /> </doctrine:dbal> </doctrine:config> <services> <service id="my.listener" class="Acme\SearchBundle\Listener\SearchIndexer"> <tag name="doctrine.event_listener" event="postPersist" /> </service> <service id="my.listener2" class="Acme\SearchBundle\Listener\SearchIndexer2"> <tag name="doctrine.event_listener" event="postPersist" connection="default" /> </service> <service id="my.subscriber" class="Acme\SearchBundle\Listener\SearchIndexerSubscriber"> <tag name="doctrine.event_subscriber" connection="default" /> </service> </services> </container>

Creare la classe dellascoltatore Nellesempio precedente, stato congurato un servizio my.listener come ascoltatore dellevento postPersist. La classe dietro al servizio deve avere un metodo postPersist, che sar richiamato al lancio dellevento:
// src/Acme/SearchBundle/Listener/SearchIndexer.php namespace Acme\SearchBundle\Listener; use Doctrine\ORM\Event\LifecycleEventArgs; use Acme\StoreBundle\Entity\Product; class SearchIndexer { public function postPersist(LifecycleEventArgs $args) { $entity = $args->getEntity(); $entityManager = $args->getEntityManager(); // si potrebbe voler fare qualcosa su unentit Product if ($entity instanceof Product) { // fare qualcosa con loggetto Product }

3.1. Ricettario

303

Documentazione Symfony, Release 2.0

} }

In ciascun evento, si ha accesso alloggetto LifecycleEventArgs, che rende disponibili sia loggetto entit dellevento che lo stesso gestore di entit. Una cosa importante da notare che un ascoltatore ascolter tutte le entit della propria applicazione. Quindi, se si vuole gestire solo un tipo specico di entit (p.e. unentit Product, ma non unentit BlogPost), si dovrebbe vericare il nome della classe dellentit nel proprio metodo (come precedentemente mostrato).

3.1.14 Come generare entit da una base dati esistente


Quando si inizia a lavorare su un nuovo progetto, che usa una base dati, si pongono due situazioni diverse. Nella maggior parte dei casi, il modello della base dati progettato e costruito da zero. A volte, tuttavia, si inizia con un modello di base dati esistente e probabilmente non modicabile. Per fortuna, Doctrine dispone di molti strumenti che aiutano a generare classi del modello da una base dati esistente. Nota: Come dice la documentazione sugli strumenti di Doctrine (http://www.doctrineproject.org/docs/orm/2.0/en/reference/tools.html#reverse-engineering), il reverse engineering un processo da eseguire una sola volta su un progetto. Doctrine in grado di convertire circa il 70-80% delle informazioni di mappatura necessarie, in base a campi, indici e vincoli di integrit referenziale. Doctrine non pu scoprire le associazioni inverse, i tipi di ereditariet, le entit con chiavi esterne come chiavi primarie, n operazioni semantiche sulle associazioni, come le cascate o gli eventi del ciclo di vita. Sar necessario un successivo lavoro manuale sulle entit generate, perch tutto corrisponda alle speciche del modello del proprio dominio. Questa guida ipotizza che si stia usando una semplice applicazione blog, con le seguenti due tabelle: blog_post e blog_comment. Una riga di un commento collegata alla riga di un post tramite una chiave esterna.
CREATE TABLE blog_post ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(100) COLLATE utf8_unicode_ci NOT NULL, content longtext COLLATE utf8_unicode_ci NOT NULL, created_at datetime NOT NULL, PRIMARY KEY (id), ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; CREATE TABLE blog_comment ( id bigint(20) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL, author varchar(20) COLLATE utf8_unicode_ci NOT NULL, content longtext COLLATE utf8_unicode_ci NOT NULL, created_at datetime NOT NULL, PRIMARY KEY (id), KEY blog_comment_post_id_idx (post_id), CONSTRAINT blog_post_id FOREIGN KEY (post_id) REFERENCES blog_post (id) ON DELETE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Prima di addentrarsi nella ricetta, ci si assicuri di aver congurato correttamente i propri parametri di connessione, nel le app/config/parameters.yml (o in qualsiasi altro posto in cui la congurazione memorizzata) e di aver inizializzato un bundle che possa ospitare le future classi entit. In questa guida, si ipotizza che esista un AcmeBlogBundle, posto nella cartella src/Acme/BlogBundle. Il primo passo nella costruzione di classi entit da una base dati esistente quello di chiedere a Doctrine unintrospezione della base dati e una generazione dei le dei meta-dati corrispondenti. I le dei meta-dati descrivono le classi entit da generare in base ai campi delle tabelle.

304

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

php app/console doctrine:mapping:convert xml ./src/Acme/BlogBundle/Resources/config/doctrine/metadata

Questo comando del terminale chiede a Doctrine lintrospezione della base dati e la generazione dei le di metadati XML sotto la cartella src/Acme/BlogBundle/Resources/config/doctrine/metadata/orm del bundle. Suggerimento: Le classi dei meta-dati possono anche essere generate in YAML, modicando il primo parametro in yml. Il le dei meta-dati BlogPost.dcm.xml assomiglia a questo:
<?xml version="1.0" encoding="utf-8"?> <doctrine-mapping> <entity name="BlogPost" table="blog_post"> <change-tracking-policy>DEFERRED_IMPLICIT</change-tracking-policy> <id name="id" type="bigint" column="id"> <generator strategy="IDENTITY"/> </id> <field name="title" type="string" column="title" length="100"/> <field name="content" type="text" column="content"/> <field name="isPublished" type="boolean" column="is_published"/> <field name="createdAt" type="datetime" column="created_at"/> <field name="updatedAt" type="datetime" column="updated_at"/> <field name="slug" type="string" column="slug" length="255"/> <lifecycle-callbacks/> </entity> </doctrine-mapping>

Una volta generati i le dei meta-dati, si pu chiedere a Doctrine di importare lo schema e costruire le relative classi entit, eseguendo i seguenti comandi.
php app/console doctrine:mapping:import AcmeBlogBundle annotation php app/console doctrine:generate:entities AcmeBlogBundle

Il primo comando genera le classi delle entit con annotazioni, ma ovviamente si pu cambiare il parametro annotation in xml o yml. La nuva classe entit BlogComment simile a questa:
<?php // src/Acme/BlogBundle/Entity/BlogComment.php namespace Acme\BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Acme\BlogBundle\Entity\BlogComment * * @ORM\Table(name="blog_comment") * @ORM\Entity */ class BlogComment { /** * @var bigint $id * * @ORM\Column(name="id", type="bigint", nullable=false) * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY")

3.1. Ricettario

305

Documentazione Symfony, Release 2.0

*/ private $id; /** * @var string $author * * @ORM\Column(name="author", type="string", length=100, nullable=false) */ private $author; /** * @var text $content * * @ORM\Column(name="content", type="text", nullable=false) */ private $content; /** * @var datetime $createdAt * * @ORM\Column(name="created_at", type="datetime", nullable=false) */ private $createdAt; /** * @var BlogPost * * @ORM\ManyToOne(targetEntity="BlogPost") * @ORM\JoinColumn(name="post_id", referencedColumnName="id") */ private $post; }

Come si pu vedere, Doctrine converte tutti i campi delle tabelle in propriet della classe. La cosa pi notevole che scopre anche la relazione con la classe entit BlogPost, basandosi sulla chiave esterna. Di conseguenza, si pu trovare una propriet $post, mappata con lentit BlogPost nella classe BlogComment. Il secondo comando genera tutti i getter e i setter per le propriet delle classi entit BlogPost e BlogComment. Le entit generate sono ora pronte per essere usate.

3.1.15 Come usare il livello DBAL di Doctrine


Nota: Questo articolo riguarda il livello DBAL di Doctrine. Di solito si lavora con il livello dellORM di Doctrine, che un livello pi astratto e usa il DBAL dietro le quinte, per comunicare con il database. Per saperne di pi sullORM di Docrine, si veda Database e Doctrine (Il modello). Il livello di astrazione del database (Database Abstraction Layer o DBAL) di Doctrine (http://www.doctrineproject.org) un livello posto sopra PDO (http://www.php.net/pdo) e offre unAPI intuitiva e essibile per comunicare con i database relazionali pi diffusi. In altre parole, la libreria DBAL facilita lesecuzione delle query ed esegue altre azioni sul database. Suggerimento: Leggere la documentazione di Doctrine DBAL Documentation (http://docs.doctrineproject.org/projects/doctrine-dbal/en/latest/index.html) per conoscere tutti i dettagli e le capacit della libreria DBAL di Doctrine.

306

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Per iniziare, congurare i parametri di connessione al database: YAML


# app/config/config.yml doctrine: dbal: driver: pdo_mysql dbname: Symfony2 user: root password: null charset: UTF8

XML
// app/config/config.xml <doctrine:config> <doctrine:dbal name="default" dbname="Symfony2" user="root" password="null" driver="pdo_mysql" /> </doctrine:config>

PHP
// app/config/config.php $container->loadFromExtension(doctrine, array( dbal => array( driver => pdo_mysql, dbname => Symfony2, user => root, password => null, ), ));

Per un elenco completo delle opzioni di congurazione, vedere Congurazione Doctrine DBAL. Si pu quindi accedere alla connessione del DBAL di Doctrine usando il servizio database_connection:
class UserController extends Controller { public function indexAction() { $conn = $this->get(database_connection); $users = $conn->fetchAll(SELECT * FROM users); // ... } }

Registrare tipi di mappatura personalizzati Si possono registrare tipi di mappatura personalizzati attraverso la congurazione di Symfony. Saranno aggiunti a tutte le congurazioni congurate. Per maggiori informazioni sui tipi di mappatura personalizzati, leggere la sezione Custom Mapping Types (http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custommapping-types) della documentazione di Doctrine.

3.1. Ricettario

307

Documentazione Symfony, Release 2.0

YAML
# app/config/config.yml doctrine: dbal: types: custom_first: Acme\HelloBundle\Type\CustomFirst custom_second: Acme\HelloBundle\Type\CustomSecond

XML

<!-- app/config/config.xml --> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:doctrine="http://symfony.com/schema/dic/doctrine" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ser http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doc <doctrine:config> <doctrine:dbal> <doctrine:dbal default-connection="default"> <doctrine:connection> <doctrine:mapping-type name="enum">string</doctrine:mapping-type> </doctrine:connection> </doctrine:dbal> </doctrine:config> </container>

PHP
// app/config/config.php $container->loadFromExtension(doctrine, array( dbal => array( connections => array( default => array( mapping_types => array( enum => string, ), ), ), ), ));

Registrare tipi di mappatura personalizzati in SchemaTool SchemaTool usato per ispezionare il database per confrontare lo schema. Per assolvere a questo compito, ha bisogno di sapere quale tipo di mappatura deve essere usato per ogni tipo di database. Se ne possono registrare di nuovi attraverso la congurazione. Mappiamo il tipo ENUM (non supportato di base dal DBAL) sul tipo di mappatura string: YAML
# app/config/config.yml doctrine: dbal: connection: default: // Other connections parameters

308

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

mapping_types: enum: string

XML

<!-- app/config/config.xml --> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:doctrine="http://symfony.com/schema/dic/doctrine" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ser http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doc <doctrine:config> <doctrine:dbal> <doctrine:type name="custom_first" class="Acme\HelloBundle\Type\CustomFirst" /> <doctrine:type name="custom_second" class="Acme\HelloBundle\Type\CustomSecond" /> </doctrine:dbal> </doctrine:config> </container>

PHP
// app/config/config.php $container->loadFromExtension(doctrine, array( dbal => array( types => array( custom_first => Acme\HelloBundle\Type\CustomFirst, custom_second => Acme\HelloBundle\Type\CustomSecond, ), ), ));

3.1.16 Come lavorare con gestori di entit multipli


Si possono usare gestori di entit multipli in unapplicazione Symfony2. Questo si rende necessario quando si usano diversi database o addirittura venditori con insiemi di entit completamente differenti. In altre parole, un gestore di entit che si connette a un database gestir alcune entit, mentre un altro gestore di entit che si connette a un altro database potrebbe gestire il resto. Nota: Luso di molti gestori di entit facile, ma pi avanzato e solitamente non richiesto. Ci si assicuri di avere effettivamente bisogno di gestori di entit multipli, prima di aggiungere un tale livello di complessit. La congurazione seguente mostra come congurare due gestori di entit: YAML
doctrine: orm: default_entity_manager: default entity_managers: default: connection: default mappings: AcmeDemoBundle: ~ AcmeStoreBundle: ~ customer:

3.1. Ricettario

309

Documentazione Symfony, Release 2.0

connection: customer mappings: AcmeCustomerBundle: ~

In questo caso, sono stati deniti due gestori di entit, chiamati default e customer. Il gestore di entit default gestisce le entit in AcmeDemoBundle e AcmeStoreBundle, mentre il gestore di entit customer gestisce le entit in AcmeCustomerBundle. Lavorando con gestori di entit multipli, occorre esplicitare quale gestore di entit si vuole usare. Se si omette il nome del gestore di entit al momento della sua richiesta, verr restituito il gestore di entit predenito (cio default):
class UserController extends Controller { public function indexAction() { // entrambi restiuiscono "default" $em = $this->get(doctrine)->getEntityManager(); $em = $this->get(doctrine)->getEntityManager(default); $customerEm = } } $this->get(doctrine)->getEntityManager(customer);

Si pu ora usare Doctrine come prima, usando il gestore di entit default per persistere e recuperare le entit da esso gestite e il gestore di entit customer per persistere e recuperare le sue entit.

3.1.17 Registrare funzioni DQL personalizzate


Doctrine consente di specicare funzioni DQL personalizzate. Per maggiori informazioni sullargomento, leggere la ricetta di Doctrine DQL User Dened Functions (http://www.doctrine-project.org/docs/orm/2.0/en/cookbook/dqluser-dened-functions.html). In Symfony, si possono registrare funzioni DQL personalizzate nel modo seguente: YAML
# app/config/config.yml doctrine: orm: # ... entity_managers: default: # ... dql: string_functions: test_string: Acme\HelloBundle\DQL\StringFunction second_string: Acme\HelloBundle\DQL\SecondStringFunction numeric_functions: test_numeric: Acme\HelloBundle\DQL\NumericFunction datetime_functions: test_datetime: Acme\HelloBundle\DQL\DatetimeFunction

XML
<!-- app/config/config.xml --> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:doctrine="http://symfony.com/schema/dic/doctrine"

310

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/ser http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/doc

<doctrine:config> <doctrine:orm> <!-- ... --> <doctrine:entity-manager name="default"> <!-- ... --> <doctrine:dql> <doctrine:string-function name="test_string>Acme\HelloBundle\DQL\StringFunct <doctrine:string-function name="second_string>Acme\HelloBundle\DQL\SecondStr <doctrine:numeric-function name="test_numeric>Acme\HelloBundle\DQL\NumericFu <doctrine:datetime-function name="test_datetime>Acme\HelloBundle\DQL\Datetim </doctrine:dql> </doctrine:entity-manager> </doctrine:orm> </doctrine:config> </container>

PHP
// app/config/config.php $container->loadFromExtension(doctrine, array( orm => array( // ... entity_managers => array( default => array( // ... dql => array( string_functions => array( test_string => Acme\HelloBundle\DQL\StringFunction, second_string => Acme\HelloBundle\DQL\SecondStringFunction, ), numeric_functions => array( test_numeric => Acme\HelloBundle\DQL\NumericFunction, ), datetime_functions => array( test_datetime => Acme\HelloBundle\DQL\DatetimeFunction, ), ), ), ), ), ));

3.1.18 Come personalizzare la resa dei form


Symfony permette unampia variet di modi per personalizzare la resa di un form. In questa guida, si apprender come personalizzare ogni possibile parte del form con il minimo sforzo possibile se si utilizza Twig o PHP come motore di template. Le basi della resa dei form Si ricordi che le label, gli errori e i widget HTML di un campo del form possono essere facilmente resi usando la funzione di Twig form_row oppure il metodo dellhelper PHP row:

3.1. Ricettario

311

Documentazione Symfony, Release 2.0

Twig
{{ form_row(form.age) }}

PHP
<?php echo $view[form]->row($form[age]) }} ?>

possibile anche rendere individualmente ogni parte dellalbero del campo: Twig
<div> {{ form_label(form.age) }} {{ form_errors(form.age) }} {{ form_widget(form.age) }} </div>

PHP
<div> <?php echo $view[form]->label($form[age]) }} ?> <?php echo $view[form]->errors($form[age]) }} ?> <?php echo $view[form]->widget($form[age]) }} ?> </div>

In entrambi i casi le label, gli errori e i widget HTML del form, sono resi utilizzando un set di markup che sono standard con Symfony. Per esempio, entrambi i template sopra renderebbero:
<div> <label for="form_age">Et</label> <ul> <li>Questo campo obbligatorio</li> </ul> <input type="number" id="form_age" name="form[age]" /> </div>

Per prototipare velocemente e testare un form, possibile rendere lintero form semplicemente con una riga: Twig
{{ form_widget(form) }}

PHP
<?php echo $view[form]->widget($form) }} ?>

Nella restante parte di questa ricetta, verr mostrato come ogni parte del codice del form pu essere modicato a diversi livelli. Per maggiori informazioni sulla resa dei form in generale, disponibile Rendere un form in un template. Cosa sono i temi di un form? Symfony usa frammenti di form, piccoli pezzi di template che rendono semplicemente alcune parti, per rendere ogni parte di un form: la label del campo, gli errori, campi di testo input, tag select, ecc. I frammenti sono deniti come dei blocchi in Twig e come dei template in PHP. Un tema non nientaltro che un insieme di frammenti che si vuole utilizzare quando si rende un form. In altre parole, se si vuole personalizzare una parte della resa del form, possibile importare un tema che contiene una personalizzazione del frammento appropriato del form.

312

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Symfony ha un tema predenito (form_div_layout.html.twig (https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twi in Twig e FrameworkBundle:Form in PHP), che denisce tutti i frammenti necessari per rendere ogni parte di un form. Nella prossima sezione si potr vedere come personalizzare un tema, sovrascrivendo qualcuno o tutti i suoi frammenti. Per esempio, quando reso il widget di un campo integer, generato un campo input number Twig
{{ form_widget(form.age) }}

PHP
<?php echo $view[form]->widget($form[age]) ?>

rende:
<input type="number" id="form_age" name="form[age]" required="required" value="33" />

Internamente, Symfony utilizza il frammento integer_widget per rendere il campo. Questo perch il tipo di campo integer e si vuole rendere il widget (in contrapposizione alla sua label o ai suoi errors). In Twig per impostazione predenita il blocco integer_widget dal template form_div_layout.html.twig. In PHP il le integer_widget.html.php FrameworkBundle/Resources/views/Form. Limplementazione del frammento integer_widget sar simile a: Twig
{% block integer_widget %} {% set type = type|default(number) %} {{ block(field_widget) }} {% endblock integer_widget %}

posizionato

nella

cartella

PHP
<!-- integer_widget.html.php -->

<?php echo $view[form]->renderBlock(field_widget, array(type => isset($type) ? $type : "nu

Come possibile vedere, questo frammento rende un altro frammento: field_widget: Twig
{% block field_widget %} {% set type = type|default(text) %} <input type="{{ type }}" {{ block(widget_attributes) }} value="{{ value }}" /> {% endblock field_widget %}

PHP
<!-- FrameworkBundle/Resources/views/Form/field_widget.html.php --> <input type="<?php echo isset($type) ? $view->escape($type) : "text" ?>" value="<?php echo $view->escape($value) ?>" <?php echo $view[form]->renderBlock(attributes) ?> />

3.1. Ricettario

313

Documentazione Symfony, Release 2.0

Il punto che il frammento detta loutput HTML di ogni parte del form. Per personalizzare loutput del form, necessario soltanto identicare e sovrascrivere il frammento corretto. Un set di queste personalizzazioni di frammenti conosciuto come tema di un form. Quando viene reso un form, possibile scegliere quale tema del form si vuole applicare. In Twig un tema un singolo le di template e i frammente sono dei blocchi deniti in questo le. In PHP un tema una cartella e i frammenti sono singoli le di template in questa cartella. Conoscere quale blocco personalizzare In questo esempio, il nome del frammento personalizzato integer_widget perch si vuole sovrascrivere lHTML del widget per tutti i tipi di campo integer. Se si ha la necessit di personalizzare campi textarea, si deve personalizzare il widget textarea_widget. Come possibile vedere, il nome del frammento una combinazione del tipo di campo e ogni parte del campo viene resa (es. widget, label, errors, row). Come tale, per personalizzare la resa degli errori solo per il campo input text, bisogna personalizzare il frammento text_errors. Pi frequentemente, tuttavia, si vorr personalizzare la visualizzazione degli errori attraverso tutti i campi. possibile fare questo personalizzando il frammento field_errors. Questo si avvale delle ereditariet del tipo di campo. Specicamente dato che il tipo text esteso dal tipo field, il componente del form guarder per prima cosa al tipo-specico di frammento (es. text_errors) prima di ricadere sul nome del frammento del suo genitore, se non esiste (es. field_errors). Per maggiori informazioni sullargomento, si veda Nomi per i frammenti di form.

Temi del Form Per vedere la potenza dei temi di un form, si supponga di voler impacchettare ogni campo di input number in un tag div. La chiave per fare questo personalizzare il frammento integer_widget. Temi del form in Twig Per personalizzare il blocco dei campi del form in Twig, si hanno due possibilit su dove il blocco del form personalizzato pu essere implementato: Metodo Nello stesso template del form In un template separato Pro Veloce e facile Riutilizzabile in pi template Contro Non utilizzabile in altri template Richiede la creazione di un template extra

Entrambi i metodi hanno lo stesso effetto ma sono consigliati per situazioni differenti.
Metodo 1: Nello stesso template del form

Il modo pi facile di personalizzare il blocco integer_widget personalizzarlo direttamente nel template che sta attualmente rendendo il form.
{% extends ::base.html.twig %} {% form_theme form _self %} {% block integer_widget %} <div class="integer_widget"> {% set type = type|default(number) %} {{ block(field_widget) }}

314

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

</div> {% endblock %} {% block content %} {# render the form #} {{ form_row(form.age) }} {% endblock %}

Utilizzando il tag speciale {% form_theme form _self %}, Twig guarda nello stesso template per ogni blocco di form sovrascritto. Assumendo che il campo form.age un tipo di campo integer, quando il suo widget reso, verr utilizzato il blocco personalizzato integer_widget. Lo svantaggio di questo metodo che il blocco del form personalizzato non pu essere riutilizzato quando si rende un altro form in altri template. In altre parole, questo metodo molto utile quando si effettuano personalizzazioni che sono speciche per singoli form nellapplicazione. Se si vuole riutilizzare una personalizzazione attraverso alcuni (o tutti) form nellapplicazione, si legga la prossima sezione.
Metodo 2: In un template separato

possibile scegliere di mettere il blocco del form personalizzato integer_widget in un interamente in un template separato. Il codice e il risultato nale sono gli stessi, ma ora possibile riutilizzare la personalizzazione del formi in diversi template:
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} {% block integer_widget %} <div class="integer_widget"> {% set type = type|default(number) %} {{ block(field_widget) }} </div> {% endblock %}

Ora che stato creato il blocco del form personalizzato, si ha la necessit di dire a Symfony di utilizzarlo. Nel template dove si sta rendendo il form, dire a Symfony di utilizzare il template attraverso il tag form_theme:
{% form_theme form AcmeDemoBundle:Form:fields.html.twig %} {{ form_widget(form.age) }}

Quando il widget form.age reso, Symfony utilizzer il blocco integer_widget dal nuovo template e il tag input sar incorporato nel div specicato nel blocco personalizzato. Temi del form in PHP Quando si utilizza PHP come motore per i temi, lunico metodo per personalizzare un frammento creare un nuovo le di tema, in modo simile al secondo metodo adottato per Twig. Bisogna nominare il le del tema dopo il frammento. Bisogna creare il le integer_widget.html.php per personalizzare il frammento integer_widget.
<!-- src/Acme/DemoBundle/Resources/views/Form/integer_widget.html.php -->

<div class="integer_widget"> <?php echo $view[form]->renderBlock(field_widget, array(type => isset($type) ? $type : "num </div>

3.1. Ricettario

315

Documentazione Symfony, Release 2.0

Ora che stato creato il tema del form personalizzato, bisogna dire a Symfony di utilizzarlo. Nel template dove viene attualmente reso il form, dire a Symfony di utilizzare il tema attraverso il metodo setTheme dellhelper:
<?php $view[form]->setTheme($form, array(AcmeDemoBundle:Form)) ;?> <?php $view[form]->widget($form[age]) ?>

Quando il widget form.age viene reso, Symfony utilizzer il tema personalizzato integer_widget.html.php e il tag input sar contenuto in un elemento div. Referenziare blocchi di form (specico per Twig)

Finora, per sovrascrivere un particolare blocco del form, il metodo migliore copiare il blocco di default da form_div_layout.html.twig (https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Twig/Resources/views/Form/form_d incollarlo in un template differente, e personalizzarlo. In molti casi, possibile evitare di fare questo referenziando il blocco di base quando lo si personalizza. Tutto ci semplice da fare, ma varia leggermente a seconda se le personalizzazioni del blocco di form sono nello stesso template del form o in un template separato.
Referenziare blocchi dallinterno dello stesso template del form

Importare i blocchi aggiungendo un tag use nel template da dove si sta rendendo il form:
{% use form_div_layout.html.twig with integer_widget as base_integer_widget %}

Ora, quando sono importati i blocchi da form_div_layout.html.twig (https://github.com/symfony/symfony/blob/master/src/Symfony/Brid il blocco integer_widget chiamato base_integer_widget. Questo signica che quando viene ridenito il blocco integer_widget, possibile referenziare il markup di default tramite base_integer_widget:
{% block integer_widget %} <div class="integer_widget"> {{ block(base_integer_widget) }} </div> {% endblock %}

Referenziare blocchi base da un template esterno

Se la personalizzazione stata fatta su un template esterno, possibile referenziare il blocco base utilizzando la funzione di Twig parent():
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} {% extends form_div_layout.html.twig %} {% block integer_widget %} <div class="integer_widget"> {{ parent() }} </div> {% endblock %}

Nota: Non possibile referenziare il blocco base quando si usa PHP come motore di template. Bisogna copiare manualmente il contenuto del blocco base nel nuovo le di template.

316

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Personalizzare lo strato applicativo Se si vuole che una determinata personalizzazione del form sia globale nellapplicazione, possibile realizzare ci effettuando personalizzazioni del form in un template esterno e dopo importarlo nella congurazione dellapplicazione:
Twig

Utilizzando la seguente congurazione, ogni blocco di form personalizzato nel template AcmeDemoBundle:Form:fields.html.twig verr utilizzato globalmente quando un form verr reso. YAML
# app/config/config.yml twig: form: resources: - AcmeDemoBundle:Form:fields.html.twig # ...

XML
<!-- app/config/config.xml --> <twig:config ...> <twig:form> <resource>AcmeDemoBundle:Form:fields.html.twig</resource> </twig:form> <!-- ... --> </twig:config>

PHP
// app/config/config.php $container->loadFromExtension(twig, array( form => array(resources => array( AcmeDemoBundle:Form:fields.html.twig, )) // ... ));

Di default, Twig utilizza un layout a div quando rende i form. Qualcuno, tuttavia, potrebbe preferire rendere i form in un layout a tabella. Utilizzare la risorsa form_table_layout.html.twig come layout: YAML
# app/config/config.yml twig: form: resources: [form_table_layout.html.twig] # ...

XML
<!-- app/config/config.xml -->

3.1. Ricettario

317

Documentazione Symfony, Release 2.0

<twig:config ...> <twig:form> <resource>form_table_layout.html.twig</resource> </twig:form> <!-- ... --> </twig:config>

PHP
// app/config/config.php $container->loadFromExtension(twig, array( form => array(resources => array( form_table_layout.html.twig, )) // ... ));

Se si vuole effettuare un cambiamento soltanto in un template, aggiungere la seguente riga al le di template piuttosto che aggiungere un template come risorsa:
{% form_theme form form_table_layout.html.twig %}

Si osservi che la variabile form nel codice sottostante la variabile della vista form che stata passata al template.
PHP

Utilizzando la congurazione seguente, ogni frammento di form personalizzato nella cartella src/Acme/DemoBundle/Resources/views/Form sar utilizzato globalmente quando un form viene reso. YAML
# app/config/config.yml framework: templating: form: resources: - AcmeDemoBundle:Form # ...

XML
<!-- app/config/config.xml --> <framework:config ...> <framework:templating> <framework:form> <resource>AcmeDemoBundle:Form</resource> </framework:form> </framework:templating> <!-- ... --> </framework:config>

PHP

318

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

// app/config/config.php // PHP $container->loadFromExtension(framework, array( templating => array(form => array(resources => array( AcmeDemoBundle:Form, ))) // ... ));

Per impostazione predenita, il motore PHP utilizza un layout a div quando rende i form. Qualcuno, tuttavia, potrebbe preferire rendere i form in un layout a tabella. Utilizzare la risorsa FrameworkBundle:FormTable per il layout: YAML
# app/config/config.yml framework: templating: form: resources: - FrameworkBundle:FormTable

XML
<!-- app/config/config.xml --> <framework:config ...> <framework:templating> <framework:form> <resource>FrameworkBundle:FormTable</resource> </framework:form> </framework:templating> <!-- ... --> </framework:config>

PHP
// app/config/config.php $container->loadFromExtension(framework, array( templating => array(form => array(resources => array( FrameworkBundle:FormTable, ))) // ... ));

Se si vuole effettuare un cambiamento soltanto in un template, aggiungere la seguente riga al le di template piuttosto che aggiungere un template come risorsa:
<?php $view[form]->setTheme($form, array(FrameworkBundle:FormTable)); ?>

Si osservi che la variabile $form nel codice sottostante la variabile della vista form che stata passata al template.

3.1. Ricettario

319

Documentazione Symfony, Release 2.0

Personalizzare un singolo campo Finora, sono stati mostrati i vari modi per personalizzare loutput di un widget di tutti i tipi di campo testuali. Ma anche possibile personalizzare singoli campi. Per esempio, si supponga di avere due campi di testo, first_name e last_name, ma si vuole personalizzare solo uno dei campi. LO si pu fare personalizzando un frammento, in cui il nome una combinazione dellattributo id del campo e in cui parte del campo viene personalizzato. Per esempio: Twig
{% form_theme form _self %} {% block _product_name_widget %} <div class="text_widget"> {{ block(field_widget) }} </div> {% endblock %} {{ form_widget(form.name) }}

PHP
<!-- Main template --> <?php echo $view[form]->setTheme($form, array(AcmeDemoBundle:Form)); ?> <?php echo $view[form]->widget($form[name]); ?> <!-- src/Acme/DemoBundle/Resources/views/Form/_product_name_widget.html.php --> <div class="text_widget"> echo $view[form]->renderBlock(field_widget) ?> </div>

Qui, il frammento _product_name_widget denisce il template da utilizzare per il campo del quale lid product_name (e il nome product[name]). Suggerimento: La porzione del campo product il nome del form, che pu essere impostato manualmente o generato automaticamente basandosi sul tipo di nome del form (es. ProductType equivale a product). Se non si sicuri di cosa sia il nome del form, basta semplicemente vedere il sorgente del form generato. possibile sovrascrivere il markup per un intera riga di campo utilizzando lo stesso metodo: Twig
{% form_theme form _self %} {% block _product_name_row %} <div class="name_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endblock %}

PHP
<!-- _product_name_row.html.php --> <div class="name_row">

320

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

<?php echo $view[form]->label($form) ?> <?php echo $view[form]->errors($form) ?> <?php echo $view[form]->widget($form) ?> </div>

Altre personalizzazioni comuni Finora, questa ricetta ha illustrato diversi modi per personalizzare la resa di un form. La chiave di tutto personalizzare uno specico frammento che corrisponde alla porzione del form che si vuole controllare (si veda nominare i blocchi dei form). Nella prossima sezone, si potr vedere come possibile effettuare diverse personalizzazioni comuni per il form. Per applicare queste personalizzazioni, si utilizzi uno dei metodi descritti nella sezione Temi del Form.
Personalizzare loutput degli errori

Nota: Il componente del form gestisce soltanto come gli errori di validazione vengono resi, e non gli attuali messaggi di errore di validazione. I messaggi derrore sono determinati dai vincoli di validazione applicati agli oggetti. Per maggiori informazioni, si veda il capitolo validazione. Ci sono diversi modi di personalizzare come gli errori sono resi quando un form viene inviato con errori. I messaggi di errore per un campo sono resi quando si utilizza lhelper form_errors: Twig
{{ form_errors(form.age) }}

PHP
<?php echo $view[form]->errors($form[age]); ?>

Di default, gli errori sono resi dentro una lista non ordinata:
<ul> <li>Questo campo obbligatorio</li> </ul>

Per sovrascrivere come gli errori sono resi per tutti i campi, basta semplicemente copiare, incollare e personalizzare il frammento field_errors. Twig
{% block field_errors %} {% spaceless %} {% if errors|length > 0 %} <ul class="error_list"> {% for error in errors %} <li>{{ error.messageTemplate|trans(error.messageParameters, validators) }}</li> {% endfor %} </ul> {% endif %} {% endspaceless %} {% endblock field_errors %}

PHP

3.1. Ricettario

321

Documentazione Symfony, Release 2.0

<!-- fields_errors.html.php --> <?php if ($errors): ?> <ul class="error_list"> <?php foreach ($errors as $error): ?> <li><?php echo $view[translator]->trans( $error->getMessageTemplate(), $error->getMessageParameters(), validators ) ?></li> <?php endforeach; ?> </ul> <?php endif ?>

Suggerimento: Si veda Temi del Form per come applicare questa personalizzazione. anche possibile personalizzare loutput dellerrore per uno specico tipo di campo. Per esempio, alcuni errori che sono globali al form (es. non specici a un singolo campo) sono resi separatamente, di solito allinizio del form: Twig
{{ form_errors(form) }}

PHP
<?php echo $view[form]->render($form); ?>

Per personalizzare solo il markup utilizzato per questi errori, si segue la stesa strada de codice sopra ma verr chiamato il blocco form_errors (Twig) / il le form_errors.html.php (PHP). Ora, quando sono resi gli errori per il form, i frammenti personalizzati verranno utilizzati al posto dei field_errors di default.
Personalizzare una riga del form

Quando possibile modicarlo, la strada pi facile per rendere il campo di un form attraverso la funzione form_row, che rende letichetta, gli errori e il widget HTML del campo. Per personalizzare il markup utilizzato per rendere tutte le righe del campo di un form bisogna sovrascrivere il frammento field_row. Per esempio, si supponga di voler aggiungere una classe allelemento div per ogni riga: Twig
{% block field_row %} <div class="form_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endblock field_row %}

PHP
<!-- field_row.html.php --> <div class="form_row"> <?php echo $view[form]->label($form) ?> <?php echo $view[form]->errors($form) ?> <?php echo $view[form]->widget($form) ?> </div>

322

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Suggerimento: Si veda Temi del Form per conoscere come applicare questa personalizzazione.

Aggiungere un asterisco obbligatorio alle label del campo

possibile denotare tutti i campi obbligatori con un asterisco (*), semplicemente personalizzando il frammento field_label. In Twig, se si sta personalizzando il form allinterno dello stesso template del form, basta modicare il tag use e aggiungere le seguenti righe:
{% use form_div_layout.html.twig with field_label as base_field_label %} {% block field_label %} {{ block(base_field_label) }} {% if required %} <span class="required" title="This field is required">*</span> {% endif %} {% endblock %}

In Twig, se si sta personalizzando il form allinterno di un template separato, bisogna utilizzare le seguenti righe:
{% extends form_div_layout.html.twig %} {% block field_label %} {{ parent() }} {% if required %} <span class="required" title="Questo campo obbligatorio">*</span> {% endif %} {% endblock %}

Quando si usa PHP come motore di template bisogna copiare il contenuto del template originale:
<!-- field_label.html.php -->

<!-- original content --> <label for="<?php echo $view->escape($id) ?>" <?php foreach($attr as $k => $v) { printf(%s="%s" , $ <!-- personalizzazione --> <?php if ($required) : ?> <span class="required" title="Questo campo obbligatorio">*</span> <?php endif ?>

Suggerimento: Si veda Temi del Form per sapere come effettuare questa personalizzazione.

Aggiungere messaggi di aiuto

possibile personalizzare i widget del form per ottenere un messaggio di aiuto opzionale. In Twig, se si sta personalizzando il form allinterno dello stesso template del form, basta modicare il tag use e aggiungere le seguenti righe:

3.1. Ricettario

323

Documentazione Symfony, Release 2.0

{% use form_div_layout.html.twig with field_widget as base_field_widget %} {% block field_widget %} {{ block(base_field_widget) }} {% if help is defined %} <span class="help">{{ help }}</span> {% endif %} {% endblock %}

In Twig, se si sta personalizzando il form allinterno di un template separato, bisogna utilizzare le seguenti righe:
{% extends form_div_layout.html.twig %} {% block field_widget %} {{ parent() }} {% if help is defined %} <span class="help">{{ help }}</span> {% endif %} {% endblock %}

Quando si usa PHP come motore di template bisogna copiare il contenuto del template originale:
<!-- field_widget.html.php --> <!-- contenuto originale --> <input type="<?php echo isset($type) ? $view->escape($type) : "text" ?>" value="<?php echo $view->escape($value) ?>" <?php echo $view[form]->renderBlock(attributes) ?> /> <!-- Personalizzazione --> <?php if (isset($help)) : ?> <span class="help"><?php echo $view->escape($help) ?></span> <?php endif ?>

Per rendere un messaggio di aiuto sotto al campo, passare nella variabile help: Twig
{{ form_widget(form.title, { help: foobar }) }}

PHP
<?php echo $view[form]->widget($form[title], array(help => foobar)) ?>

Suggerimento: Si veda Temi del Form per sapere come applicare questa congurazione.

3.1.19 Utilizzare i data transformer


Spesso si avr la necessit di trasformare i dati che lutente ha immesso in un form in qualcosa di diverso da utilizzare nel programma. Tutto questo si potrebbe fare manualmente nel controller ma nel caso in cui si volesse utilizzare il form in posti diversi?

324

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Supponiamo di avere una relazione uno-a-uno tra Task e Rilasci, per esempio un Task pu avere un rilascio associato. Avere una casella di riepilogo con la lista di tutti i rilasci pu portare ad una casella di riepilogo molto lunga nella quale risulter impossibile cercare qualcosa. Si vorrebbe, piuttosto, aggiungere un campo di testo nel quale lutente pu semplicemente inserire il numero del rilascio. Nel controller si pu convertire questo numero di rilascio in un task attuale ed eventualmente aggiungere errori al form se non stato trovato ma questo non il modo migliore di procedere. Sarebbe meglio se questo rilascio fosse cercato automaticamente e convertito in un oggetto rilascio, in modo da poterlo utilizzare nellazione. In questi casi entrano in gioco i data transformer. Come prima cosa, bisogna creare un form che abbia un data transformer collegato che, dato un numero, ritorni un oggetto Rilascio: il tipo selettore rilascio. Eventualmente sar semplicemente un campo di testo, dato che la congurazione dei campi che estendono impostata come campo di testo, nel quale si potr inserire il numero di rilascio. Il campo di testo far comparire un errore se verr inserito un numero di rilascio che non esiste:
// src/Acme/TaskBundle/Form/IssueSelectorType.php namespace Acme\TaskBundle\Form\Type; use use use use Symfony\Component\Form\AbstractType; Symfony\Component\Form\FormBuilder; Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer; Doctrine\Common\Persistence\ObjectManager;

class IssueSelectorType extends AbstractType { private $om; public function __construct(ObjectManager $om) { $this->om = $om; } public function buildForm(FormBuilder $builder, array $options) { $transformer = new IssueToNumberTransformer($this->om); $builder->appendClientTransformer($transformer); } public function getDefaultOptions(array $options) { return array( invalid_message=>Il rilascio che cerchi non esiste. ); } public function getParent(array $options) { return text; } public function getName() { return issue_selector; } }

Suggerimento: possibile utilizzare i transformer senza necessariamente creare un nuovo form personalizzato invocando la funzione appendClientTransformer su qualsiasi eld builder:

3.1. Ricettario

325

Documentazione Symfony, Release 2.0

use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { // ... // si assume che lentity manager stato passato come opzione $entityManager = $options[em]; $transformer = new IssueToNumberTransformer($entityManager); // utilizza un campo di testo ma trasforma il testo in un oggetto rilascio $builder ->add(issue, text) ->appendClientTransformer($transformer) ; } // ... }

quindi, creiamo il data transformer che effettua la vera e propria conversione:


// src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php namespace Acme\TaskBundle\Form\DataTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\DataTransformerInterface; use Doctrine\Common\Persistence\ObjectManager; class IssueToNumberTransformer implements DataTransformerInterface { private $om; public function __construct(ObjectManager $om) { $this->om = $om; } // trasforma loggetto Rilascio in una stringa public function transform($val) { if (null === $val) { return ; } return $val->getNumber(); } // trasforma il numero rilascio in un oggetto rilascio public function reverseTransform($val) { if (!$val) { return null; }

$issue = $this->om->getRepository(AcmeTaskBundle:Issue)->findOneBy(array(number => $val))

326

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

if (null === $issue) { throw new TransformationFailedException(sprintf(Un rilascio con numero %s non esiste, $ } return $issue; } }

Inne, poich abbiamo deciso di creare un campo di testo personalizzato che utilizza il data transformer, bisogna registrare il tipo nel service container, in modo che lentity manager pu essere automaticamente iniettato: YAML
services: acme_demo.type.issue_selector: class: Acme\TaskBundle\Form\IssueSelectorType arguments: ["@doctrine.orm.entity_manager"] tags: - { name: form.type, alias: issue_selector }

XML
<service id="acme_demo.type.issue_selector" class="Acme\TaskBundle\Form\IssueSelectorType"> <argument type="service" id="doctrine.orm.entity_manager"/> <tag name="form.type" alias="issue_selector" /> </service>

Ora possibile aggiungere il tipo al form dal suo alias come segue:
// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add(task); $builder->add(dueDate, null, array(widget => single_text)); $builder->add(issue, issue_selector); } public function getName() { return task; } }

Ora sar molto facile in qualsiasi punto dellapplicazione, usare questo tipo selettore per selezionare un rilascio da un numero. Tutto questo, senza aggiungere nessuna logica al controllore. Se si vuole creare un nuovo rilascio quando viene inserito un numero di rilascio sconosciuto, possibile istanziarlo piuttosto che lanciare leccezione TransformationFailedException e inoltre persiste nel proprio entity manager se il task non ha opzioni a cascata per il rilascio.

3.1. Ricettario

327

Documentazione Symfony, Release 2.0

3.1.20 Come generare dinamicamente form usando gli eventi form


Prima di addentrarci nella generazione dinamica dei form, diamo unocchiata veloce alla classe dei form:
//src/Acme/DemoBundle/Form/ProductType.php namespace Acme\DemoBundle\Form use Symfony\Component\Form\AbstractType use Symfony\Component\Form\FormBuilder; class ProductType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add(nome); $builder->add(prezzo); } public function getName() { return prodotto; } }

Nota: Se questa particolare sezione di codice non familiare, probabilmente necessario tornare indietro e come prima cosa leggere il capitolo sui form prima di andare avanti. Si assuma per un momento che questo form utilizzi una classe immaginaria prodotto questa ha solo due attributi rilevanti (nome e prezzo). Il form generato da questa classe avr lo stesso aspetto, indipendentemente se un nuovo prodotto sta per essere creato oppure se un prodotto esistente sta per essere modicato (es. un prodotto ottenuto da database). Si supponga ora, di non voler abilitare lutente alla modica del campo nome una volta che loggetto stato creato. Per fare ci si pu dare unocchiata al sistema Event Dispatcher, che analizza loggetto e modica il form basato sull oggetto prodotto. In questa ricetta si imparer come aggiungere questo livello di essibilit ai form. Aggiungere un evento sottoscrittore alla classe di un form Invece di aggiungere direttamente il widget nome tramite la classe dei form ProductType si deleghi la responsabilit di creare questo particolare campo a un evento sottoscrittore:
//src/Acme/DemoBundle/Form/ProductType.php namespace Acme\DemoBundle\Form use Symfony\Component\Form\AbstractType use Symfony\Component\Form\FormBuilder; use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber; class ProductType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $subscriber = new AddNameFieldSubscriber($builder->getFormFactory()); $builder->addEventSubscriber($subscriber); $builder->add(price); }

328

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

public function getName() { return prodotto; } }

Levento sottoscrittore passato dalloggetto FormFactory nel suo costruttore, quindi il nuovo sottoscrittore in grado di creare il widget del form una volta che viene noticata dallevento inviato durante la creazione del form. Dentro la classe dellevento sottoscrittore Lobiettivo di creare un campo nome solo se loggetto Prodotto sottostante nuovo (es. non stato persistito nel database). Basandosi su questo, lsottoscrittore potrebbe essere simile a questo:
// src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php namespace Acme\DemoBundle\Form\EventListener; use use use use Symfony\Component\Form\Event\DataEvent; Symfony\Component\Form\FormFactoryInterface; Symfony\Component\EventDispatcher\EventSubscriberInterface; Symfony\Component\Form\FormEvents;

class AddNameFieldSubscriber implements EventSubscriberInterface { private $factory; public function __construct(FormFactoryInterface $factory) { $this->factory = $factory; } public static function getSubscribedEvents() { // Indica al dispacher che si vuole ascoltare levento form.pre_set_data // e che verr invocato il metodo preSetData. return array(FormEvents::PRE_SET_DATA => preSetData); } public function preSetData(DataEvent $event) { $data = $event->getData(); $form = $event->getForm(); // // // // // if } // controlla se loggetto Prodotto nuovo if (!$data->getId()) { $form->add($this->factory->createNamed(text, name)); } Dutante la creazione del form, setData chiamata con parametri null dal costruttore di FormBuilder. Si interessati a quando setData invocato con loggetto Entity attuale (se nuovo, oppure recuperato con Doctrine). Bisogner uscire dal metoro se la condizione restituisce null. (null === $data) { return;

3.1. Ricettario

329

Documentazione Symfony, Release 2.0

} }

Attenzione: facile fraintendere lo scopo dellistruzione if (null === $data) dellevento sottoscrittore. Per comprendere appieno il suo ruolo, bisogna dare uno sguardo alla classe Form (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php) e prestare attenzione a dove setData() invocato alla ne del costruttore, nonch al metodo setData() stesso. La riga FormEvents::PRE_SET_DATA viene attualmente risolta nella stringa form.pre_set_data. La classe FormEvents (https://github.com/symfony/Form/blob/master/FormEvents.php) ha uno scopo organizzativo. Ha una posizione centralizzata in quello che si pu trovare tra i diversi eventi dei form disponibili.

Anche se in questo esempio si potrebbe utilizzare levento form.set_data o anche levento form.post_set_data, utilizzando form.pre_set_data si garantisce che i dati saranno ottenuti dalloggetto Event che non stato modicato da nessun altro sottoscrittore o ascoltatore. Questo perch form.pre_set_data passa alloggetto DataEvent (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/DataEvent.php) invece delloggetto FilterDataEvent (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/FilterDataEvent.php passato dallevento form.set_data. DataEvent (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/E a differenza del suo glio FilterDataEvent (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Event/Filte non ha il metodo setData(). Nota: possibile consultare la lista completa degli eventi del form tramite la classe FormEvents (https://github.com/symfony/Form/blob/master/FormEvents.php), nel bundle dei form.

3.1.21 Come unire una collezione di form


Con questa ricetta si apprender come creare un form che unisce una collezione di altri form. Ci pu essere utile, ad esempio, se si ha una classe Task e si vuole modicare/creare/cancellare oggetti Tag connessi a questo Task, allinterno dello stesso form. Nota: Con questa ricetta, si assume di utilizzare Doctrine come ORM. Se non si utilizza Doctrine (es. Propel o semplicemente una connessione a database), il tutto pressapoco simile. Se si utilizza Doctrine, si avr la necessit di aggiungere meta-dati Doctrine, includendo una relazione ManyToMany sulla colonna tags di Task. Iniziamo: supponiamo che ogni Task appartiene a pi oggetti Tags. Si crei una semplice classe Task:
// src/Acme/TaskBundle/Entity/Task.php namespace Acme\TaskBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; class Task { protected $description; protected $tags; public function __construct() { $this->tags = new ArrayCollection();

330

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

} public function getDescription() { return $this->description; } public function setDescription($description) { $this->description = $description; } public function getTags() { return $this->tags; } public function setTags(ArrayCollection $tags) { $this->tags = $tags; } }

Nota: ArrayCollection specica per Doctrine ed fondamentalmente la stessa cosa di utilizzare un array (ma deve essere un ArrayCollection) se si utilizza Doctrine. Ora, si crei una classe Tag. Come possibile vericare, un Task pu avere pi oggetti Tag:
// src/Acme/TaskBundle/Entity/Tag.php namespace Acme\TaskBundle\Entity; class Tag { public $name; }

Suggerimento: La propriet name qui pubblica, ma pu essere facilmente protetta o privata (ma in questo caso si avrebbe bisogno dei metodi getName e setName). Si crei ora una classe di form cosicch un oggetto Tag pu essere modicato dallutente:
// src/Acme/TaskBundle/Form/Type/TagType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class TagType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add(name); } public function getDefaultOptions(array $options) {

3.1. Ricettario

331

Documentazione Symfony, Release 2.0

return array( data_class => Acme\TaskBundle\Entity\Tag, ); } public function getName() { return tag; } }

Questo sufciente per rendere un form tag. Ma dal momento che lobiettivo nale permettere la modica dei tag di un task nello stesso form del task, bisogna creare un form per la classe Task. Da notare che si unisce una collezione di form TagType utilizzando il tipo di campo collection:
// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add(description); $builder->add(tags, collection, array(type => new TagType())); } public function getDefaultOptions(array $options) { return array( data_class => Acme\TaskBundle\Entity\Task, ); } public function getName() { return task; } }

Nel controllore, possibile inizializzare una nuova istanza di TaskType:


// src/Acme/TaskBundle/Controller/TaskController.php namespace Acme\TaskBundle\Controller; use use use use use Acme\TaskBundle\Entity\Task; Acme\TaskBundle\Entity\Tag; Acme\TaskBundle\Form\TaskType; Symfony\Component\HttpFoundation\Request; Symfony\Bundle\FrameworkBundle\Controller\Controller;

class TaskController extends Controller { public function newAction(Request $request) {

332

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

$task = new Task(); // codice fittizio: qui solo perch il Task ha alcuni tag // altrimenti, questo non un esempio interessante $tag1 = new Tag() $tag1->name = tag1; $task->getTags()->add($tag1); $tag2 = new Tag() $tag2->name = tag2; $task->getTags()->add($tag2); // fine del codice fittizio $form = $this->createForm(new TaskType(), $task); // processare in qualche modo il form, in una richiesta POST return $this->render(AcmeTaskBundle:Task:new.html.twig, array( form => $form->createView(), )); } }

Il template corrispondente ora abilitato a rendere entrambi i campi description per il form dei task, oltre tutti i form TagType che sono relazionati a questo Task. Nel controllore sottostante, viene aggiunto del codice ttizio cos da poterlo vedere in azione (dato che un Task non ha tags appena viene creato). Twig
{# src/Acme/TaskBundle/Resources/views/Task/new.html.twig #} {# ... #} {# rende solo il campo: description #} {{ form_row(form.description) }} <h3>Tags</h3> <ul class="tags"> {# itera per ogni tag esistente e rende solo il campo: nome #} {% for tag in form.tags %} <li>{{ form_row(tag.name) }}</li> {% endfor %} </ul> {{ form_rest(form) }} {# ... #}

PHP
<!-- src/Acme/TaskBundle/Resources/views/Task/new.html.php --> <!-- ... --> <h3>Tags</h3> <ul class="tags"> <?php foreach($form[tags] as $tag): ?> <li><?php echo $view[form]->row($tag[name]) ?></li> <?php endforeach; ?> </ul> <?php echo $view[form]->rest($form) ?> <!-- ... -->

3.1. Ricettario

333

Documentazione Symfony, Release 2.0

Quando lutente invia il form, i dati inviati per i campi di Tags sono utilizzato per costruire un ArrayCollection di oggetti Tag,che viene poi impostato sul campo tag dellistanza Task. La collezione Tags acessibile tramite $task->getTags() e pu essere persistita nel database oppure utilizzata dove se ne ha bisogno. Finora, tutto ci funziona bene, ma questo non permette di aggiungere nuovi dinamicamente todo o eliminare todo esistenti. Quindi, la modica dei todo esistenti funziona bene ma ancora non si possono aggiungere nuovi todo. Permettere nuovi todo con prototipo Permettere allutente di inserire dinamicamente nuovi todo signica che abbiamo la necessit di utilizzare Javascript. Precedentemente sono stati aggiunti due tags al nostro form nel controllore. Ora si ha la necessit che lutente possa aggiungere diversi form di tag secondo le sue necessit direttamente dal browser. Questo pu essere fatto attraverso un po di Javascript. La prima cosa di cui si ha bisogno di far capire alla collezione di form che ricever un numero indeterminato di tag. Finora sono stati aggiunti due tag e il form si aspetta di riceverne esattamente due, altrimenti verr lanciato un errore: Questo form non pu contenere campi extra. Per rendere essibile il form, bisogner aggiungere lopzione allow_add alla collezione di campi:
// ... public function buildForm(FormBuilder $builder, array $options) { $builder->add(description); $builder->add(tags, collection, array( type => new TagType(), allow_add => true, by_reference => false, )); }

Da notare che stata aggiunto by_reference => false. Questo perch non si sta inviando una referenza ad un tag esistente ma piuttosto si sta creando un nuovo tag quando si salva insieme il todo e i suoi tag. Lopzione allow_add effettua anche unaltra cosa. Aggiunge la propriet data-prototype al div che contiene la collezione del tag. Questa propriet contiene html da aggiungere allelemento Tag nella pagina, come il seguente esempio:

<div data-prototype="&lt;div&gt;&lt;label class=&quot; required&quot;&gt;$$name$$&lt;/label&gt;&lt;di </div>

Sar, quindi, possibile ottenere questa propriet da Javascript ed utilizzarla per visualizzare U nuovo form di Tag. Per rendere le cose semplici, verr incorporato jQuery nella pagina dato che permette la manipolazione della pagina in modalit cross-browser.. Come prima cosa, si aggiunga un nuovo form con la classe add_tag_link. Ogni volta che viene cliccato dallutente, verr aggiunto un tag vuoto:
$(.record_action).append(<li><a href="#" class="add_tag_link">Add a tag</a></li>);

Inoltre, bisogner includere un template che contenga il codice Javascript necessario per aggiungere gli elementi del form quando il link verr premuto.. Il codice pu essere semplice:

334

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

function addTagForm() { // Ottiene il div che detiene la collezione di tag var collectionHolder = $(#task_tags); // prende il data-prototype var prototype = collectionHolder.attr(data-prototype); // Sostituisce $$name$$ nellhtml del prototype in the prototypes HTML // affich sia un nummero basato sulla lunghezza corrente della collezione. form = prototype.replace(/\$\$name\$\$/g, collectionHolder.children().length); // Visualizza il form nella pagina collectionHolder.append(form); } // Aggiunge il link per aggiungere ulteriori tag $(.record_action).append(<li><a href="#" class="add_tag_link">Aggiungi un tag</a></li>); // Quando il link viene premuto, aggiunge un campo per immettere un nuovo tag $(a.jslink).click(function(event){ addTagForm(); });

Ora, ogni volta che un utente clicca sul link Aggiungi un tag, apparir un nuovo form nella pagina. Il form lato server consapevole di tutto e non si aspetter nessuna specica dimensione per la collezione Tag. Tutti i tag verranno aggiunti creando un nuovo Todo salvandolo insieme a esso. Per ulteriori dettagli, guarda collection form type reference. Permettere la rimozione di todo Questa sezione non ancora stata scritta, ma lo sar presto. Se si interessati a scrivere questa sezione, si veda Contribuire alla documentazione.

3.1.22 Come creare un tipo di campo personalizzato di un form


Symfony dotato di una serie di tipi di campi per la costruzione dei form. Tuttavia ci sono situazioni in cui necessario realizzare un campo personalizzato per uno scopo specico. Questa ricetta ipotizza che si abbia necessit di un capo personalizzato che contenga il genere di una persona, un nuovo campo basato su un campo di tipo scelta. Questa sezione spiega come il campo denito, come si pu personalizzare il layout e, inne, come possibile registrarlo per utilizzarlo nellapplicazione. Denizione del tipo di campo Per creare il tipo di campo personalizzato, necessario creare per prima la classe che rappresenta il campo. Nellesempio proposto la classe che realizza il tipo di campo sar chiamata GenderType e il le sar salvato nella cartella default contenente i capi del form, che <BundleName>\Form\Type. Assicurati che il campo estenda Symfony\Component\Form\AbstractType:
# src/Acme/DemoBundle/Form/Type/GenderType.php namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class GenderType extends AbstractType { public function getDefaultOptions(array $options) {

3.1. Ricettario

335

Documentazione Symfony, Release 2.0

return array( choices => array( m => Male, f => Female, ) ); } public function getParent(array $options) { return choice; } public function getName() { return gender; } }

Suggerimento: La cartella di memorizzazione di questo le non importante: la cartella Form\Type solo una convenzione. Qui, il valore di ritorno del metodo getParent indica che che si sta estendendo il tipo di campo choice. Questo signica che di default, sono ereditate tutte le logiche e la resa di queto tipo di campo. Per vedere alcune logiche, controlla la classe ChoiceType (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php). Ci sono tre metodi che sono particolarmente importanti: buildForm() - Ogni tipo di campo possiede un metodo buildForm, che permette di congurare e creare ogni campo/campi. Notare che questo lo stesso metodo che utilizzato per la preparazione del proprio form, e qui funziona allo stesso. buildView() - Questo metodo utilizzato per impostare le altre variabili che sono necessarie per la resa del campo nel template. Per esempio, nel tipo di campo ChoiceType (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php), la variabile multiple impostata e utilizzata nel template per impostare (o non impostare) lattributo multiple nel campo select. Si faccia riferimento a Creare un template per il campo_ per maggiori dettagli. getDefaultOptions() - Questo metodo denisce le opzioni per il tipo di form che possono essere utilizzate in buildForm() e buildView(). Ci sono molte opzioni comuni a tutti i campi (vedere FieldType (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php)), ma possibile crearne altre, quante sono necessarie. Suggerimento: Se si sta creando un campo che consiste di molti campi, assicurarsi di impostare come padre un tipo come form o qualcosaltro che estenda form. Nello stesso modo, se occorre modicare la vista di ogni sottotipo che estende il proprio tipo, utilizzare il metodo buildViewBottomUp(). Il metodo getName() restituisce un identicativo che dovrebbe essere unico allinterno dellapplicazione. Questo usato in vari posti, ad esempio nel momento in cui il tipo di form reso. Lobiettivo del nostro tipo di campo era di estendere il tipo choice per permettere la selezione del genere. Ci si ottiene impostando in maniera ssa le choices con la lista dei generi.

336

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Creazione del template per il campo Ogni campo reso da un template, che determinato in parte dal valore del metodo getName(). Per maggiori informazioni, vedere Cosa sono i temi di un form?. In questo caso, dato che il campo padre choice, non necessario fare altre attivit e il tipo di campo creato sar automaticamente reso come tipo choice. Ma per avere un esempio pi incisivo, supponiamo che il tipo di campo creato sia expanded (ad es. radio button o checkbox, al posto di un campo select), vogliamo sempre la resa del campo in un elemento ul. Nel template del proprio form (vedere il link sopra per maggiori dettagli), creare un blocco gender_widget per gestire questo caso:
{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} {% block gender_widget %} {% spaceless %} {% if expanded %} <ul {{ block(widget_container_attributes) }}> {% for child in form %} <li> {{ form_widget(child) }} {{ form_label(child) }} </li> {% endfor %} </ul> {% else %} {# far rendere il tag select al widget choice #} {{ block(choice_widget) }} {% endif %} {% endspaceless %} {% endblock %}

Nota: Assicurarsu che il presso del widget utilizzato sia corretto. In questo esempio il nome dovrebbe essere gender_widget, in base al valore restituito da getName. Inoltre, il le principale di congurazione dovrebbe puntare al template personalizzato del form, in modo che sia utilizzato per la resa di tutti i form.
# app/config/config.yml twig: form: resources: - AcmeDemoBundle:Form:fields.html.twig

Utilizzare il tipo di campo Ora si pu utilizzare il tipo di campo immediatamente, creando semplicemente una nuova istanza del tipo in un form:
// src/Acme/DemoBundle/Form/Type/AuthorType.php namespace Acme\DemoBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class AuthorType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) {

3.1. Ricettario

337

Documentazione Symfony, Release 2.0

$builder->add(gender_code, new GenderType(), array( empty_value => Choose a gender, )); } }

Questo funziona perch il GenderType() veramente semplice. Cosa succede se i valori del genere sono stati inseriti nella congurazione o nel database? La prossima sezione spiega come un tipo di campo pi complesso pu risolvere questa situazione. Creazione di un tipo di campo come servizio Finora, questa spiegazione ha assunto che si ha un tipo di campo molto semplice. Ma se fosse necessario accedere alla congurazione o al database o a qualche altro servizio, necessario registrare il tipo di campo come servizio. Per esempio, si supponga che i valori del genere siano memorizzati nella congurazione: YAML
# app/config/config.yml parameters: genders: m: Male f: Female

XML
<!-- app/config/config.xml --> <parameters> <parameter key="genders" type="collection"> <parameter key="m">Male</parameter> <parameter key="f">Female</parameter> </parameter> </parameters>

Per utilizzare i parametri, necessario denire il tipo di campo come un servizio, iniettando i valori dei parametri di genders come primo parametro del metodo __construct: YAML
# src/Acme/DemoBundle/Resources/config/services.yml services: form.type.gender: class: Acme\DemoBundle\Form\Type\GenderType arguments: - "%genders%" tags: - { name: form.type, alias: gender }

XML
<!-- src/Acme/DemoBundle/Resources/config/services.xml --> <service id="form.type.gender" class="Acme\DemoBundle\Form\Type\GenderType"> <argument>%genders%</argument> <tag name="form.type" alias="gender" /> </service>

Suggerimento: Assicurarsi che il le dei servizi sia importato. Leggere Importare la congurazione con imports per dettagli. 338 Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Assicurarsi che lattributo alias di tags corrisponda al valore restituito dal metodo getName denito precedentemente. Si vedr limportanza di questo nel momento in cui si utilizzer il tipo di campo. Ma prima, si aggiunga al metodo __construct di GenderType un parametro, che ricever la congurazione di gender:
# src/Acme/DemoBundle/Form/Type/GenderType.php namespace Acme\DemoBundle\Form\Type; // ... class GenderType extends AbstractType { private $genderChoices; public function __construct(array $genderChoices) { $this->genderChoices = $genderChoices; } public function getDefaultOptions(array $options) { return array( choices => $this->genderChoices, ); } // ... }

Benissimo! Il tipo GenderType ora caricato con i parametri di congurazione ed registrato come servizio. In quanto nella congurazione del servizio si utilizza nel form.type lalias, utilizzare il campo risulta molto semplice:
// src/Acme/DemoBundle/Form/Type/AuthorType.php namespace Acme\DemoBundle\Form\Type; // ... class AuthorType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add(gender_code, gender, array( empty_value => Choose a gender, )); } }

Notare che al posto di creare listanza di una nuova istanza, ora possibile riferirsi al tipo di campo tramite lalias utilizzato nella congurazione del servizio, gender.

3.1.23 Come creare vincoli di validazione personalizzati


possibile creare vincoli personalizzati estendendo la classe base Symfony\Component\Validator\Constraint. Le opzioni dei propri vincoli sono rappresentate come propriet pubbliche della classe. Ad esempio, i vincoli di Url includono le propriet message (messaggio) e protocols (protocolli):
namespace Symfony\Component\Validator\Constraints; use Symfony\Component\Validator\Constraint;

3.1. Ricettario

339

Documentazione Symfony, Release 2.0

/** * @Annotation */ class Url extends Constraint { public $message = This value is not a valid URL; public $protocols = array(http, https, ftp, ftps); }

Nota: In questo vincolo, lannotazione @Annotation necessaria per poterne rendere disponibile luso nelle altre classi. Come si pu vedere, un vincolo estremamente minimalistico. La validazione vera e propria effettuata da unaltra classe di validazione dei vincoli. La classe per la validazione dei vincoli denita dal metodo del vincolo validatedBy(), che usa una semplice logica predenita:
// nella classe base Symfony\Component\Validator\Constraint public function validatedBy() { return get_class($this).Validator; }

In altre parole, se si crea un Constraint, ovvero un vincolo, personalizzato (come MioVincolo), Symfony2, automaticamente, cercher anche unaltra la classe, MioVincoloValidator per effettuare la validazione vera e propria. Anche la classe validatrice semplice e richiede solo un metodo obbligatorio: isValid. Si prenda, ad esempio, la classe NotBlankValidator:
class NotBlankValidator extends ConstraintValidator { public function isValid($value, Constraint $constraint) { if (null === $value || === $value) { $this->setMessage($constraint->message); return false; } return true; } }

Validatori di vincoli con dipendenze Se il proprio vincolo ha delle dipendenze, come una connessione alla base dati, sar necessario congurarlo come servizio nel contenitore delle dipendenze. Questo servizio dovr includere il tag validator.constraint_validator e lattributo alias: YAML
services: validator.unique.nome_proprio_validatore: class: Nome\Pienamente\Qualificato\Della\Classe\Validatore tags: - { name: validator.constraint_validator, alias: nome_alias }

340

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

XML

<service id="validator.unique.nome_proprio_validatore" class="Nome\Pienamente\Qualificato\Della\ <argument type="service" id="doctrine.orm.default_entity_manager" /> <tag name="validator.constraint_validator" alias="nome_alias" /> </service>

PHP

$container ->register(validator.unique.nome_proprio_validatore, Nome\Pienamente\Qualificato\Della\Cl ->addTag(validator.constraint_validator, array(alias => nome_alias)) ;

La classe del vincolo dovr utilizzare lalias appena denito per riferirsi al validatore corretto:
public function validatedBy() { return nome_alias; }

Come gi detto, Symfony2 cercher automaticamente una classe il cui nome sia uguale a quello del vincolo ma con il sufsso Validator. Se il proprio validatore di vincoli denito come servizio, importante che si faccia loverride del metodo validatedBy() in modo tale che restituisca lalias utilizzato nella denizione del servizio altrimenti Symfony2 non utilizzer il servizio di validazione dei vincoli e istanzier la classe senza che le dipendenze vengano iniettate.

3.1.24 Come padroneggiare e creare nuovi ambienti


Ogni applicazione la combinazione di codice e di un insieme di congurazioni che determinano come il codice dovr lavorare. La congurazione pu denire il database da utilizzare, cosa dovr essere messo in cache e cosa non, o quanto esaustivi dovranno essere i log. In Symfony2, lidea di ambiente quella di eseguire il codice, utilizzando differenti congurazioni. Per esempio, lambiente dev dovrebbe usare una congurazione che renda lo sviluppo semplice e ricco di informazioni, mentre lambiente prod dovrebbe usare un insieme di congurazioni che ottimizzino la velocit. Ambienti differenti, differenti le di congurazione Una tipica applicazione Symfony2 inizia con tre ambienti: dev, prod e test. Come si gi detto, ogni ambiente rappresenta un modo in cui eseguire lintero codice con differenti congurazioni. Non dovrebbe destare sorpresa il fatto che ogni ambiente carichi i suoi propri le di congurazione. Se si utilizza il formato di congurazione YAML, verranno utilizzati i seguenti le: per lambiente dev: app/config/config_dev.yml per lambiente prod: app/config/config_prod.yml per lambiente test: app/config/config_test.yml Il funzionamento si basa su di un semplice comportamento predenito allinterno della classe AppKernel:
// app/AppKernel.php // ... class AppKernel extends Kernel { // ...

3.1. Ricettario

341

Documentazione Symfony, Release 2.0

public function registerContainerConfiguration(LoaderInterface $loader) { $loader->load(__DIR__./config/config_.$this->getEnvironment()..yml); } }

Come si pu vedere, quando Symfony2 viene caricato, utilizza lambiente per determinare quale le di congurazione caricare. Questo permette di avere ambienti differenti in modo elegante, efcace e trasparente. Ovviamente, in realt, ogni ambiente differisce solo per alcuni aspetti dagli altri. Generalmente, gli ambienti condividono gran parte della loro congurazione. Aprendo il le di congurazione di dev, si pu vedere come questo venga ottenuto facilmente e in modo trasparente: YAML
imports: - { resource: config.yml } # ...

XML
<imports> <import resource="config.xml" /> </imports> <!-- ... -->

PHP
$loader->import(config.php); // ...

Per condividere una congurazione comune, i le di congurazione di ogni ambiente importano, per iniziare, un le di congurazione comune (config.yml). Il resto del le potr deviare dalla congurazione predenita, sovrascrivendo i singoli parametri. Ad esempio, nellambiente dev, la barra delle applicazioni viene attivata modicando, nel le di congurazione di dev, il relativo parametro predenito: YAML
# app/config/config_dev.yml imports: - { resource: config.yml } web_profiler: toolbar: true # ...

XML
<!-- app/config/config_dev.xml --> <imports> <import resource="config.xml" /> </imports> <webprofiler:config toolbar="true" # ... />

342

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

PHP
// app/config/config_dev.php $loader->import(config.php); $container->loadFromExtension(web_profiler, array( toolbar => true, // .. ));

Eseguire unapplicazione in ambienti differenti Per eseguire lapplicazione in ogni ambiente, sar necessario caricarla utilizzando il front controller app.php (per lambiente prod) o utilizzando il front controller app_dev.php (per lambiente dev):
http://localhost/app.php http://localhost/app_dev.php -> ambiente *prod* -> ambiente *dev*

Nota: Le precedenti URL presuppongono che il server web sia congurato in modo da usare la cartella web/ dellapplicazione, come radice. Per approfondire, si legga Installare Symfony2. Guardando il contenuto di questi le, si vede come lambiente utilizzato da entrambi, sia denito in modo esplicito:
1 2 3 4 5 6 7 8 9

<?php require_once __DIR__./../app/bootstrap_cache.php; require_once __DIR__./../app/AppCache.php; use Symfony\Component\HttpFoundation\Request; $kernel = new AppCache(new AppKernel(prod, false)); $kernel->handle(Request::createFromGlobals())->send();

Si pu vedere come la chiave prod specica che lambiente di esecuzione sar lambiente prod. Unapplicazione Symfony2 pu essere esguita in qualsiasi ambiente utilizzando lo stesso codice, cambiando la sola stringa relativa allambiente. Nota: Lambiente test utilizzato quando si scrivono i test funzionali e non perci accessibile direttamente dal browser tramite un front controller. In altre parole, diversamente dagli altri ambienti, non c alcun le, per il front controller, del tipo app_test.php.

3.1. Ricettario

343

Documentazione Symfony, Release 2.0

Modalit debug Importante, ma non collegato allargomento ambienti, il valore false in riga 8 del precedente front controller. Questo valore specica se lapplicazione dovr essere eseguit in modalit debug o meno. Indipendentemente dallambiente, unapplicazione Symfony2 pu essere eseguita con la modalit debug congurata a true o a false. Questo modifica diversi aspetti dellapplicazione, come il fatto che gli errori vengano mostrati o se la cache debba essere ricreata dinamicamente a ogni richiesta. Sebbene non sia obbligatorio, la modalit debug sempre configurata a true negli ambienti dev e test e a false per lambiente prod. Internamente il valore della modalit debug diventa il parametro kernel.debug utilizzato allinterno del contenitore di servizi. Dando uno sguardo al le di congurazione dellapplicazione, si vede come il parametro venga utilizzato, ad esempio, per avviare o interrompere il logging quando si utilizza il DBAL di Doctrine: YAML
doctrine: dbal: logging: # ...

%kernel.debug%

XML
<doctrine:dbal logging="%kernel.debug%" ... />

PHP
$container->loadFromExtension(doctrine, array( dbal => array( logging => %kernel.debug%, // ... ), // ... ));

Creare un nuovo ambiente Unapplicazione Symfony2 viene generata con tre ambienti precongurati per gestire la maggior parte dei casi. Ovviamente, visto che un ambiente non nientaltro che una stringa che corrisponde ad un insieme di congurazioni, creare un nuovo ambiente abbastanza semplice. Supponiamo, per esempio, di voler misurare le prestazioni dellapplicazione prima del suo invio in produzione. Un modo quello di usare una congurazione simile a quella del rilascio ma che utilizzasse il web_profiler di Symfony2. Queso permetterebbe a Symfony2 di registrare le informazioni dellapplicazione mentre se ne misura le prestazioni. Il modo migliore per ottenere tutto ci tramite un ambiente che si chiami, per esempio, benchmark. Si parte creando un nuovo le di congurazione: YAML
# app/config/config_benchmark.yml imports: - { resource: config_prod.yml } framework: profiler: { only_exceptions: false }

344

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

XML
<!-- app/config/config_benchmark.xml --> <imports> <import resource="config_prod.xml" /> </imports> <framework:config> <framework:profiler only-exceptions="false" /> </framework:config>

PHP
// app/config/config_benchmark.php $loader->import(config_prod.php) $container->loadFromExtension(framework, array( profiler => array(only-exceptions => false), ));

Con queste poche e semplici modiche, lapplicazione supporta un nuovo ambiente chiamato benchmark. Questa nuova congurazione importa la congurazione dellambiente prod e la modica. Cos si garantice che lambiente sia identico a quello prod eccetto per le modiche espressamente inserite in congurazione. Siccome sar necessario che lambiente sia accessibile tramite browser, sar necessario creare un apposito front controller. Baster copiare il le web/app.php nel le web/app_benchmark.php e modicare lambiente in modo che punti a benchmark:
<?php require_once __DIR__./../app/bootstrap.php; require_once __DIR__./../app/AppKernel.php; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel(benchmark, false); $kernel->handle(Request::createFromGlobals())->send();

Il nuovo ambiente sar accessibile tramite:


http://localhost/app_benchmark.php

Nota: Alcuni ambienti, come il dev, non dovrebbero mai essere accessibile su di un server pubblico di produzione. Questo perch alcuni ambienti, per facilitarne il debug, potrebbero fornire troppe informazioni relative allinfrastruttura sottostante lapplicazione. Per essere sicuri che questi ambienti non siano accessibili, il front controller solitamente protetto dallaccesso da parte di indirizzi IP esterni tramite il seguente codice, posto in cima al controllore:

if (!in_array(@$_SERVER[REMOTE_ADDR], array(127.0.0.1, ::1))) { die(You are not allowed to access this file. Check .basename(__FILE__). for more informat }

Gli ambienti e la cartella della cache Symfony2 sfrutta la cache in diversi modi: la congurazione dellapplicazione, la congurazione delle rotte, i template di Twig vengono tutti immagazzinati in oggetti PHP e salvati su le nella cartella della cache. 3.1. Ricettario 345

Documentazione Symfony, Release 2.0

Normalmente questi le sono conservati principalmente nella cartella app/cache. Comunque ogni ambiente usa il suo proprio insieme di le della cache:
app/cache/dev app/cache/prod - cartella per la cache dellambiente *dev* - cartella per la cache dellambiente *prod*

Alcune volte, durante il debug, pu essere utile poter controllare i le salvati in cache, per capire come le cose stiano funzionando. In questi casi bisogna ricordarsi di guardare nella cartella dellambiente che si sta utilizzando (solitamente, in fase di sviluppo e debug, il dev). Sebbene possa variare, il contenuto della cartella app/cache/dev includer i seguenti le: appDevDebugProjectContainer.php - il contenitore di servizi salvato in cache che rappresenta la congurazione dellapplicazione; appdevUrlGenerator.php - la classe PHP generata a partire dalla congurazione delle rotte e usata nella generazione degli URL; appdevUrlMatcher.php - la classe PHP utilizzata per ricercare le rotte: qui possibile vedere le espressioni regolari utilizzate per associare gli URL in ingresso con le rotte disponibili; twig/ - questa cartella contiene la cache dei template di Twig. Approfondimenti Si legga larticolo Congurare parametri esterni nel contenitore dei servizi.

3.1.25 Congurare parametri esterni nel contenitore dei servizi


Nel capitolo Come padroneggiare e creare nuovi ambienti, si visto come gestire la congurazione dellapplicazione. Alle volte potrebbe essere utile, per lapplicazione, salvare alcune credenziali al di fuori del codice del progetto. Ad esempio la congurazione dellaccesso alla base dati. La essibilit del contenitore dei servizi di symfony permette di farlo in modo agevole. Variabili dambiente Symfony recupera qualsiasi variabile dambiente, il cui presso sia SYMFONY__ e la usa come un parametro allinterno del contenitore dei servizi. Il doppio trattino basso viene sostituito da un punto, dato che il punto non un carattere valido per i nomi delle variabili dambiente. Ad esempio, se si usa lambiente Apache, le variabili dambiente possono essere congurate utilizzando la seguente congurazione del VirtualHost:
<VirtualHost *:80> ServerName DocumentRoot DirectoryIndex SetEnv SetEnv Symfony2 "/percorso/applicazione/symfony_2/web" index.php index.html SYMFONY__UTENTE__DATABASE utente SYMFONY__PASSWORD__DATABASE segreto

<Directory "/percorso/applicazione/symfony_2/web"> AllowOverride All Allow from All </Directory> </VirtualHost>

346

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Nota: Il precedente esempio relativo alla congurazione di Apache e utilizza la direttiva SetEnv (http://httpd.apache.org/docs/current/env.html). Comunque, lo stesso concetto si applica a qualsiasi server web che supporti la congurazione delle variabili dambiente. Inoltre, per far si che possa funzionare anche per la riga di comando (che non utilizza Apache), sar necessario esportare i parametri come variabili di shell. Su di un sistema Unix, lo si pu fare con il seguente comando:
export SYMFONY__UTENTE__DATABASE=utente export SYMFONY__PASSWORD__DATABASE=segreto

Una volta dichiarate, le variabili saranno disponibili allinterno della variabile globale $_SERVER di PHP. Symfony si occuper di trasformare tutte le variabili di $_SERVER, con presso SYMFONY__, in parametri per il contenitore dei servizi. A questo punto, sar possibile richiamare questi parametri ovunque sia necessario. YAML
doctrine: dbal: driver dbname: user: password:

pdo_mysql symfony2_project %utente.database% %password.database%

XML

<!-- xmlns:doctrine="http://symfony.com/schema/dic/doctrine" --> <!-- xsi:schemaLocation="http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/do <doctrine:config> <doctrine:dbal driver="pdo_mysql" dbname="progetto_symfony2" user="%utente.database%" password="%password.database%" /> </doctrine:config>

PHP
$container->loadFromExtension(doctrine, array(dbal => array( driver => pdo_mysql, dbname => progetto_symfony2, user => %utente.database%, password => %password.database%, ));

Costanti Il contenitore permette di usare anche le costanti PHP come parametri. Per poter usare questa funzionalit, si dovr associare la costante alla chiave del parametro e denirne il tipo come constant.
<?xml version="1.0" encoding="UTF-8"?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3.1. Ricettario

347

Documentazione Symfony, Release 2.0

>

<parameters> <parameter key="valore.costante.globale" type="constant">COSTANTE_GLOBALE</parameter> <parameter key="mia_classe.valore.constante" type="constant">Mia_Classe::NOME_COSTANTE</ </parameters> </container>

Nota: Per funzionare necessario che la congurazione usi lXML. Se non si sta usando lXML, per sfruttare questa funzionalit, basta importarne uno:
// app/config/config.yml imports: - { resource: parametri.xml }

Congurazioni varie La direttiva import pu essere usata per importare parametri conservati in qualsiasi parte. Importare un le PHP permette di avere la essibilit di aggiungere qualsiasi cosa sia necessaria al contenitore. Il seguente esempio importa un le di nome parametri.php. YAML
# app/config/config.yml imports: - { resource: parametri.php }

XML
<!-- app/config/config.xml --> <imports> <import resource="parametri.php" /> </imports>

PHP
// app/config/config.php $loader->import(parametri.php);

Nota: Un le di risorse pu essere espresso in diversi formati. PHP, XML, YAML, INI e risorse di closure, sono tutti supportati dalla direttiva imports. parametri.php conterr i parametri che si vuole che il contenitore dei servizi conguri. Questo specialmente utile nel caso si voglia importare una congurazione con formato non standard. Il seguente esempio importa la congurazione di una base dati per Drupal in un contenitore di servizi symfony.
// app/config/parameters.php include_once(/percorso/al/sito/drupal/default/settings.php); $container->setParameter(url.database.drupal, $db_url);

348

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

3.1.26 Usare il factory per creare servizi


Il contenitore di servizi di Symfony2 mette a disposizione potenti strumenti per la creazione di oggetti, permettendo di specicare sia i parametri da passare al costruttore, sia i metodi di chiamata, che i parametri di congurazione. Alle volte, per, questo non sufciente a soddisfare tutti i requisiti per la creazione dei propri oggetti. In questi casi, possibile usare un factory per la creazione di oggetti e fare in modo che il contenitore di servizi chiami uno specico metodo nel factory, invece che inizializzare direttamente loggetto. Supponiamo di avere un factory che congura e restituisce un oggetto GestoreNewsletter:
namespace Acme\HelloBundle\Newsletter; class NewsletterFactory { public function get() { $gestoreNewsletter = new GestoreNewsletter(); // ... return $gestoreNewsletter; } }

Per rendere disponibile, in forma di servizio, loggetto GestoreNewsletter, possibile congurare un contenitore di servizi in modo che usi la classe factory NewsletterFactory: YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... gestore_newsletter.class: Acme\HelloBundle\Newsletter\GestoreNewsletter newsletter_factory.class: Acme\HelloBundle\Newsletter\NewsletterFactory services: gestore_newsletter: class: %gestore_newsletter.class% factory_class: %newsletter_factory.class% factory_method: get

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="gestore_newsletter.class">Acme\HelloBundle\Newsletter\GestoreNewsletter</par <parameter key="newsletter_factory.class">Acme\HelloBundle\Newsletter\NewsletterFactory</par </parameters> <services> <service id="gestore_newsletter" class="%gestore_newsletter.class%" factory-class="%newsletter_factory.class%" factory-method="get" /> </services>

PHP

3.1. Ricettario

349

Documentazione Symfony, Release 2.0

// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition;

// ... $container->setParameter(gestore_newsletter.class, Acme\HelloBundle\Newsletter\GestoreNewslet $container->setParameter(newsletter_factory.class, Acme\HelloBundle\Newsletter\NewsletterFact $container->setDefinition(gestore_newsletter, new Definition( %gestore_newsletter.class% ))->setFactoryClass( %newsletter_factory.class% )->setFactoryMethod( get );

Quando si specica la classe da utilizzare come factory (tramite factory_class) il metodo verr chiamato staticamente. Se il factory stesso dovesse essere istanziato e il relativo metodo delloggetto sia chiamato (come nellesempio), si dovr congurare il factory come servizio: YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... gestore_newsletter.class: Acme\HelloBundle\Newsletter\GestoreNewsletter newsletter_factory.class: Acme\HelloBundle\Newsletter\NewsletterFactory services: newsletter_factory: class: %newsletter_factory.class% gestore_newsletter: class: %gestore_newsletter.class% factory_service: newsletter_factory factory_method: get

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="gestore_newsletter.class">Acme\HelloBundle\Newsletter\GestoreNewsletter</par <parameter key="newsletter_factory.class">Acme\HelloBundle\Newsletter\NewsletterFactory</par </parameters> <services> <service id="newsletter_factory" class="%newsletter_factory.class%"/> <service id="gestore_newsletter" class="%gestore_newsletter.class%" factory-service="newsletter_factory" factory-method="get" /> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition;

// ... $container->setParameter(gestore_newsletter.class, Acme\HelloBundle\Newsletter\GestoreNewslet

350

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

$container->setParameter(newsletter_factory.class, Acme\HelloBundle\Newsletter\NewsletterFact $container->setDefinition(newsletter_factory, new Definition( %newsletter_factory.class% )) $container->setDefinition(gestore_newsletter, new Definition( %gestore_newsletter.class% ))->setFactoryService( newsletter_factory )->setFactoryMethod( get );

Nota: Il servizio del factory viene specicato tramite il suo nome id e non come un riferimento al servizio stesso. Perci non necessario usare la sintassi con @.

Passaggio di argomenti al metodo del factory Per poter passare argomenti al metodo del factory, si pu utilizzare lopzione arguments allinterno del contenitore di servizi. Si supponga, ad esempio, che il metodo get, del precedente esempio, accetti il servizio templating come argomento: YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... gestore_newsletter.class: Acme\HelloBundle\Newsletter\GestoreNewsletter newsletter_factory.class: Acme\HelloBundle\Newsletter\NewsletterFactory services: newsletter_factory: class: %newsletter_factory.class% gestore_newsletter: class: %gestore_newsletter.class% factory_service: newsletter_factory factory_method: get arguments: @templating

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="gestore_newsletter.class">Acme\HelloBundle\Newsletter\GestoreNewsletter</par <parameter key="newsletter_factory.class">Acme\HelloBundle\Newsletter\NewsletterFactory</par </parameters> <services> <service id="newsletter_factory" class="%newsletter_factory.class%"/> <service id="gestore_newsletter" class="%gestore_newsletter.class%" factory-service="newsletter_factory" factory-method="get" > <argument type="service" id="templating" />

3.1. Ricettario

351

Documentazione Symfony, Release 2.0

</service> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition;

// ... $container->setParameter(gestore_newsletter.class, Acme\HelloBundle\Newsletter\GestoreNewslet $container->setParameter(newsletter_factory.class, Acme\HelloBundle\Newsletter\NewsletterFact $container->setDefinition(newsletter_factory, new Definition( %newsletter_factory.class% )) $container->setDefinition(gestore_newsletter, new Definition( %gestore_newsletter.class%, array(new Reference(templating)) ))->setFactoryService( newsletter_factory )->setFactoryMethod( get );

3.1.27 Gestire le dipendenza comuni con i servizi padre


Aggiungendo funzionalit alla propria applicazione, si pu arrivare ad un punto in cui classi tra loro collegate condividano alcune dipendenze. Si potrebbe avere, ad esempio, un Gestore Newsletter che usa una setter injection per congurare le proprie dipendenze:
namespace Acme\HelloBundle\Mail; use Acme\HelloBundle\Mailer; use Acme\HelloBundle\FormattatoreMail; class GestoreNewsletter { protected $mailer; protected $formattatoreMail; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; } public function setFormattatoreMail(FormattatoreMail $formattatoreMail) { $this->formattatoreMail = $formattatoreMail; } // ... }

ed una classe BigliettoAuguri che condivide le stesse dipendenze:


namespace Acme\HelloBundle\Mail; use Acme\HelloBundle\Mailer;

352

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

use Acme\HelloBundle\FormattatoreMail; class GestoreBigliettoAuguri { protected $mailer; protected $formattatoreMail; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; } public function setFormattatoreMail(FormattatoreMail $formattatoreMail) { $this->formattatoreMail = $formattatoreMail; } // ... }

La congurazione del servizio per queste classi sar simile alla seguente: YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... gestore_newsletter.class: Acme\HelloBundle\Mail\GestoreNewsletter gestore_biglietto_auguri.class: Acme\HelloBundle\Mail\GestoreBigliettoAuguri services: mio_mailer: # ... mio_formattatore_mail: # ... gestore_newsletter: class: %gestore_newsletter.class% calls: - [ setMailer, [ @mio_mailer ] ] - [ setFormattatoreMail, [ @mio_formattatore_mail] ] gestore_biglietto_auguri: class: %gestore_biglietto_auguri.class% calls: - [ setMailer, [ @mio_mailer ] ] - [ setFormattatoreMail, [ @mio_formattatore_mail] ]

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="gestore_newsletter.class">Acme\HelloBundle\Mail\GestoreNewsletter</parameter <parameter key="gestore_biglietto_auguri.class">Acme\HelloBundle\Mail\GestoreBigliettoAuguri </parameters> <services> <service id="mio_mailer" ... > <!-- ... --> </service> <service id="mio_formattatore_mail" ... >

3.1. Ricettario

353

Documentazione Symfony, Release 2.0

<!-- ... --> </service> <service id="gestore_newsletter" class="%gestore_newsletter.class%"> <call method="setMailer"> <argument type="service" id="mio_mailer" /> </call> <call method="setFormattatoreMail"> <argument type="service" id="mio_formattatore_mail" /> </call> </service> <service id="gestore_biglietto_auguri" class="%gestore_biglietto_auguri.class%"> <call method="setMailer"> <argument type="service" id="mio_mailer" /> </call> <call method="setFormattatoreMail"> <argument type="service" id="mio_formattatore_mail" /> </call> </service> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference;

// ... $container->setParameter(gestore_newsletter.class, Acme\HelloBundle\Mail\GestoreNewsletter); $container->setParameter(gestore_biglietto_auguri.class, Acme\HelloBundle\Mail\GestoreBigliet $container->setDefinition(mio_mailer, ... ); $container->setDefinition(mio_formattatore_mail, ... ); $container->setDefinition(gestore_newsletter, new Definition( %gestore_newsletter.class% ))->addMethodCall(setMailer, array( new Reference(mio_mailer) ))->addMethodCall(setFormattatoreMail, array( new Reference(mio_formattatore_mail) )); $container->setDefinition(gestore_biglietto_auguri, new Definition( %gestore_biglietto_auguri.class% ))->addMethodCall(setMailer, array( new Reference(mio_mailer) ))->addMethodCall(setFormattatoreMail, array( new Reference(mio_formattatore_mail) ));

Ci sono molte ripetizioni, sia nelle classi che nella congurazione. Questo vuol dire che se qualcosa viene cambiato, ad esempio le classi Mailer o FormattatoreMail che dovranno essere iniettate tramite il costruttore, sar necessario modicare la congurazione in due posti. Allo stesso modo, se si volesse modicare il metodo setter, sarebbe necessario modicare entrambe le classi. Il tipico modo di gestire i metodi comuni di queste classi sarebbe quello di far si che estendano una super classe comune:
namespace Acme\HelloBundle\Mail; use Acme\HelloBundle\Mailer; use Acme\HelloBundle\FormattatoreMail; abstract class GestoreMail

354

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

{ protected $mailer; protected $formattatoreMail; public function setMailer(Mailer $mailer) { $this->mailer = $mailer; } public function setFormattatoreMail(EmailFormatter $formattatoreMail) { $this->formattatoreMail = $formattatoreMail; } // ... }

Le classi GestoreNewsletter e GestoreBigliettoAuguri potranno estendere questa super classe:


namespace Acme\HelloBundle\Mail; class GestoreNewsletter extends GestoreMail { // ... }

e:
namespace Acme\HelloBundle\Mail; class GestoreBigliettoAuguri extends GestoreMail { // ... }

Allo stesso modo, il contenitore di servizi di Symfony2 supporta la possibilit di estendere i servizi nella congurazione in modo da poter ridurre le ripetizioni specicando un servizio padre. YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... gestore_newsletter.class: Acme\HelloBundle\Mail\GestoreNewsletter gestore_biglietto_auguri.class: Acme\HelloBundle\Mail\GestoreBigliettoAuguri gestore_mail.class: Acme\HelloBundle\Mail\GestoreMail services: mio_mailer: # ... mio_formattatore_mail: # ... gestore_mail: class: %gestore_mail.class% abstract: true calls: - [ setMailer, [ @mio_mailer ] ] - [ setFormattatoreMail, [ @mio_formattatore_mail] ] gestore_newsletter: class: %gestore_newsletter.class% parent: gestore_mail

3.1. Ricettario

355

Documentazione Symfony, Release 2.0

gestore_biglietto_auguri: class: %gestore_biglietto_auguri.class% parent: gestore_mail

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="gestore_newsletter.class">Acme\HelloBundle\Mail\GestoreNewsletter</parameter <parameter key="gestore_biglietto_auguri.class">Acme\HelloBundle\Mail\GestoreBigliettoAuguri <parameter key="gestore_mail.class">Acme\HelloBundle\Mail\GestoreMail</parameter> </parameters>

<services> <service id="mio_mailer" ... > <!-- ... --> </service> <service id="mio_formattatore_mail" ... > <!-- ... --> </service> <service id="gestore_mail" class="%gestore_mail.class%" abstract="true"> <call method="setMailer"> <argument type="service" id="mio_mailer" /> </call> <call method="setFormattatoreMail"> <argument type="service" id="mio_formattatore_mail" /> </call> </service> <service id="gestore_newsletter" class="%gestore_newsletter.class%" parent="gestore_mail"/> <service id="gestore_biglietto_auguri" class="%gestore_biglietto_auguri.class%" parent="gest </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference;

// ... $container->setParameter(gestore_newsletter.class, Acme\HelloBundle\Mail\GestoreNewsletter); $container->setParameter(gestore_biglietto_auguri.class, Acme\HelloBundle\Mail\GestoreBigliet $container->setParameter(gestore_mail.class, Acme\HelloBundle\Mail\GestoreMail); $container->setDefinition(mio_mailer, ... ); $container->setDefinition(mio_formattatore_mail, ... ); $container->setDefinition(gestore_mail, new Definition( %gestore_mail.class% ))->SetAbstract( true )->addMethodCall(setMailer, array( new Reference(mio_mailer) ))->addMethodCall(setFormattatoreMail, array( new Reference(mio_formattatore_mail) )); $container->setDefinition(gestore_newsletter, new DefinitionDecorator( gestore_mail ))->setClass(

356

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

%gestore_newsletter.class% ); $container->setDefinition(gestore_biglietto_auguri, new DefinitionDecorator( gestore_mail ))->setClass( %gestore_biglietto_auguri.class% );

In questo contesto, avere un servizio padre implica che gli argomenti e le chiamate dei metodi del servizio padre dovrebbero essere utilizzati per i servizi gli. Nello specico, i metodi setter deniti nel servizio padre verranno chiamati quando i servizi gli saranno istanziati. Nota: Rimuovendo la chiave di congurazione parent i servizi verranno comunque istanziati e estenderanno comunque la classe GestoreMail. La differenza che, omettendo la chiave di congurazione parent, le chiamate denite nel servizio gestore_mail non saranno eseguite quando i servizi gli saranno istanziati. La classe padre astratta e dovrebbe essere istanziata direttamente. Congurarla come astratta nel le di congurazione, cos come stato fatto precedentemente, implica che potr essere usata come servizio padre e che non potr essere utilizzata direttamente come servizio da iniettare e che verr rimossa in fase di compilazione. In altre parole, esister semplicemente come un template che altri servizi potranno usare. Override delle dipendenze della classe padre Potrebbe succedere che sia preferibile fare loverride della classe passata come dipendenza di un servizio glio. Fortunatamente, aggiungendo la congurazione della chiamata al metodo per il servizio glio, le dipendenze congurate nella classe padre verranno sostituite. Perci, nel caso si volesse passare una dipendenza diversa solo per la classe GestoreNewsletter, la congurazione sar simile alla seguente: YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... gestore_newsletter.class: Acme\HelloBundle\Mail\GestoreNewsletter gestore_biglietto_auguri.class: Acme\HelloBundle\Mail\GestoreBigliettoAuguri gestore_mail.class: Acme\HelloBundle\Mail\GestoreMail services: mio_mailer: # ... mio_mailer_alternativo: # ... mio_formattatore_mail: # ... gestore_mail: class: %gestore_mail.class% abstract: true calls: - [ setMailer, [ @mio_mailer ] ] - [ setFormattatoreMail, [ @mio_formattatore_mail] ] gestore_newsletter: class: %gestore_newsletter.class% parent: gestore_mail calls: - [ setMailer, [ @mio_mailer_alternativo ] ]

3.1. Ricettario

357

Documentazione Symfony, Release 2.0

gestore_biglietto_auguri: class: %gestore_biglietto_auguri.class% parent: gestore_mail

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="gestore_newsletter.class">Acme\HelloBundle\Mail\GestoreNewsletter</parameter <parameter key="gestore_biglietto_auguri.class">Acme\HelloBundle\Mail\GestoreBigliettoAuguri <parameter key="gestore_mail.class">Acme\HelloBundle\Mail\GestoreMail</parameter> </parameters>

<services> <service id="mio_mailer" ... > <!-- ... --> </service> <service id="mio_mailer_alternativo" ... > <!-- ... --> </service> <service id="mio_formattatore_mail" ... > <!-- ... --> </service> <service id="gestore_mail" class="%gestore_mail.class%" abstract="true"> <call method="setMailer"> <argument type="service" id="mio_mailer" /> </call> <call method="setFormattatoreMail"> <argument type="service" id="mio_formattatore_mail" /> </call> </service> <service id="gestore_newsletter" class="%gestore_newsletter.class%" parent="gestore_mail"> <call method="setMailer"> <argument type="service" id="mio_mailer_alternativo" /> </call> </service> <service id="gestore_biglietto_auguri" class="%gestore_biglietto_auguri.class%" parent="gest </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference;

// ... $container->setParameter(gestore_newsletter.class, Acme\HelloBundle\Mail\GestoreNewsletter); $container->setParameter(gestore_biglietto_auguri.class, Acme\HelloBundle\Mail\GestoreBigliet $container->setParameter(gestore_mail.class, Acme\HelloBundle\Mail\GestoreMail); $container->setDefinition(mio_mailer, ... ); $container->setDefinition(mio_mailer_alternativo, ... ); $container->setDefinition(mio_formattatore_mail, ... ); $container->setDefinition(gestore_mail, new Definition( %gestore_mail.class% ))->SetAbstract( true )->addMethodCall(setMailer, array(

358

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

new Reference(mio_mailer) ))->addMethodCall(setFormattatoreMail, array( new Reference(mio_formattatore_mail) )); $container->setDefinition(gestore_newsletter, new DefinitionDecorator( gestore_mail ))->setClass( %gestore_newsletter.class% )->addMethodCall(setMailer, array( new Reference(mio_mailer_alternativo) )); $container->setDefinition(gestore_newsletter, new DefinitionDecorator( gestore_mail ))->setClass( %gestore_biglietto_auguri.class% );

Il GestoreBigliettoAuguri ricever le stesse dipendenze di prima mentre al GestoreNewsletter verr passato il mio_mailer_alternativo invece del servizio mio_mailer. Collezioni di dipendenze da notare che il metodo setter di cui si fatto loverride nel precedente esempio viene chiamato due volte: una volta nella denizione del padre e una nella denizione del glio. Nel precedente esempio la cosa va bene, visto che la chiamata al secondo setMailer sostituisce loggetto mailer congurato nella prima chiamata. In alcuni casi, per, questo potrebbe creare problemi. Ad esempio, nel caso in cui il metodo per cui si fa loverride dovesse aggiungere qualcosa ad una collezione, si potrebbero aggiungere due oggetti alla collezione. Di seguito se ne pu vedere un esempio:
namespace Acme\HelloBundle\Mail; use Acme\HelloBundle\Mailer; use Acme\HelloBundle\FormattatoreMail; abstract class GestoreMail { protected $filtri; public function setFiltro($filtro) { $this->filtri[] = $filtro; } // ... }

Ipotizziamo di avere la seguente congurazione: YAML


# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... gestore_newsletter.class: Acme\HelloBundle\Mail\GestoreNewsletter gestore_mail.class: Acme\HelloBundle\Mail\GestoreMail services: mio_filtro: # ...

3.1. Ricettario

359

Documentazione Symfony, Release 2.0

altro_filtro: # ... gestore_mail: class: %gestore_mail.class% abstract: true calls: - [ setFiltro, [ @mio_filtro ] ] gestore_newsletter: class: %gestore_newsletter.class% parent: gestore_mail calls: - [ setFiltro, [ @altro_filtro ] ]

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="gestore_newsletter.class">Acme\HelloBundle\Mail\GestoreNewsletter</parameter <parameter key="gestore_mail.class">Acme\HelloBundle\Mail\GestoreMail</parameter> </parameters> <services> <service id="mio_filtro" ... > <!-- ... --> </service> <service id="altro_filtro" ... > <!-- ... --> </service> <service id="gestore_mail" class="%gestore_mail.class%" abstract="true"> <call method="setFiltro"> <argument type="service" id="mio_filtro" /> </call> </service> <service id="gestore_newsletter" class="%gestore_newsletter.class%" parent="gestore_mail"> <call method="setFiltro"> <argument type="service" id="altro_filtro" /> </call> </service> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference;

// ... $container->setParameter(gestore_newsletter.class, Acme\HelloBundle\Mail\GestoreNewsletter); $container->setParameter(gestore_mail.class, Acme\HelloBundle\Mail\GestoreMail); $container->setDefinition(mio_filtro, ... ); $container->setDefinition(altro_filtro, ... ); $container->setDefinition(gestore_mail, new Definition( %gestore_mail.class% ))->SetAbstract( true )->addMethodCall(setFiltro, array(

360

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

new Reference(mio_filtro) )); $container->setDefinition(gestore_newsletter, new DefinitionDecorator( gestore_mail ))->setClass( %gestore_newsletter.class% )->addMethodCall(setFiltro, array( new Reference(altro_filtro) ));

In questo caso il metodo setFiltro del servizio gestore_newsletter verrebbe chiamato due volte cosa che produrr, come risultato che larray $filtri conterr sia loggetto mio_filtro che loggetto altro_filtro. Il che va bene se lobbiettivo quello di avere pi ltri nella sotto classe. Ma se si volesse sostituire il ltro passato alla sotto classe, la rimozione della congurazione della classe padre eviter che la classe base chiami il metodo setFiltro.

3.1.28 Come lavorare con gli scope


Questa ricetta parla di scope, un argomento alquanto avanzato, relativo al Contenitore di servizi. Se si ottiene un errore che menziona gli scopes durante la creazione di servizi oppure se si ha lesigenza di creare un servizio che dipenda dal servizio request, questa la ricetta giusta. Capure gli scope Lo scope di un servizio controlla quanto a lungo unistanza di un servizio usata dal contenitore. Il componente Dependency Injection fornisce due scope generici: container (quello predenito): la stessa istanza usata ogni volta che la si richiede da questo contenitore. prototype: viene creata una nuova istanza, ogni volta che si richiede il servizio. FrameworkBundle denisce anche un terzo scope: request. Questi scope sono legati alla richiesta, il che vuol dire che viene creata una nuova istanza per ogni sotto-richiesta, non disponibile al di fuori della richiesta stessa (per esempio nella CLI). Gli scope aggiungono un vincolo sulle dipendenze di un servizio: un servizio non pu dipendere da servizi con scope pi stretti. Per esempio, se si crea un generico servizio pippo, ma si prova a iniettare il componente request, si ricever una Symfony\Component\DependencyInjection\Exception\ScopeWideningInjectionException alla compilazione del contenitore. Leggere la nota seguente sotto per maggiori dettagli.

3.1. Ricettario

361

Documentazione Symfony, Release 2.0

Scope e dipendenze Si immagini di aver congurato un servizio posta. Non stato congurato lo scope del servizio, quindi ha container. In altre parole, ogni volta che si chiede al contenitore il servizio posta, si ottiene lo stesso oggetto. Solitamente, si vuole che un servizio funzioni in questo modo. Si immagini, tuttavia, di aver bisogno del servizio request da dentro posta, magari perch si deve leggere lURL della richiesta corrente. Lo si aggiunge quindi come parametro del costruttore. Vediamo quali problemi si presentano: Alla richiesta di posta, viene creata unistanza di posta (chiamiamola PostaA), a cui viene passato il servizio request (chiamiamolo RequestA). Fin qui tutto bene. Si effettua ora una sotto-richiesta in Symfony, che un modo carino per dire che stata chiamata, per esempio, la funzione {% render ... %} di Twig, che esegue un altro controllore. Internamente, il vecchio servizio request (RequestA) viene effettivamente sostituito da una nuova istanza di richiesta (RequestB). Questo avviene in background ed del tutto normale. Nel proprio controllore incluso, si richiede nuovamente il servizio posta. Poich il servizio nello scope container scope, viene riutilizzata la stessa istanza (PostaA). Ma ecco il problema: listanza PostaA contiene ancora il vecchio oggetto RequestA, che non ora loggetto di richiesta corretto da avere (attualmente RequestB il servizio request). La differenza sottile, ma questa mancata corrispondenza potrebbe causare grossi guai, per questo non consentita. Questa dunque la ragione per cui esistono gli scope e come possono causare problemi. Vedremo pi avanti delle soluzioni comuni.

Nota: Ovviamente, un servizio pu dipendere senza alcun problema da un altro servizio che abbia uno scope pi ampio, .

Impostare lo scope nella denizione Lo scope di un servizio denito nella denizione del servizio stesso: YAML
# src/Acme/HelloBundle/Resources/config/services.yml services: greeting_card_manager: class: Acme\HelloBundle\Mail\GreetingCardManager scope: request

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <services> <service id="greeting_card_manager" class="Acme\HelloBundle\Mail\GreetingCardManager" scope= </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; $container->setDefinition( greeting_card_manager, new Definition(Acme\HelloBundle\Mail\GreetingCardManager) )->setScope(request);

362

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Se non si specica lo scope, viene usato container, che quello che si desidera la maggior parte delle volte. A meno che il proprio servizio non dipenda da un altro servizio con uno scope pi stretto (solitamente, il servizio request), probabilmente non si avr bisogno di impostare lo scope. Usare un servizio da uno scope pi stretto Se il proprio servizio dipende da un servizio con scope, la soluzione migliore metterlo nello stesso scope (o in uno pi stretto). Di solito, questo vuol dire mettere il proprio servizio nello scope request. Ma questo non sempre possibile (per esempio, unestensione di Twig deve stare nello scope container, perch lambiente di Twig ne ha bisogno per le sue dipendenze). In questi casi, si dovrebbe passare lintero contenitore dentro il proprio servizio e recuperare le proprie dipendenze dal contenitore ogni volta che servono, per assicurarsi di avere listanza giusta:
namespace Acme\HelloBundle\Mail; use Symfony\Component\DependencyInjection\ContainerInterface; class Mailer { protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function sendEmail() { $request = $this->container->get(request); // Fare qualcosa con la richiesta in questo punto } }

Attenzione: Si faccia attenzione a non memorizzare la richiesta in una propriet delloggetto per una chiamata futura del servizio, perch causerebbe lo stesso problema spiegato nella prima sezione (tranne per il fatto che Symfony non in grado di individuare lerrore). La congurazione del servizio per questa classe assomiglia a questa: YAML
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... posta.class: Acme\HelloBundle\Mail\Mailer services: posta: class: %posta.class% arguments: - "@service_container" # scope: container pu essere omesso, perch il predefinito

XML
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... -->

3.1. Ricettario

363

Documentazione Symfony, Release 2.0

<parameter key="posta.class">Acme\HelloBundle\Mail\Mailer</parameter> </parameters> <services> <service id="posta" class="%posta.class%"> <argument type="service" id="service_container" /> </service> </services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; // ... $container->setParameter(posta.class, Acme\HelloBundle\Mail\Mailer); $container->setDefinition(posta, new Definition( %posta.class%, array(new Reference(service_container)) ));

Nota: Iniettare lintero contenitore in un servizio di solito non una buona idea (iniettare solo ci che serve). In alcuni rari casi, necessario quando si ha un servizio nello scope container che necessita di un servizio nello scope request. Se si denisce un controllore come servizio, si pu ottenere loggetto Request senza iniettare il contenitore, facendoselo passare come parametro nel metodo dellazione. Vedere La Request come parametro del controllore per maggiori dettagli.

3.1.29 Come far s che i servizi usino le etichette


Molti dei servizi centrali di Symfony2 dipendono da etichette per capire quali servizi dovrebbero essere caricati, ricevere notiche di eventi o per essere maneggiati in determinati modi. Ad esempio, Twig usa letichetta twig.extension per caricare ulteriori estensioni. Ma possibile usare etichette anche nei propri bundle. Ad esempio nel caso in cui uno dei propri servizi gestisca una collezione di un qualche genere o implementi una lista nella quale diverse strategie alternative vengono provate no a che una non risulti efcace. In questo articolo si user come esempio una lista di trasporto che una collezione di classi che implementano \Swift_Transport. Usando questa lista il mailer di Swift prover diversi tipi di trasporto no a che uno non abbia successo. Questo articolo si focalizza fondamentalmente sullargomento delliniezione di dipendenze. Per iniziare si denisce la classe della ListaDiTrasporto:
namespace Acme\MailerBundle; class ListaDiTrasporto { private $trasporti; public function __construct() { $this->trasporti = array(); }

364

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

public function aggiungiTrasporto(\Swift_Transport { $this->trasporti[] = $trasporto; } }

$trasporto)

Quindi si denisce la lista come servizio: YAML


# src/Acme/MailerBundle/Resources/config/services.yml parameters: acme_mailer.lista_trasporto.class: Acme\MailerBundle\ListaDiTrasporto services: acme_mailer.lista_trasporto: class: %acme_mailer.lista_trasporto.class%

XML
<!-- src/Acme/MailerBundle/Resources/config/services.xml -->

<parameters> <parameter key="acme_mailer.lista_trasporto.class">Acme\MailerBundle\ListaDiTrasporto</param </parameters> <services> <service id="acme_mailer.lista_trasporto" class="%acme_mailer.lista_trasporto.class%" /> </services>

PHP
// src/Acme/MailerBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition;

$container->setParameter(acme_mailer.lista_trasporto.class, Acme\MailerBundle\ListaDiTrasport

$container->setDefinition(acme_mailer.lista_trasporto, new Definition(%acme_mailer.lista_tras

Denire un servizio con etichette personalizzate A questo punto si vuole che diverse classi di \Swift_Transport vengano istanziate e automaticamente aggiunte alla lista, usando il metodo aggiungiTrasporto. Come esempio si possono aggiungere i seguenti trasporti come servizi: YAML
# src/Acme/MailerBundle/Resources/config/services.yml services: acme_mailer.transport.smtp: class: \Swift_SmtpTransport arguments: - %mailer_host% tags: - { name: acme_mailer.transport } acme_mailer.transport.sendmail: class: \Swift_SendmailTransport

3.1. Ricettario

365

Documentazione Symfony, Release 2.0

tags: -

{ name: acme_mailer.transport }

XML
<!-- src/Acme/MailerBundle/Resources/config/services.xml --> <service id="acme_mailer.transport.smtp" class="\Swift_SmtpTransport"> <argument>%mailer_host%</argument> <tag name="acme_mailer.transport" /> </service> <service id="acme_mailer.transport.sendmail" class="\Swift_SendmailTransport"> <tag name="acme_mailer.transport" /> </service>

PHP
// src/Acme/MailerBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; $definitionSmtp = new Definition(\Swift_SmtpTransport, array(%mailer_host%)); $definitionSmtp->addTag(acme_mailer.transport); $container->setDefinition(acme_mailer.transport.smtp, $definitionSmtp); $definitionSendmail = new Definition(\Swift_SendmailTransport); $definitionSendmail->addTag(acme_mailer.transport); $container->setDefinition(acme_mailer.transport.sendmail, $definitionSendmail);

Si noti letichetta acme_mailer.transport. Si vuole che il bundle riconosca questi trasporti e li aggiunga, autonomamente, alla lista. Per realizzare questo meccanismo necessario denire un metodo build() nella classe AcmeMailerBundle:
namespace Acme\MailerBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; use Acme\MailerBundle\DependencyInjection\Compiler\TransportCompilerPass; class AcmeMailerBundle extends Bundle { public function build(ContainerBuilder $contenitore) { parent::build($contenitore); $contenitore->addCompilerPass(new TransportCompilerPass()); } }

Creazione del CompilerPass Si pu notare che il metodo fa riferimento alla non ancora esistente classe TransportCompilerPass. Questa classe dovr fare in modo che tutti i servizi etichettat come acme_mailer.transport vengano aggiunti alla classe ListaDiTrasporto tramite una chiamata al metodo aggiungiTrasporto(). La classe TransportCompilerPass sar simile alla seguente:

366

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

namespace Acme\MailerBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Reference; class TransportCompilerPass implements CompilerPassInterface { public function process(ContainerBuilder $contenitore) { if (false === $contenitore->hasDefinition(acme_mailer.lista_trasporto)) { return; } $definizione = $contenitore->getDefinition(acme_mailer.lista_trasporto); foreach ($contenitore->findTaggedServiceIds(acme_mailer.transport) as $id => $attributi) { $definizione->addMethodCall(aggiungiTrasporto, array(new Reference($id))); } } }

Il metodo process() controllo lesistenza di un servizio acme_mailer.lista_trasporto, quindi cerca tra tutti i servizi etichettati acme_mailer.transport. Aggiunge alla denizione del servizio acme_mailer.lista_trasporto una chiamata a aggiungiTrasporto() per ogni servizio acme_mailer.transport trovato. Il primo argomento di ognuna di queste chiamate sar lo stesso servizio di trasporto della posta. Nota: Per convenzione, in nomi di etichette sono formati dal nome del bundle(minuscolo con il trattino basso come separatore), seguito dal punto e, inne, dal nome reale: perci, letichetta transport di AcmeMailerBundle sar acme_mailer.transport.

Denizione dei servizi compilati Aggiungere il passo della compilazione avr come risultato la creazione, in automatico, delle seguenti righe di codice nella versione compilata del contenitore di servizi. Nel caso si stia lavorando nellambiente dev, si apra il le /cache/dev/appDevDebugProjectContainer.php e si cerchi il metodo getTransportChainService(). Dovrebbe essere simile al seguente:

protected function getAcmeMailer_ListaTrasportoService() { $this->services[acme_mailer.lista_trasporto] = $instance = new \Acme\MailerBundle\ListaDiTraspo $instance->aggiungiTrasporto($this->get(acme_mailer.transport.smtp)); $instance->aggiungiTrasporto($this->get(acme_mailer.transport.sendmail)); return $instance; }

3.1.30 Usare PdoSessionStorage per salvare le sessioni nella base dati


Normalmente, nella gestione delle sessioni, Symfony2 salva le relative informazioni allinterno di le. Solitamente, i siti web di dimensioni medio grandi utilizzano la basi dati, invece dei le, per salvare i dati di sessione. Questo perch le basi dati sono pi semplici da utilizzare e sono pi scalabili in ambienti multi-webserver. 3.1. Ricettario 367

Documentazione Symfony, Release 2.0

Symfony2 ha, al suo interno, una soluzione per larchiviazione delle sessioni su base dati, chiamata Symfony\Component\HttpFoundation\SessionStorage\PdoSessionStorage. Per utilizzarla sufciente cambiare alcuni parametri di config.yml (o del proprio formato di congurazione): YAML
# app/config/config.yml framework: session: # ... storage_id: session.storage.pdo parameters: pdo.db_options: db_table: db_id_col: db_data_col: db_time_col:

sessione sessione_id sessione_value sessione_time

services: pdo: class: PDO arguments: dsn: "mysql:dbname=db_sessione" user: utente_db password: password_db session.storage.pdo: class: Symfony\Component\HttpFoundation\SessionStorage\PdoSessionStorage arguments: [@pdo, %session.storage.options%, %pdo.db_options%]

XML
<!-- app/config/config.xml --> <framework:config> <framework:session storage-id="session.storage.pdo" lifetime="3600" auto-start="true"/> </framework:config> <parameters> <parameter key="pdo.db_options" type="collection"> <parameter key="db_table">sessione</parameter> <parameter key="db_id_col">sessione_id</parameter> <parameter key="db_data_col">sessione_value</parameter> <parameter key="db_time_col">sessione_time</parameter> </parameter> </parameters> <services> <service id="pdo" class="PDO"> <argument>mysql:dbname=db_sessione</argument> <argument>utente_db</argument> <argument>password_db</argument> </service>

<service id="session.storage.pdo" class="Symfony\Component\HttpFoundation\SessionStorage\Pdo <argument type="service" id="pdo" /> <argument>%session.storage.options%</argument> <argument>%pdo.db_options%</argument>

368

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

</service> </services>

PHP
// app/config/config.yml use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; $container->loadFromExtension(framework, array( // ... session => array( // ... storage_id => session.storage.pdo, ), )); $container->setParameter(pdo.db_options, array( db_table => sessione, db_id_col => sessione_id, db_data_col => sessione_value, db_time_col => sessione_time, )); $pdoDefinition = new Definition(PDO, array( mysql:dbname=db_sessione, utente_db, password_db, )); $container->setDefinition(pdo, $pdoDefinition);

$storageDefinition = new Definition(Symfony\Component\HttpFoundation\SessionStorage\PdoSessionS new Reference(pdo), %session.storage.options%, %pdo.db_options%, )); $container->setDefinition(session.storage.pdo, $storageDefinition);

db_table: Nome della tabella, nella base dati, per le sessioni db_id_col: Nome della colonna id della tabella delle sessioni (VARCHAR(255) o maggiore) db_data_col: Nome della colonna dove salvare il valore della sessione (TEXT o CLOB) db_time_col: Nome della colonna per la registrazione del tempo della sessione (INTEGER) Condividere le informazioni di connessione della base dati Grazie a questa congurazione, i parametri della connessione alla base dati sono deniti solo per larchiviazione dei dati di sessione. La qual cosa perfetta se si usa una base dati differente per i dati di sessione. Ma se si preferisce salvare i dati di sessione nella stessa base dati in cui risiedono i rimanenti dati del progetto, possibile utilizzare i parametri di connessione di parameter.ini, richiamandone la congurazione della base dati: YAML
pdo: class: PDO arguments:

3.1. Ricettario

369

Documentazione Symfony, Release 2.0

- "mysql:dbname=%database_name%" - %database_user% - %database_password%

XML
<service id="pdo" class="PDO"> <argument>mysql:dbname=%database_name%</argument> <argument>%database_user%</argument> <argument>%database_password%</argument> </service>

XML
$pdoDefinition = new Definition(PDO, array( mysql:dbname=%database_name%, %database_user%, %database_password%, ));

Esempi di dichiarazioni SQL


MySQL

La dichiarazione SQL per creare la necessaria tabella nella base dati potrebbe essere simile alla seguente (MySQL):
CREATE TABLE sessione ( sessione_id varchar(255) NOT NULL, sessione_value text NOT NULL, sessione_time int(11) NOT NULL, PRIMARY KEY (session_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

PostgreSQL

Per PostgreSQL, la dichiarazione sar simile alla seguente:


CREATE TABLE sessione ( sessione_id character varying(255) NOT NULL, sessione_value text NOT NULL, sessione_time integer NOT NULL, CONSTRAINT session_pkey PRIMARY KEY (session_id), );

3.1.31 Struttura del bundle e best practice


Un bundle una cartella con una struttura ben denita, che pu ospitare qualsiasi cosa, dalle classi ai controllori e alle risorse web. Anche se i bundle sono molto essibili, si devono seguire alcune best practice per distribuirne uno. Nome del bundle Un bundle anche uno spazio dei nomi di PHP. Lo spazio dei nomi deve seguire gli standard (http://groups.google.com/group/php-standards/web/psr-0-nal-proposal) tecnici di interoperabilit di PHP 5.3 per gli 370 Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

spazi dei nomi e i nomi delle classi: inizia con un segmento del venditore, seguito da zero o pi segmenti di categoria e nisce con il nome breve dello spazio dei nomi, che deve terminare col sufsso Bundle. Uno spazio dei nomi diventa un bundle non appena vi si aggiunge una classe Bundle. La classe Bundle deve seguire queste semplici regole: Usare solo caratteri alfanumerici e trattini bassi; Usare un nome in CamelCase; Usare un nome breve e descrittivo (non oltre 2 parole); Aggiungere un presso con il nome del venditore (e, opzionalmente, con gli spazi dei nomi della categoria); Aggiungere il sufsso Bundle. Ecco alcuni spazi dei nomi e nomi di classi Bundle validi: Spazio dei nomi Acme\Bundle\BlogBundle Acme\Bundle\Social\BlogBundle Acme\BlogBundle Nome classe Bundle AcmeBlogBundle AcmeSocialBlogBundle AcmeBlogBundle

Per convenzione, il metodo getName() della classe Bundle deve restituire il nome della classe. Nota: Se si condivide pubblicamente il bundle, si deve usare il nome della classe Bundle per il repository (AcmeBlogBundle e non BlogBundle, per esempio).

Nota: I bundle del nucleo di Symfony2 non hanno il presso Symfony e hanno sempre un sotto-spazio dei nomi Bundle; per esempio: Symfony\Bundle\FrameworkBundle\FrameworkBundle. Ogni bundle ha un alias, che la versione breve in minuscolo del nome del bundle, con trattini bassi (acme_hello per AcmeHelloBundle o acme_social_blog per Acme\Social\BlogBundle, per esempio). Questo alias usato per assicurare lunivocit di un bundle (vedere pi avanti alcuni esempi dutilizzo). Struttura della cartella La struttura di base delle cartella di un bundle HelloBundle deve essere come segue:
XXX/... HelloBundle/ HelloBundle.php Controller/ Resources/ meta/ LICENSE config/ doc/ index.rst translations/ views/ public/ Tests/

Le cartelle XXX riettono la struttura dello spazio dei nomi del bundle. I seguenti le sono obbligatori: HelloBundle.php; 3.1. Ricettario 371

Documentazione Symfony, Release 2.0

Resources/meta/LICENSE: La licenza completa del codice; Resources/doc/index.rst: Il le iniziale della documentazione del bundle. Nota: Queste convenzioni assicurano che strumenti automatici possano appoggiarsi a tale struttura predenita nel loro funzionamento. La profondit delle sotto-cartelle va mantenuta al minimo per le classi e i le pi usati (massimo 2 livelli). Ulteriori livelli possono essere deniti per le meno usati e non strategici. La cartella del bundle in sola lettura. Se occorre scrivere le temporanei, memorizzarli sotto le cartelle cache/ o log/ dellapplicazione. Degli strumenti possono generare le nella cartella del bundle, ma solo se i le generati devono far parte del repository. Le seguenti classi e i seguenti le hanno postazioni speciche: Tipo Comandi Controllori Estensioni del contenitore Ascoltatori di eventi Congurazione Risorse Web File di traduzione Template Test unitari e funzionali Classi La struttura delle cartelle di un bundle usata dalla gerarchia degli spazi dei nomi. Per esempio, un controllore HelloController posto in Bundle/HelloBundle/Controller/HelloController.php e il nome pienamente qualicato della classe Bundle\HelloBundle\Controller\HelloController. Tutte le classi e i le devono seguire gli standard di codice di Symfony2. Alcune classi vanno viste solo come facciati e devono essere pi corte possibile, come comandi, helper, ascoltatori e controllori. Le classi che si connettono al distributore di eventi devono avere come sufsso Listener. Le classi eccezione devono essere poste nel sotto-spazio dei nomi Exception. Venditori Un bundle non deve includere librerie PHP di terze parti. Deve invece appoggiarsi allauto-caricamento standard di Symfony2. Un bundle non dovrebbe includere librerie di terze parti scritte in JavaScript, CSS o altro linguaggio. Test Un bundle deve avere una suite di test scritta con PHPUnit e posta sotto la cartella Tests/. I test devono seguire i seguenti principi: La suite di test deve essere eseguibile con un semplice comando phpunit, eseguito da unapplicazione di esempio; 372 Capitolo 3. Ricettario Cartella Command/ Controller/ DependencyInjection/ EventListener/ Resources/config/ Resources/public/ Resources/translations/ Resources/views/ Tests/

Documentazione Symfony, Release 2.0

I test funzionali vanno usati solo per testare la risposta e alcune informazioni di prole, se se ne hanno; La copertura del codice deve essere almeno del 95%. Nota: Una suite di test non deve contenere script come AllTests.php, ma appoggiarsi a un le phpunit.xml.dist.

Documentazione Tutte le classi e le funzioni devono essere complete di PHPDoc. Una documentazione estensiva andrebbe fornita in formato reStructuredText, sotto la cartella Resources/doc/; il le Resources/doc/index.rst lunico le obbligatorio e deve essere il punto di ingresso della documentazione. Controllori Come best practice, i controllori di un bundle inteso per essere distribuito non devono estendere la classe base Symfony\Bundle\FrameworkBundle\Controller\Controller. Possono implementare Symfony\Component\DependencyInjection\ContainerAwareInterface oppure estendere Symfony\Component\DependencyInjection\ContainerAware . Nota: Se si d uno sguardo ai metodi di Symfony\Bundle\FrameworkBundle\Controller\Controller, si vedr che sono solo delle scorciatoie utili per facilitare lapprendimento.

Rotte Se il bundle fornisce delle rotte, devono avere come presso lalias del bundle. Per esempio, per AcmeBlogBundle, tutte le rotte devono avere come presso acme_blog_. Template Se un bundle fornisce template, devono usare Twig. Un bundle non deve fornire un layout principale, tranne se fornisce unapplicazione completa. File di traduzione Se un bundle fornisce messaggi di traduzione, devono essere deniti in formato XLIFF; il dominio deve avere il nome del bundle (bundle.hello). Un bundle non deve sovrascrivere messaggi esistenti in altri bundle. Congurazione Per fornire maggiore essibilit, un bundle pu fornire impostazioni congurabili, usando i meccanismi di Symfony2. Per semplici impostazioni di congurazione, appoggiarsi alla voce predenita parameters della congurazione di Symfony2. I parametri di Symfony2 sono semplici coppie chiave/valore; un valore pu essere un qualsiasi valore valido in PHP. Ogni nome di parametro dovrebbe iniziare con lalias del bundle, anche se questo solo un suggerimento. Gli altri nomi di parametri useranno un punto (.) per separare le varie parti (p.e. acme_hello.email.from). 3.1. Ricettario 373

Documentazione Symfony, Release 2.0

Lutente nale pu fornire valori in qualsiasi le di congurazione: YAML


# app/config/config.yml parameters: acme_hello.email.from: fabien@example.com

XML
<!-- app/config/config.xml --> <parameters> <parameter key="acme_hello.email.from">fabien@example.com</parameter> </parameters>

PHP
// app/config/config.php $container->setParameter(acme_hello.email.from, fabien@example.com);

INI
[parameters] acme_hello.email.from = fabien@example.com

Recuperare i parametri di congurazione nel proprio codice dal contenitore:


$container->getParameter(acme_hello.email.from);

Pur essendo questo meccanismo abbastanza semplice, si consiglia caldamente luso della congurazione semantica, descritta nel ricettario. Nota: Se si deniscono servizi, deve avere anche essi come presso lalias del bundle.

Imparare di pi dal ricettario Come esporre una congurazione semantica per un bundle

3.1.32 Come usare lereditariet per sovrascrivere parti di un bundle


Lavorando con bundle di terze parti, ci si trover probabilmente in situazioni in cui si vuole sovrascrivere un le in quel bundle con un le del proprio bundle. Symfony fornisce un modo molto conveniente per sovrascrivere cose come controllori, template, traduzioni e altri le nella cartella Resources/ di un bundle. Per esempio, si supponga di aver installato FOSUserBundle (https://github.com/friendsofsymfony/fosuserbundle), ma di voler sovrascrivere il suo template base layout.html.twig, cos come uno dei suoi controllori. Si supponga anche di avere il proprio AcmeUserBundle, in cui si vogliono mettere i le sovrascritti. Si inizi registrando FOSUserBundle come genitore del proprio bundle:
// src/Acme/UserBundle/AcmeUserBundle.php namespace Acme\UserBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; class AcmeUserBundle extends Bundle { public function getParent()

374

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

{ return FOSUserBundle; } }

Con questa semplice modica, si possono ora sovrascrivere diverse parti di FOSUserBundle, semplicemente creando un le con lo stesso nome. Sovrascrivere i controllori Si supponga di voler aggiungere alcune funzionalit a registerAction di un RegistrationController, che sta dentro FOSUserBundle. Per poterlo fare, creare un proprio RegistrationController.php, ridenire i metodi originali del bundle e cambiarne le funzionalit:
// src/Acme/UserBundle/Controller/RegistrationController.php namespace Acme\UserBundle\Controller; use FOS\UserBundle\Controller\RegistrationController as BaseController; class RegistrationController extends BaseController { public function registerAction() { $response = parent::registerAction(); // do custom stuff return $response; } }

Suggerimento: A seconda di quanto si vuole cambiare il comportamento, si potrebbe voler richiamare parent::registerAction() oppure sostituirne completamente la logica con una propria.

Nota: Sovrascrivere i controllori in questo modo funziona solo se il bundle fa riferimento al controllore tramite la sintassi standard FOSUserBundle:Registration:register in rotte e template. Questa la best practice.

Sovrascrivere risorse: template, rotte, traduzioni, validazione, ecc. Anche molte risorse possono essere sovrascritte, semplicemente creando un le con lo stesso percorso del bundle genitore. Per esempio, molto comune lesigenza di sovrascrivere il template layout.html.twig di FOSUserBundle, in modo che usi il layout di base della propria applicazione. Poich il le si trova in Resources/views/layout.html.twig di FOSUserBundle, si pu creare il proprio le nello stesso posto in AcmeUserBundle. Symfony ignorer completamente il le dentro FOSUserBundle e user il nuovo le al suo posto. Lo stesso vale per i le delle rotte, della congurazione della validazione e di altre risorse. Nota: La sovrascrittura di risorse funziona solo se ci si riferisce alle risorse col metodo @FosUserBundle/Resources/config/routing/security.xml. Se ci si riferisce alle risorse senza usare la scorciatoia @NomeBundle, non possono essere sovrascritte in tal modo. 3.1. Ricettario 375

Documentazione Symfony, Release 2.0

Attenzione: I le delle traduzioni non funzionano nel modo descritto sopra. Tutti i le di traduzione sono raggruppati in un insieme di domini di pool (uno per ciascuno). Symfony carica i le delle traduzioni prima dai bundle (nellordine in cui i bundle sono inizializzati) e poi dalla cartella app/Resources. Se la stessa traduzione compare in pi risorse, sar applicata la traduzione della risorsa caricata per ultima.

3.1.33 Come sovrascrivere parti di un bundle


Questo articolo non ancora stato scritto, ma lo sar presto. Se qualcuno fosse interessato a scriverlo, veda Contribuire alla documentazione. Questo argomento dovrebbe mostrare come sovrascrivere ciascuna parte di un bundle, sia da unapplicazione che da altri bundle. Questo potrebbe includere: Template Rotte Controllori Servizi & congurazione Entit & mappatura di entit Form Validazione In alcuni casi, si potrebbe parlare di best practice che un bundle deve usare per fare in modo che certe parti siano sovrascrivibili (o facilmente sovrascrivibili). Si potrebbe anche parlare di come alcune parti non siano effettivamente sovrascrivibili, mostrando lapproccio migliore per risolvere il problema.

3.1.34 Come esporre una congurazione semantica per un bundle


Se si apre il le di congurazione della propria applicazione (di solito app/config/config.yml), si vedranno un certo numero di spazi di nomi di congurazioni, come framework, twig e doctrine. Ciasucno di questi congura uno specico bundle, consentendo di congurare cose ad alto livello e quindi lasciando al bundle tutte le modiche complesse e di basso livello. Per esempio, il codice seguente dice a FrameworkBundle di abilitare lintegrazione con i form, che implica la denizione di alcuni servizi, cos come anche lintegrazione di altri componenti correlati: YAML
framework: # ... form:

true

XML
<framework:config> <framework:form /> </framework:config>

PHP

376

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

$container->loadFromExtension(framework, array( // ... form => true, // ... ));

Quando si crea un bundle, si hanno due scelte sulla gestione della congurazione: 1. Normale congurazione di servizi (facile): Si possono specicare i propri servizi in un le di congurazione (p.e. services.yml) posto nel proprio bundle e quindi importarlo dalla congurazione principale della propria applicazione. Questo molto facile, rapido ed efcace. Se si usano i parametri, si avr ancora la essibilit di personalizzare il bundle dalla congurazione della propria applicazione. Vedere Importare la congurazione con imports per ulteriori dettagli. 2. Esporre una congurazione semantica (avanzato): Questo il modo usato per la congurazione dei bundle del nucleo (come descritto sopra). Lidea di base che, invece di far sovrascrivere allutente i singoli parametri, lasciare che ne conguri alcune opzioni create specicatamente. Lo sviluppatore del bundle deve quindi analizzare tale congurazione e caricare i servizi allinterno di una classe Extension. Con questo metodo, non si avr bisogno di importare alcuna risorsa di congurazione dallappplicazione principale: la classe Extension pu gestire tutto. La seconda opzione, di cui parleremo, molto pi essibili, ma richiede anche pi tempo di preparazione. Se si ci sta chiedendo quale metodo scegliere, probabilmente una buona idea partire col primo metodo, poi cambiare al secondo, se se ne ha necessit. Il secondo metodo ha diversi vantaggi: Molto pi potente che denire semplici parametri: un valore specico di unopzione pu scatenare la creazioni di molte denizioni di servizi; Possibilit di avere una gerarchia di congurazioni Fusione intelligente quando diversi le di congurazione (p.e. sovrascrivono le proprie congurazioni a vicenda; config_dev.yml e config.yml)

Validazione della congurazione (se si usa una classe di congurazione); auto-completamento nellIDE quando si crea un XSD e lo sviluppatore usa XML. Sovrascrivere parametri dei bundle Se un bundle fornisce una classe Extension, in generale non si dovrebbe sovrascrivere alcun parametro del contenitore di servizi per quel bundle. Lidea che se presente una classe Extension, ogni impostazione congurabile sia presente nella congurazione messa a disposizione da tale classe. In altre parole, la classe Extension denisce tutte le impostazioni supportate pubblicamente, per i quali sar mantenuta una retro-compatibilit.

Creare una classe Extension Se si sceglie di esporre una congurazione semantica per il proprio bundle, si avr prima bisogno di creare una nuova classe Extension, per gestire il processo. Tale classe va posta nella cartella DependencyInjection del proprio bundle e il suo nome va costruito sostituendo il postsso Bundle del nome della classe del bundle con Extension. Per esempio, la classe Extension di AcmeHelloBundle si chiamerebbe AcmeHelloExtension:

3.1. Ricettario

377

Documentazione Symfony, Release 2.0

// Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\ContainerBuilder; class AcmeHelloExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { // qui sta tutta la logica } public function getXsdValidationBasePath() { return __DIR__./../Resources/config/; } public function getNamespace() { return http://www.example.com/symfony/schema/; } }

Nota: I metodi getXsdValidationBasePath e getNamespace servono solo se il bundle fornisce degli XSD facoltativi per la congurazione. La presenza della classe precedente vuol dire che si pu denire uno spazio dei nomi acme_hello in un qualsiasi le di congurazione. Lo spazio dei nomi acme_hello viene dal nome della classe Extension, a cui stata rimossa la parola Extension e posto in minuscolo e con trattini bassi il resto del nome. In altre parole, AcmeHelloExtension diventa acme_hello. Si pu iniziare specicando la congurazione sotto questo spazio dei nomi: YAML
# app/config/config.yml acme_hello: ~

XML
<!-- app/config/config.xml --> <?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:acme_hello="http://www.example.com/symfony/schema/" xsi:schemaLocation="http://www.example.com/symfony/schema/ http://www.example.com/symfony/sc <acme_hello:config /> ... </container>

PHP
// app/config/config.php $container->loadFromExtension(acme_hello, array());

378

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Suggerimento: Seguendo le convenzioni di nomenclatura viste sopra, il metodo load() della propria estensione sar sempre richiamato, a patto che il proprio bundle sia registrato nel Kernel. In altre parole, anche se lutente non fornisce alcuna congurazione (cio se la voce acme_hello non appare mai), il metodo load() sar richiamato, passandogli un array $configs vuoto. Si possono comunque fornire valori predeniti adeguati per il proprio bundle, se lo si desidera.

Analisi dellarray $configs Ogni volta che un utente include lo spazio dei nomi acme_hello in un le di congurazione, la congurazione sotto di esso viene aggiunta a un array di congurazioni e passata al metodo load() dellestensione (Symfony2 converte automaticamente XML e YAML in array). Si prenda la seguente congurazione: YAML
# app/config/config.yml acme_hello: foo: valoreDiFoo bar: valoreDiBar

XML
<!-- app/config/config.xml --> <?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:acme_hello="http://www.example.com/symfony/schema/" xsi:schemaLocation="http://www.example.com/symfony/schema/ http://www.example.com/symfony/sc <acme_hello:config foo="valoreDiFoo"> <acme_hello:bar>valoreDiBar</acme_hello:bar> </acme_hello:config> </container>

PHP
// app/config/config.php $container->loadFromExtension(acme_hello, array( foo => valoreDiFoo, bar => valoreDiBar, ));

Larray passato al metodo load() sar simile a questo:


array( array( foo => valoreDiFoo, bar => valoreDiBar, ) )

Si noti che si tratta di un array di array, non di un semplice array di valori di congurazione. stato fatto intenzionalmente. Per esempio, se acme_hello appare in un altro le di congurazione, come config_dev.yml, con valori diversi sotto di esso, larray in uscita sar simile a questo:

3.1. Ricettario

379

Documentazione Symfony, Release 2.0

array( array( foo bar ), array( foo baz ), )

=> valoreDiFoo, => valoreDiBar,

=> valoreDevDiFoo, => nuovaVoceDiConfig,

Lordine dei due array dipende da quale stato denito prima. compito di chi sviluppa il bundle, quindi, decidere in che modo tali congurazioni vadano fuse insieme. Si potrebbe, per esempio, voler fare in modo che i valori successivi sovrascrivano quelli precedenti, oppure fonderli in qualche modo. Successivamente, nella sezione classe Conguration, si imparer un modo robusto per gestirli. Per ora, ci si pu accontentare di fonderli a mano:
public function load(array $configs, ContainerBuilder $container) { $config = array(); foreach ($configs as $subConfig) { $config = array_merge($config, $subConfig); } // usare ora larray $config }

Attenzione: Assicurarsi che la tecnica di fusione vista sopra abbia senso per il proprio bundle. Questo solo un esempio e andrebbe usato con la dovuta cautela.

Usare il metodo load() Con load(), la variabile $container si riferisce a un contenitore che conosce solo la congurazione del proprio spazio dei nomi (cio non contiene informazioni su servizi caricati da altri bundle). Lo scopo del metodo load() quello di manipolare il contenitore, aggiungere e congurare ogni metodo o servizio necessario per il proprio bundle.
Caricare risorse di congurazioni esterne

Una cosa che si fa di solito caricare un le di congurazione esterno, che potrebbe contenere i servizi necessari al proprio bundle. Per esempio, si supponga di avere un le services.xml, che contiene molte delle congurazioni di servizio del proprio bundle:
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\Config\FileLocator; public function load(array $configs, ContainerBuilder $container) { // prepara la propria variabile $config $loader = new XmlFileLoader($container, new FileLocator(__DIR__./../Resources/config)); $loader->load(services.xml); }

380

Capitolo 3. Ricettario

Documentazione Symfony, Release 2.0

Lo si potrebbe anche con una condizione, basata su uno dei valori di congurazione. Per esempio, si supponga di voler caricare un insieme di servizi, ma solo se unopzione enabled impostata a true:
public function load(array $configs, ContainerBuilder $container) { // prepara la propria variabile $config $loader = new XmlFileLoader($container, new FileLocator(__DIR__./../Resources/config)); if (isset($config[enabled]) && $config[enabled]) { $loader->load(services.xml); } }

Congurare servizi e impostare parametri

Una volta caricati alcune congurazioni di servizi, si potrebbe aver bisogno di modicare la congurazione in base ad alcuni valori inseriti. Per esempio, si supponga di avere un servizio il cui primo parametro una stringa type, che sar u