You are on page 1of 658

Symfony2 documentation

Documentation
Release 2

SensioLabs

March 12, 2012

CONTENTS

Giro rapido
1.1 Giro rapido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1
1

Libro
2.1 Libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

23
23

Ricettario
251
3.1 Ricettario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251

Componenti
435
4.1 I componenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435

Documenti di riferimento
471
5.1 Documenti di riferimento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471

Bundle
591
6.1 Bundle di Symfony SE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591

Contributi
633
7.1 Contribuire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633

Index

651

ii

CHAPTER

ONE

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.
Tip: Si vuole imparare perch e quando si ha bisogno di un framework? Si legga il documento Symfony in 5
minuti.

Scaricare Symfony2
Prima di tutto, verificare di avere almeno PHP 5.3.2 (o successivo) installato e configurato correttamente per
funzionare con un server web, come Apache.
Pronti? Iniziamo scaricando Symfony2 Standard Edition, una distribuzione di Symfony preconfigurata 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/
Acme/
DemoBundle/
Controller/
Resources/
...
vendor/
symfony/

Symfony2 documentation Documentation, Release 2

doctrine/
...
web/
app.php
...

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

Verifica della configurazione


Per evitare mal di testa successivamente, Symfony2 dispone di uno strumento per testare la configurazione, per
verificare configurazioni 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 modificare la propria configurazione, seguendo le
raccomandazioni fornite. Quando tutto a posto, cliccare su Bypass configuration 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 finora!

Capire i fondamenti
Uno degli obiettivi principali di un framework quello di assicurare la Separazione degli ambiti. 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.

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

Tip: 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

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
(/demo/hello/Fabien) alla risorsa a essa associata (la pagina HTML Hello Fabien!).

dellutente

Rotte

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

1.1. Giro rapido

Symfony2 documentation Documentation, Release 2

Le prime righe (dopo il commento) definiscono 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 significa.
Tip: La Standard Edition usa YAML per i suoi file di configurazione, 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));

Note: Symfony2 abbraccia le specifiche 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 configurazione 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);
}
}

Tip:
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 flessibilit.
La classe WelcomeController estende la classe predefinita 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:

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

public function indexAction()


{
$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 finale 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.
Tip: 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 file 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 file di configurazione 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 file,


scritti in
YAML, XML, PHP o anche inseriti in annotazioni PHP. Qui, il nome logico del file

@AcmeDemoBundle/Controller/DemoController.php
e
si
riferisce
al
file
src/Acme/DemoBundle/Controller/DemoController.php. In questo file, le rotte sono definite 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() definisce 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.
Note: Anche se le annotazioni sono sono supportate nativamente da PHP, possono essere usate in Symfony2
come mezzo conveniente per configurare i comportamenti del framework e mantenere la configurazione accanto
al codice.

1.1. Giro rapido

Symfony2 documentation Documentation, Release 2

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).
Tip: Le annotazioni @Route() e @Template() sono pi potenti dei semplici esempi mostrati in questa guida.
Si pu approfondire largomento annotazioni nei controllori nella documentazione ufficiale.

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 predefinita, Symfony2 usa Twig 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 finora. Tutto il codice che si scrive per
la propria applicazione organizzato in bundle. Nel linguaggio di Symfony2, un bundle un insieme strutturato
di file (file 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.

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

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

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

Infine, 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:
http://localhost/demo/hello/Fabien

Note: 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

1.1. Giro rapido

Symfony2 documentation Documentation, Release 2

Edition di Symfony, si otterr un errore 404, perch AcmeDemoBundle abilitato solo in ambiente dev e le sue
rotte importate in app/config/routing_dev.yml.
Per rendere lambiente di produzione pi veloce possibile, Symfony2 mantiene una cache sotto la cartella
app/cache/. Quando si fanno delle modifiche al codice o alla configurazione, occorre rimuovere a mano i
file 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 configurazione. In effetti, una configurazione 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 file di configurazione config_dev.yml) importa il file globale config.yml
e lo modifica, in questo caso, abilitando la barra di debug del web.
Considerazioni finali
Congratulazioni! Avete avuto il vostro primo assaggio di codice di Symfony2. Non era cos difficile, 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. Twig un sistema di template
veloce, flessibile e sicuro per PHP. Rende i propri template pi leggibili e concisi e anche pi amichevoli per i
designer.
Note: Invece di Twig, si pu anche usare PHP per i proprio template. Entrambi i sistemi di template sono
supportati da Symfony2.

Familiarizzare con Twig

Tip: Se si vuole imparare Twig, suggeriamo caldamente di leggere la sua documentazione ufficiale. Questa
sezione solo un rapido sguardo ai concetti principali.
Un template Twig un file di test che pu generare ogni tipo di contenuto (HTML, XML, CSV, LaTeX, ...). Twig
definisce due tipi di delimitatori:
{{ ...

}}: Stampa una variabile o il risultato di unespressione;

{% ... %}: Controlla la logica del template; usato per eseguire dei cicli for e delle istruzioni if,
per esempio.
Segue un template minimale, che illustra alcune caratteristiche di base, usando due variabili, page_title e
navigation, che dovrebbero essere passate al template:

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

<!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>

Tip: 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) }}

Note: 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.

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 definisce dei blocchi, che i template figli
possono sovrascrivere.

1.1. Giro rapido

Symfony2 documentation Documentation, Release 2

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 file corrispondente si trova direttamente sotto la cartella Resources/views/.
Diamo ora unocchiata a una versione semplificata di layout.html.twig:
{# src/Acme/DemoBundle/Resources/views/layout.html.twig #}
<div class="symfony-content">
{% block content %}
{% endblock %}
</div>

I tag {% block %} definiscono blocchi che i template figli possono riempire. Tutto ci che fa un tag di blocco
dire al sistema di template che un template figlio 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, filtri e funzioni
Una delle migliori caratteristiche di Twig la sua estensibilit tramite tag, filtri 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 definire 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:


{# 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:

10

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

{# 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 configurazione delle rotte. In questo
modo, tutti gli URL saranno facilmente aggiornati al cambiare della configurazione:
<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 definiti 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()
*/
public function helloAction($name)
{
return array(name => $name);
}

Tip: La funzione url genera URL assoluti: {{ url(_demo_hello, { name:


}}.

Thomas })

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) }}" />

1.1. Giro rapido

11

Symfony2 documentation Documentation, Release 2

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 configurato in modo predefinito per lescape automatico di ogni output. Si legga la documentazione di
Twig per sapere di pi sullescape delloutput e sullestensione Escaper.
Considerazioni finali
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
flessibile.
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. Modificare il file routing.yml e aggiungere un formato
_format, con valore xml:
// 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 definito 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:

12

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

// 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|xm
* @Template()
*/
public function helloAction($name)
{
return array(name => $name);
}

Ora
il
controller
sar
richiamato
/demo/hello/Fabien.json.

per

URL

come

/demo/hello/Fabien.xml

La voce requirements definisce 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

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.
1.1. Giro rapido

13

Symfony2 documentation Documentation, Release 2

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 configurazione di sicurezza, che soddisfa i bisogni pi
comuni:
# app/config/security.yml
security:
encoders:
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:
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/

14

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

Questa configurazione richiede agli utenti di effettuare login per ogni URL che inizi per /demo/secured/ e
definisce 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).
Tip: Per leggibilit, le password sono memorizzate in chiaro in questa semplice configurazione, ma si pu usare
un qualsiasi algoritmo di hash, modificando 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),
indicando che lutente non autorizzato ad accedere a tale risorsa.
Note: Il livello di sicurezza di Symfony2 molto flessibile e fornisce diversi provider per gli utenti (come quello
per lORM Doctrine) e provider di autenticazione (come HTTP basic, HTTP digest o certificati X509). Si legga il
capitolo Sicurezza del libro per maggiori informazioni su come usarli e configurarli.

Mettere in cache le risorse


Non appena il proprio sito inizia a generare pi traffico, 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 predefinito di Symfony2. Ma poich la cache gestita usando
i normali header di cache di HTTP, possibile rimpiazzare il reverse proxy predefinito con Varnish o Squid e
scalare facilmente la propria applicazione.
1.1. Giro rapido

15

Symfony2 documentation Documentation, Release 2

Note: 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 finali
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 finora 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.
Capire la struttura delle cartelle
La struttura delle cartelle di unapplicazione Symfony2 alquanto flessibile, ma la struttura delle cartelle della
distribuzione Standard Edition riflette la struttura tipica e raccomandata di unapplicazione Symfony2:
app/: La configurazione 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 file pubblici e statici, come immagini, fogli di stile, file 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 file 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 configurazione dellapplicazione e quindi memorizzata nella cartella app/.
Questa classe deve implementare due metodi:
16

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

registerBundles() deve restituire un array di tutti i bundle necessari per eseguire lapplicazione;
registerContainerConfiguration() carica la configurazione dellapplicazione (approfondito
pi avanti);
Il caricamento automatico di PHP pu essere configurato 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,
Assetic
=> __DIR__./../vendor/assetic/src,
Metadata
=> __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 file


che rispettano gli standard di interoperabilit per gli spazi dei nomi di PHP 5.3 oppure la convenzione 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.
Note: Se si vuole approfondire largomento flessibilit 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 flessibilit 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
fine giornata, il codice della propria applicazione importante quanto il nucleo stesso del framework.
Registrare un bundle

Unapplicazione composta di bundle, come definito nel metodo registerBundles() della classe
AppKernel . Ogni bundle una cartella che contiene una singola classe Bundle che la descrive:

1.1. Giro rapido

17

Symfony2 documentation Documentation, Release 2

// 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(),
);
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.
Configurare un bundle

Ogni bundle pu essere personalizzato tramite file di configurazione scritti in YAML, XML o PHP. Si veda la
configurazione predefinita:
# app/config/config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
framework:
#esi:
#translator:
secret:
charset:
router:
form:
csrf_protection:
validation:
templating:
default_locale:
session:
auto_start:

~
{ fallback: %locale% }
%secret%
UTF-8
{ resource: "%kernel.root_dir%/config/routing.yml" }
true
true
{ enable_annotations: true }
{ engines: [twig] } #assets_version: SomeVersionScheme
%locale%
true

# Configurazione di Twig
twig:
debug:
%kernel.debug%
strict_variables: %kernel.debug%
# Configurazione di
assetic:
debug:
use_controller:
bundles:

18

Assetic
%kernel.debug%
false
[ ]

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

# java: /usr/bin/java
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%
port:
%database_port%
dbname:
%database_name%
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 definisce la configurazione per uno specifico bundle. Per esempio, framework
configura FrameworkBundle, mentre swiftmailer configura SwiftmailerBundle.
Ogni ambiente pu sovrascrivere la configurazione predefinita, fornendo un file di configurazione specifico.
Per esempio, lambiente dev carica il file config_dev.yml, che carica la configurazione principale (cio
config.yml) e quindi la modifica 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:

1.1. Giro rapido

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

19

Symfony2 documentation Documentation, Release 2

assetic:
use_controller: true

Estendere un bundle

Oltre a essere un modo carino per organizzare e configurare 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 file. Qui sono daiuto i nomi logici (come
@AcmeDemoBundle/Controller/SecuredController.php), che astraggono i posti in cui le risorse
sono effettivamente memorizzate.
Nomi logici di file Quando si vuole fare riferimento a un file 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 file src/Acme/DemoBundle/Resources/views/Welcome/index.html.twig.
I template diventano ancora pi interessanti quando si realizza che i file non hanno bisogno di essere memorizzati
su filesystem. 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 file, 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 flessibile? 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 file YAML e XML a ogni richiesta? In parte, per il suo sistema di cache. La
configurazione dellapplicazione analizzata solo per la prima richiesta e poi compilata in semplice file PHP,
memorizzato nella cartella app/cache/ dellapplicazione. Nellambiente di sviluppo, Symfony2 abbastanza
intelligente da pulire la cache quando cambiano dei file. In produzione, invece, occorre pulire la cache manualmente quando si aggiorna il codice o si modifica la configurazione.
Sviluppando unapplicazione web, le cose possono andar male in diversi modi. I file di log nella cartella
app/logs/ dicono tutto a proposito delle richieste e aiutano a risolvere il problema in breve tempo.

20

Chapter 1. Giro rapido

Symfony2 documentation Documentation, Release 2

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.
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 finali
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, finch 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

21

Symfony2 documentation Documentation, Release 2

22

Chapter 1. Giro rapido

CHAPTER

TWO

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 filosofia 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, ha luogo la seguente conversazione
(approssimata):

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

23

Symfony2 documentation Documentation, Release 2

scopo del proprio server sempre quello di interpretare semplici richieste testuali e restituire semplici risposte
testuali.
Symfony2 costruito fin 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)

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 identifica la risorsa che il client vuole.
Il metodo HTTP (p.e. GET) definisce cosa si vuole fare con la risorsa. I metodi HTTP sono verbi della richiesta e
definiscono 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 specifica voce di un blog, per esempio:
DELETE /blog/15 HTTP/1.1

Note: Ci sono in realt nove metodi HTTP definiti dalla specifica 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.
24

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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
<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
fine? 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.
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.
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 filosofia di sviluppo seguita, lo scopo finale di unapplicazione sempre quello di capire
ogni richiesta e creare e restituire unappropriata risposta.
Larchitettura di Symfony strutturata per corrispondere a questa realt.

2.1. Libro

25

Symfony2 documentation Documentation, Release 2

Tip: Per saperne di pi sulla specifica HTTP, si pu leggere la RFC HTTP 1.1 originale o la HTTP Bis, che uno
sforzo attivo di chiarire la specifica originale. Un importante strumento per verificare sia gli header di richiesta
che quelli di risposta durante la navigazione lestensione Live HTTP Headers 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, 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);

26

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// 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() verifica tre diversi valori in PHP che possono indicare se lutente si stia
connettendo o meno tramite una connessione sicura (cio https).
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\\ParameterBag::all 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.
Tip: 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 file.

Il viaggio dalla richiesta alla risposta


Come lo stesso HTTP, gli oggetti Request e Response sono molto semplici. La parte difficile 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?
2.1. Libro

27

Symfony2 documentation Documentation, Release 2

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 file fisico:
index.php
contact.php
blog.php

Ci sono molti problemi con questo approccio, inclusa la flessibilit degli URL (che succede se si vuole cambiare
blog.php con news.php senza rompere tutti i collegamenti?) e il fatto che ogni file deve includere manualmente alcuni file 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 file 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

Tip: 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 file 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 verificare 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 difficile. Fortunatamente, esattamente quello che Symfony studiato
per fare.
Il flusso 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:

28

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Figure 2.1: Le richieste in entrata sono interpretate dal routing e passate alle funzioni del controllore, che restituisce oggetti Response.
Ogni pagina del proprio sito definita in un file di configurazione 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.
cos facile! Rivediamolo:
Ogni richiesta esegue un file front controller;
Il sistema delle rotte determina quale funzione PHP deve essere eseguita, in base allinformazione proveniente dalla richiesta e alla configurazione 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 file di
configurazione delle rotte:
contact:
pattern: /contact
defaults: { _controller: AcmeDemoBundle:Main:contact }

Note: Lesempio usa YAML per definire la configurazione delle rotte. La configurazione 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 specificato eseguito.
Come si imparer nel capitolo delle rotte, la stringa AcmeDemoBundle:Main:contact una sintassi breve
che punta a uno specifico metodo PHP contactAction in una classe chiamata MainController:
class MainController
{
public function contactAction()
{
return new Response(<h1>Contattaci!</h1>);
}
}

2.1. Libro

29

Symfony2 documentation Documentation, Release 2

In questo semplice esempio, il controllore semplicemente crea un oggetto Response con il codice HTML
<h1>Contacttaci!</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 file template separato. Questo consente al controllore di preoccuparsi solo delle cose difficili:
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 difficile 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 - Contiene le classi Request e Response, insieme ad altre classi per gestire sessioni e
caricamenti di file;
Routing - Sistema di rotte potente e veloce, che consente di mappare uno specifico URI (p.e.
/contact) ad alcune informazioni su come tale richiesta andrebbe gestita (p.e. eseguendo il metodo
contactAction());
Form - Un framework completo e flessibile per creare form e gestire invii di dati;
Validator Un sistema per creare regole sui dati e quindi validarli, sia che i dati inviati dallutente seguano o
meno tali regole;
ClassLoader Una libreria di autoloading che consente luso di classi PHP senza bisogno di usare manualmente require sui file che contengono tali classi;
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 - Una potente libreria per gestire tutti i tipi di sicurezza allinterno di unapplicazione;
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:
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 configurazione 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 configurato o sostituito interamente.

30

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 configurazioni predefinite ragionevoli. Gli
utenti avanzati hanno il cielo come limite.

2.1.2 Symfony2 contro PHP puro


Perch Symfony2 meglio che aprire un file e scrivere PHP puro?
Questo capitolo per chi non ha mai usato un framework PHP, non ha familiarit con la filosofia 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 fine, 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>
</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 verifica degli errori: Che succede se la connessione al database fallisce?

2.1. Libro

31

Symfony2 documentation Documentation, Release 2

Scarsa organizzazione: Se lapplicazione cresce, questo singolo file 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?
Difficolt nel riusare il codice: Essendo tutto in un solo file, non c modo di riusare alcuna parte
dellapplicazione per altre pagine del blog.
Note: Un altro problema non menzionato il fatto che il database legato a MySQL. Sebbene non affrontato
qui, Symfony2 integra in pieno Doctrine, 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 beneficiare 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 file separato (templates/list.php), che essenzialmente un file 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): ?>
<li>
<a href="/read?id=<?php echo $post[id] ?>">
<?php echo $post[title] ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</body>
</html>

Per convenzione, il file 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.

32

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 file 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 file, 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;
}

Tip: 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 rappresenta
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.

2.1. Libro

33

Symfony2 documentation Documentation, Release 2

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 file
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 semplificato, 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.
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 identificato dal parametro
id.
Per iniziare, creiamo nel file 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);

34

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

return $row;
}

Quindi, creiamo un file 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;

Infine, creiamo un nuovo file 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 file model.php. Che fare se poi
occorresse includere un secondo file o eseguire un altro compito globale (p.e. garantire la sicurezza)? Nella
situazione attuale, tale codice dovrebbe essere aggiunto a ogni singolo file. Se lo si dimentica in un file, speriamo
che non sia qualcosa legato alla sicurezza.
Un front controller alla riscossa
La soluzione usare un front controller: un singolo file PHP attraverso il quale tutte le richieste sono processate.
Con un front controller, gli URI dellapplicazione cambiano un poco, ma iniziano a diventare pi flessibili:
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)

Tip: 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 file PHP (index.php in questo caso) rende ogni richiesta. Per la pagina
che mostra il post, /index.php/show eseguir in effetti il file index.php, che ora responsabile per gestire
internamente le richieste, in base allURI. Come vedremo, un front controller uno strumento molto potente.

2.1. Libro

35

Symfony2 documentation Documentation, Release 2

Creazione del front controller

Stiamo per fare un grosso passo avanti con lapplicazione. Con un solo file a gestire tutte le richieste, possiamo
centralizzare cose come gestione della sicurezza, caricamento della configurazione, 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 file separato, controllers.php:
function list_action()
{
$posts = get_all_posts();
require templates/list.php;
}
function show_action($id)
{
$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.
Tip: Un altro vantaggio di un front controller sono gli URL flessibili. 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 file. In Symfony2, gli URL sono ancora pi flessibili.
Finora, lapplicazione si evoluta da un singolo file 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?

36

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 file che contiene la classe.
Primo, scaricare symfony e metterlo in una cartella vendor/symfony/.
Poi, creare un file
app/bootstrap.php. Usarlo per il require dei due file dellapplicazione e per configurare 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 file che le contengono.
Una delle idee principali della filosofia di Symfony che il compito principale di unapplicazione
sia quello di interpretare ogni richiesta e restituire una risposta.
A tal fine, 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;
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()
{

2.1. Libro

37

Symfony2 documentation Documentation, Release 2

$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 flessibile e pi affidabile. La classe
Request fornisce un modo di accedere alle informazioni sulla richiesta HTTP. Nello specifico, 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 flessibilit durante la costruzione della risposta HTTP, consentendo di aggiungere header
e contenuti HTTP tramite uninterfaccia orientata agli oggetti. Mentre in questa applicazione le risposte molto
semplici, tale flessibilit 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 e 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));

38

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

}
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>
<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>

Note: 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 configurazione delle rotte fornisce tali
informazioni in un formato leggibile:
# app/config/routing.yml
blog_list:
pattern: /blog
defaults: { _controller: AcmeBlogBundle:Blog:list }

2.1. Libro

39

Symfony2 documentation Documentation, Release 2

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 modificarlo 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 finale.
Non resta molto altro da fare.
Per una rappresentazione visuale di come Symfony2 gestisca ogni richiesta, si veda il diagramma di flusso 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 flessibili, 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. 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
Template migliori
Se lo si vuole usare, Symfony2 ha un motore di template predefinito, chiamato Twig, 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 %}

40

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

{% 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>
<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
Definire i controllori come servizi

2.1.3 Installare e configurare 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.
Tip: 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

Tip: Verificare 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 configurazioni predefinite. 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:

2.1. Libro

41

Symfony2 documentation Documentation, Release 2

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 installato sulla propria macchina, si dovrebbe
scaricare Symfony2 senza venditori, perch aggiunge pi flessibilit 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
file):
# per il file .tgz
tar zxvf Symfony_Standard_Vendors_2.0.###.tgz
# per il file .zip
unzip Symfony_Standard_Vendors_2.0.###.zip

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 fine, 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 cookbookmanaging-vendor-libraries.
Configurazione

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

Se ci sono problemi, correggerli prima di proseguire.

42

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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/l


sudo chmod +a "whoami allow delete,write,append,file_inherit,directory_inherit" app/cache app/l

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 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 modificare lACL delle cartelle, occorrer modificare 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 file
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 modifica 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 finora!

2.1. Libro

43

Symfony2 documentation Documentation, Release 2

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, verificare il file README.rst incluso nella distribuzione
(aprendolo come file 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
configurazioni 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 specifiche 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 file .gitignore la seguente
riga:
vendor/

Ora la cartella dei venditori non sar inviata in commi 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.

44

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 definisce lURL (p.e. /about) verso la pagina e specifica 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 filosofia 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 finito, 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.
Note: La guida presume che Symfony2 sia stato gi scaricato e il server web configurato. 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 specifica caratteristica, incluse
classi PHP, configurazioni e anche fogli di stile e file 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 predefinite):
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 file app/AppKernel.php, in modo che il bundle sia registrato nel kernel:
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Acme\HelloBundle\AcmeHelloBundle(),
);
// ...
return $bundles;
}

2.1. Libro

45

Symfony2 documentation Documentation, Release 2

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 predefinita, il file di configurazione delle rotte in unapplicazione Symfony2 si trova in
app/config/routing.yml. Come ogni configurazione in Symfony2, si pu anche scegliere di usare XML
o PHP per configurare le rotte.
Se si guarda il file 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/r
<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 configurazione delle rotte dal file
Resources/config/routing.yml, che si trova dentro AcmeHelloBundle. Questo vuol dire che si
mette la configurazione 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 file routing.yml del bundle stato importato, aggiungere la nuova rotta, che definisce lURL della
pagina che stiamo per creare:
YAML
# 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" ?>

46

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

<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/r
<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 specifica 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.
Note: Il sistema delle rotte ha molte altre importanti caratteristiche per creare strutture di URL flessibili 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 file dentro
il nostro AcmeHelloBundle:
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
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 finale 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
{

2.1. Libro

47

Symfony2 documentation Documentation, Release 2

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

Tip: 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.


Note: 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 file separato e riusare diverse
porzioni del layout della pagina. Invece di scrivere il codice HTML dentro al controllore, meglio rendere un
template:
1
2

// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;

3
4

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

5
6
7
8
9
10

class HelloController extends Controller


{
public function indexAction($name)
{
return $this->render(AcmeHelloBundle:Hello:index.html.twig, array(name => $name));

11

// render a PHP template instead


// return $this->render(AcmeHelloBundle:Hello:index.html.php, array(name => $name));

12
13

14
15

Note:
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 fine loggetto Response viene restituito.

48

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Si noti che ci sono due diversi esempi su come rendere il template. Per impostazione predefinita, Symfony2
supporta due diversi linguaggi di template: i classici template PHP e i template, concisi ma potenti, Twig. 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 fisica, 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

{# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #}
{% extends ::base.html.twig %}

3
4
5
6

{% 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 definisce un template padre. Il template definisce esplicitamente un file 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 fine 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:
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>

2.1. Libro

49

Symfony2 documentation Documentation, Release 2

<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 definisce il layout HTML e rende il blocco body, che era stato definito nel template
index.html.twig. Rende anche un blocco title, che si pu scegliere di definire nel template nel template
index.html.twig. Poich non stato definito il blocco title nel template figlio, il suo valore predefinito
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 filosofia 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
fine di questa sezione, sapremo dove cercare e inserire i vari tipi di file, e perch.
Sebbene interamente flessibili, per impostazione predefinita, ogni application Symfony ha la stessa struttura di
cartelle raccomandata:
app/: Questa cartella contiene la configurazione dellapplicazione;
src/: Tutto il codice PHP del progetto sta allinterno di questa cartella;
vendor/: Ogni libreria dei venditori inserita qui, per convenzione;
web/: Questa la cartella radice del web e contiene ogni file accessibile pubblicamente;
La cartella web

La cartella radice del web la casa di tutti i file pubblici e statici, inclusi immagini, fogli di stile, file 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 file del front controller (app.php in questo esempio) il file PHP che viene eseguito quando si usa
unapplicazione Symfony2 e il suo compito quello di usare una classe kernel, AppKernel, per inizializzare
lapplicazione.
Tip: Aver un front controller vuol dire avere URL diverse e pi flessibili rispetto a una tipica applicazione in
puro PHP. Quando si usa un front controller, gli URL sono formattati nel modo seguente:
50

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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

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

Sebbene i front controller siano essenziali nella gestione di ogni richiesta, raramente si avr bisogno di modificarli
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 configurazione. Per questo memorizzata nella cartella app/.
Questa classe deve implementare due metodi, che definiscono 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 predefinite.
registerBundles(): Restituisce un array di tutti bundle necessari per eseguire lapplicazione (vedere
Il sistema dei bundle);
registerContainerConfiguration():
Carica il file della
dellapplicazione (vedere la sezione Configurazione dellapplicazione).

configurazione

principale

Nello sviluppo quotidiano, per lo pi si user la cartella app/ per modificare i file di configurazione e delle
rotte nella cartella app/config/ (vedere Configurazione dellapplicazione). Essa contiene anche la cartella
della cache dellapplicazione (app/cache), la cartella dei log (app/logs) e la cartella dei file risorsa a livello
di applicazione, come i template (app/Resources). Ognuna di queste cartella sar approfondita nei capitoli
successivi.
Autoload
Quando Symfony si carica, un file speciale chiamato app/autoload.php viene incluso. Questo file
responsabile di configurare lautoloader, che auto-caricher i file 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 file al posto nostro, nel momento in cui la classe necessaria.
Lautoloader gi configurato per cercare nella cartella src/ tutte le proprie classi PHP. Per poterlo far
funzionare, il nome della classe e quello del file 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 file 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, file di configurazione, 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?
2.1. Libro

51

Symfony2 documentation Documentation, Release 2

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 flessibilit 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.
Note: Pur trovando qui i fondamentali, unintera ricetta dedicata allorganizzazione e alle pratiche migliori in
bundle.
Un bundle semplicemente un insieme strutturato di file 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 file 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 definito nel metodo registerBundles() della classe
AppKernel:
// 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(),
);
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).
Tip: Un bundle pu stare ovunque, purch possa essere auto-caricato (tramite lautoloader configurato 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.

52

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Tip: La parte Acme solo un nome fittizio, 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 file chiamato
AcmeTestBundle.php:
// src/Acme/TestBundle/AcmeTestBundle.php
namespace Acme\TestBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeTestBundle extends Bundle
{
}

Tip: 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 file TestBundle.php).
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.
Tip: 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 flessibile. Per impostazione predefinita, 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 configurazione, compresa la configurazione delle rotte (p.e.
routing.yml);

2.1. Libro

53

Symfony2 documentation Documentation, Release 2

Resources/views/ contiene
Hello/index.html.twig);

template,

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 file 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.
Configurazione dellapplicazione
Unapplicazione composta da un insieme di bundle, che rappresentano tutte le caratteristiche e le capacit
dellapplicazione stessa. Ogni bundle pu essere personalizzato tramite file di configurazione, scritti in YAML,
XML o PHP. Per impostazione predefinita, il file di configurazione 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.yml }
- { resource: security.yml }
framework:
secret:
charset:
router:
# ...

%secret%
UTF-8
{ resource: "%kernel.root_dir%/config/routing.yml" }

# Configurazione di Twig
twig:
debug:
%kernel.debug%
strict_variables: %kernel.debug%
# ...

XML
<!-- app/config/config.xml -->
<imports>
<import resource="parameters.yml" />
<import resource="security.yml" />
</imports>
<framework:config charset="UTF-8" secret="%secret%">
<framework:router resource="%kernel.root_dir%/config/routing.xml" />
<!-- ... -->
</framework:config>
<!-- Configurazione di Twig -->
<twig:config debug="%kernel.debug%" strict-variables="%kernel.debug%" />
<!-- ... -->

PHP
$this->import(parameters.yml);
$this->import(security.yml);

54

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

$container->loadFromExtension(framework, array(
secret
=> %secret%,
charset
=> UTF-8,
router
=> array(resource => %kernel.root_dir%/config/routing.php),
// ...
),
));
// Configurazione di Twig
$container->loadFromExtension(twig, array(
debug
=> %kernel.debug%,
strict_variables => %kernel.debug%,
));
// ...

Note: Vedremo esattamente come caricare ogni formato di file nella prossima sezione, Ambienti.
Ogni voce di primo livello, come framework o twig, definisce la configurazione per un particolare bundle. Per esempio, la voce framework definisce la configurazione per il bundle del nucleo di Symfony
FrameworkBundle e include configurazioni per rotte, template e altri sistemi fondamentali.
Per ora, non ci preoccupiamo delle opzioni di configurazione specifiche di ogni sezione. Il file di configurazione ha
delle opzioni predefinite impostate. Leggendo ed esplorando ogni parte di Symfony2, le opzioni di configurazione
specifiche saranno man mano approfondite.
Formati di configurazione
Nei vari capitoli, tutti gli esempi di configurazione 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 configurazione standard.

Esportazione della configurazione predefinita

New in version 2.1: Il comando config:dump-reference stato aggiunto in Symfony 2.1 Si


pu esportare la configurazione predefinita per un bundle in yaml sulla console, usando il comando
config:dump-reference. Ecco un esempio di esportazione della configurazione predefinita di FrameworkBundle:
app/console config:dump-reference FrameworkBundle

Note: Vedere la ricetta Come esporrre una configurazione semantica per un bundle per informazioni sullaggiunta
di configurazioni per il proprio bundle.

Ambienti
Unapplicazione pu girare in vari ambienti. I diversi ambienti condividono lo stesso codice PHP (tranne per il
front controller), ma usano differenti configurazioni. Per esempio, un ambiente dev salver nei log gli avvertimenti e gli errori, mentre un ambiente prod solamente gli errori. Alcuni file 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.

2.1. Libro

55

Symfony2 documentation Documentation, Release 2

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 configurazione, le rotte e i template Twig sono compilato
in classi in puro PHP e messi in cache. Per vedere delle modifiche in ambiente prod, occorrer pulire tali file in
cache e consentire che siano ricostruiti:
php app/console cache:clear --env=prod --no-debug

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

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

Note: 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.

Configurazione degli ambienti

La classe AppKernel responsabile del caricare effettivamente i file 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 configurazione. Si noti anche che ogni ambiente carica i propri file di configurazione.
Consideriamo il file di configurazione 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
<!-- app/config/config_dev.xml -->
<imports>
<import resource="config.xml" />
</imports>

56

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

<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 file di configurazione principale
(config.yml) sia caricato per primo. Il resto del file gestisce la configurazione 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 file di configurazione
di base e quindi modifica i suoi file di configurazione per soddisfare le esigenze dello specifico ambiente. Questa
solo una convenzione, ma consente di riusare la maggior parte della propria configurazione 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 flessibile. 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/ (configurazione), 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 file relativi a tale caratteristica;
la configurazione per ciascun bundle risiede nella cartella app/config e pu essere specificata 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 file di configurazione.
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 flessibilit della sua architettura e la potenza che d nello sviluppo rapido di
applicazioni.

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.

2.1. Libro

57

Symfony2 documentation Documentation, Release 2

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 fine 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.
Infine, 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 infine esegue un controllore, che ospita il codice personalizzato dellapplicazione:
1. Ogni richiesta gestita da un singolo file 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).
Note: 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 file 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 modificare, visualizzare o preoccuparsi del controllore principale dellapplicazione.

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.

58

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// src/Acme/HelloBundle/Controller/HelloController.php

2
3
4

namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;

5
6
7
8
9
10
11
12

class HelloController
{
public function indexAction($name)
{
return new Response(<html><body>Ciao .$name.!</body></html>);
}
}

Tip: 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 configurazione delle rotte.
linea 8: A ogni azione in una classe controllore viene aggiunto il suffisso Action mentre nella configurazione 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 specifico 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>

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

2.1. Libro

59

Symfony2 documentation Documentation, Release 2

Andando in /hello/ryan ora viene eseguito il controllore HelloController::indexAction() e viene


passato ryan nella variabile $name. Creare una pagina significa 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 flessibile 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.
Note: Questo esempio pone la configurazione 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.

Tip: 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 verifica 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 }

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

60

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// 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 predefinita 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 flessibile. 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 definito 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
Se, per esempio, last_name non importante per il controllore, si pu ometterlo del tutto:
public function indexAction($first_name, $color)
{
// ..
}

Tip: 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.

2.1. Libro

61

Symfony2 documentation Documentation, Release 2

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 modificare 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.
Tip: 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.

Note: inoltre possibile definire 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.

62

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 predefinita, il metodo redirect() esegue un rinvio 302 (temporaneo). Per eseguire un rinvio
301 (permanente), modificare il secondo parametro:
public function indexAction()
{
return $this->redirect($this->generateUrl(homepage), 301);
}

Tip: 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 specificato. 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

// modifica ulteriormente la risposta o ritorna direttamente


return $response;
}

Si noti che il metodo forward() utilizza la stessa rappresentazione stringa del controllore utilizzato nella configurazione 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 modifica lordine dei parametri, Symfony2 continuer a passare il corretto
valore di ogni variabile.

2.1. Libro

63

Symfony2 documentation Documentation, Release 2

Tip: 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 fine 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.
Tip: 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));

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

64

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 finale viene mostrata una pagina di errore predefinita 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
predefinita, Symfony2 memorizza gli attributi in un cookie utilizzando le sessioni PHP native.
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 flash

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 flash.
Per esempio, immaginiamo che si stia elaborando un form inviato:
public function updateAction()
{
$form = $this->createForm(...);

2.1. Libro

65

Symfony2 documentation Documentation, Release 2

$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 flash notice e poi rinvia. Il nome
(notice) non significativo, solo quello che si utilizza per identificare 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; ?>

Per come sono stati progettati, i messaggi flash sono destinati a vivere esattamente per una richiesta (hanno la
durata di un flash). 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);

Tip: La propriet headers un oggetto Symfony\Component\HttpFoundation\HeaderBag con


alcuni utili metodi per leggere e modificare 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:

66

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

$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 finali
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 finale 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.
Imparare di pi dal ricettario
Come personalizzare le pagine di errore
Definire i controllori come servizi

2.1.6 Le rotte
URL ben realizzati sono una cosa assolutamente da avere per qualsiasi applicazione web seria. Questo
significa lasciarsi alle spalle URL del tipo index.php?article_id=57 in favore di qualcosa come
/read/intro-to-symfony.
Avere flessibilit ancora pi importante. Che cosa succede se necessario modificare lURL di una pagina da
/blog a /news? Quanti collegamenti bisogna cercare e aggiornare per realizzare la modifica? Se si stanno
utilizzando le rotte di Symfony la modifica semplice.
Le rotte di Symfony2 consentono di definire URL creativi che possono essere mappati in differenti aree
dellapplicazione. Entro la fine 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:

2.1. Libro

67

Symfony2 documentation Documentation, Release 2

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/r
<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;
$collection = new RouteCollection();
$collection->add(blog_show, new Route(/blog/{slug}, array(
_controller => AcmeBlogBundle:Blog:show,
)));
return $collection;

Lo schema definito 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 specifico 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.

68

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 verifica la corrispondenza dellURL in arrivo con una specifica 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 fine restituisce un oggetto Response.

Figure 2.2: Lo strato delle rotte uno strumento che traduce lURL in ingresso in uno specifico controllore da
eseguire.

Creazione delle rotte


Symfony carica tutte le rotte per lapplicazione da un singolo file con la configurazione delle rotte. Il file generalmente app/config/routing.yml, ma pu essere configurato per essere qualunque cosa (compreso un file
XML o PHP) tramite il file di configurazione 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

2.1. Libro

69

Symfony2 documentation Documentation, Release 2

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

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

Configurazione di base delle rotte

Definire 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:
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/r
<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:

70

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

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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/r
<route id="blog_show" pattern="/blog/{slug}">
<default key="_controller">AcmeBlogBundle:Blog:show</default>
</route>
</routes>

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 verifica questa stringa.
Tuttavia lo schema non deve corrispondere semplicemente a /blog. Questo perch, per impostazione predefinita,
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/r
<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();

2.1. Libro

71

Symfony2 documentation Documentation, Release 2

$collection->add(blog, new Route(/blog, array(


_controller => AcmeBlogBundle:Blog:index,
)));
return $collection;

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/r
<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 verifica {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 predefinita 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, modificare
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/r
<route id="blog" pattern="/blog/{page}">

72

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

<default key="_controller">AcmeBlogBundle:Blog:index</default>
<default key="page">1</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,
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 finora:
YAML
blog:
pattern:
defaults:

/blog/{page}
{ _controller: AcmeBlogBundle:Blog:index, page: 1 }

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/r
<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
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;

2.1. Libro

73

Symfony2 documentation Documentation, Release 2

$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 verificano 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 verificato 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/r
<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(
_controller => AcmeBlogBundle:Blog:index,
page => 1,
), array(
page => \d+,
)));
return $collection;

74

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 significato 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 flessibilit 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/r
<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(
_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).

2.1. Libro

75

Symfony2 documentation Documentation, Release 2

/
/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 verificare 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 configurazione 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/r
<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
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,
)));

76

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 significa che possibile visualizzare
il form e invia e inviarlo utilizzando lo stesso URL ma controllori distinti per le due azioni.
Note: Se non viene specificato 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 flessibile 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/r
<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>
</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;

2.1. Libro

77

Symfony2 documentation Documentation, Release 2

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 definitiva, 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 predefinito 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);
Tip: Se si usa il parametro _locale in una rotta, il valore sar memorizzato nella sessione, in modo che le
richieste successive lo mantengano.

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 specifico metodo PHP di una certa classe. Lo schema ha tre parti, ciascuna
separata da due punti:
bundle:controllore:azione
Per esempio, se _controller ha valore AcmeBlogBundle:Blog:show significa:
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)
{
// ...
}
}

78

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 flessibilit.
Note: 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 Definire 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
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.
Tip: 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 file di configurazione, generalmente
app/config/routing.yml (vedere Creazione delle rotte sopra). In genere, per, si desidera caricare
le rotte da altri posti, come un file di rotte presente allinterno di un bundle. Questo pu essere fatto importando
il file:
YAML
# app/config/routing.yml
acme_hello:
resource: "@AcmeHelloBundle/Resources/config/routing.yml"

XML

2.1. Libro

79

Symfony2 documentation Documentation, Release 2

<!-- 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/r
<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;

Note: 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
file, dove la sintassi scorciatoia @AcmeHelloBundle viene risolta con il percorso del bundle. Il file importato
potrebbe essere tipo questo:
YAML
# src/Acme/HelloBundle/Resources/config/routing.yml
acme_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/r
<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 file sono analizzate e caricate nello stesso modo del file principale delle rotte.

80

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Prefissare le rotte importate

Si pu anche scegliere di fornire un prefisso per le rotte importate. Per esempio, si supponga di volere che la
rotta acme_hello abbia uno schema finale 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/r
<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"),
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 configurate 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 specifiche 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 +
2.1. Libro

81

Symfony2 documentation Documentation, Release 2

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


Tip: Se la propria applicazione usa richieste AJAX, si potrebbe voler generare URL in JavaScript, che siano
basate sulla propria configurazione delle rotte. Usando 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 predefinita, il router genera URL relativi (ad esempio /blog). Per generare un URL assoluto,
sufficiente 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

Note: 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

82

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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
<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), t


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 file testuale che pu generare qualsiasi formato basato sul testo (HTML, XML, CSV,
LaTeX ...). Il tipo pi familiare di template un template PHP, un file testuale analizzato da PHP che contiene un
misto di testo e codice PHP:

2.1. Libro

83

Symfony2 documentation Documentation, Release 2

<!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>
</body>
</html>

Ma Symfony2 possiede un linguaggio di template ancora pi potente, chiamato Twig. Twig consente di scrivere
template concisi e leggibili, pi amichevoli per i grafici 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 definisce 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.
Note: 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 filtri, che modificano 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 e filtri, disponibili in maniera predefinita. Si possono anche aggiungere le proprie
estensioni a Twig, se necessario.
Tip: facile registrare unestensione di Twig basta creare un nuovo servizio e taggarlo con twig.extension
tag.
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
84

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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.
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 benefici e distinzione. E, ovviamente, essere amati da tutti i grafici 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 filtri 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 modifica subita. Questo vuol dire che durante lo sviluppo si possono tranquillamente effettuare
cambiamenti a un template Twig e vedere immediatamente le modifiche, 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, definiti come
blocchi (li si pensi come classi PHP con metodi base). Un template figlio 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 file per il layout di base:
Twig

2.1. Libro

85

Symfony2 documentation Documentation, Release 2

{# 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>
<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>

Note: Sebbene la discussione sullereditariet dei template sia relativa a Twig, la filosofia condivisa tra template
Twig e template PHP.
Questo template definisce lo scheletro del documento HTML di base di una semplice pagina a due colonne. In
questo esempio, tre aree {% block %} sono definite (title, sidebar e body). Ciascun blocco pu essere
sovrascritto da un template figlio o lasciato alla sua implementazione predefinita. Questo template potrebbe anche
essere reso direttamente. In questo caso, i blocchi title, sidebar e body manterrebbero semplicemente i
valori predefiniti usati in questo template.
Un template figlio potrebbe assomigliare a questo:
Twig

86

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
{% extends ::base.html.twig %}
{% 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() ?>

Note: Il template padre identificato 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 definisce i vari blocchi. Quindi viene reso il template figlio e i blocchi title e body del padre vengono rimpiazzati da quelli del figlio. 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>

Si noti che, siccome il template figlio non definisce 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
predefinito.
2.1. Libro

87

Symfony2 documentation Documentation, Release 2

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 figli non devono
definire tutti i blocchi del padre, quindi si possono creare molti blocchi nei template base e dar loro dei
valori predefiniti adeguati. Pi blocchi si hanno in un template base, pi sar flessibile 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 predefinita, 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 specifico:
AcmeBlogBundle:Blog:index.html.twig: Questa sintassi usata per specificare un template
per una determinata pagina. Le tre parti della stringa, ognuna separata da due-punti (:), hanno il seguente
significato:
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 file index.html.twig.
Ipotizzando che AcmeBlogBundle sia dentro src/Acme/BlogBundle, il percorso finale 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 specifico di AcmeBlogBundle. Poich la parte centrale, controllore, manca, (p.e. Blog), il template
Resources/views/layout.html.twig dentro AcmeBlogBundle.
::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 significa che il template non in alcun bundle, ma invece nella cartella
radice app/Resources/views/.

88

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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.
Tip: Si spera che la sintassi dei nomi risulti familiare: la stessa convenzione di nomi usata per Schema per il
nome dei controllori.

Suffissi dei template

Il formato bundle:controllore:template di ogni template specifica dove il file del template si trova. Ogni nome di
template ha anche due estensioni, che specificano 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 predefinita, ogni template Symfony2 pu essere scritto in Twig o in PHP, e lultima parte
dellestensione (p.e. .twig o .php) specifica quale di questi due motori va usata. La prima parte dellestensione,
(p.e. .html, .css, ecc.) il formato finale 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.
Note: I motori disponibili possono essere configurati e se ne possono aggiungere di nuovi. Vedere Configurazione 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
difficile 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 predefiniti ({% 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.
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

2.1. Libro

89

Symfony2 documentation Documentation, Release 2

{# 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: arti
{% 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(
<?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.
Tip: 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}.

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.

90

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 =>


}
}

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; ?>

Note:
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
<!-- app/Resources/views/base.html.php -->
...

<div id="sidebar">
<?php echo $view[actions]->render(AcmeArticleBundle:Article:recentArticles, array(ma
</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.
2.1. Libro

91

Symfony2 documentation Documentation, Release 2

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 configurazione delle rotte. Pi avanti, se si vuole modificare lURL di una particolare pagina,
tutto ci di cui si avr bisogno cambiare la configurazione delle rotte: i template genereranno automaticamente
il nuovo URL.
Primo, collegare la pagina _welcome, accessibile tramite la seguente configurazione 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
<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 specificare 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:
92

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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->g
<?php echo $article->getTitle() ?>
</a>
<?php endforeach; ?>

Tip: 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 PH, 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
<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="tex

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 configurazione assets_version.
Includere fogli di stile e Javascript in Twig
Nessun sito sarebbe completo senza linclusione di file Javascript e fogli di stile. In Symfony, linclusione di tali
risorse gestita elegantemente sfruttando lereditariet dei template.

2.1. Libro

93

Symfony2 documentation Documentation, Release 2

Tip: Questa sezione insegner la filosofia che sta dietro linclusione di fogli di stile e Javascript in Symfony.
Symfony dispone di unaltra libreria, chiamata Assetic, che segue la stessa filosofia, 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 figlio? 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 %}
{{ parent() }}
<link href="{{ asset(/css/contact.css) }}" type="text/css" rel="stylesheet" />
{% endblock %}
{# ... #}

Nel template figlio, 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 file
nella posizione corretta (la posizione predefinita sotto la cartella web).
<link href="{{ asset(bundles/acmedemo/css/contact.css) }}" type="text/css" rel="stylesheet" />

Il risultato finale una pagina che include i fogli di stile main.css e contact.css.
Global Template Variables
During
each
request,
both Twig and PHP

94

Symfony2
will
template engines

set
by

a
global
default.

template
variable
app
The app variable is

in
a

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables
give you access to some application specific variables automatically:

instance

which

will

app.security - The security context.


app.user - The current user object.
app.request - The request object.
app.session - The session object.
app.environment - The current environment (dev, prod, etc).
app.debug - True if in debug mode. False otherwise.
Twig
<p>Username: {{ app.user.username }}</p>
{% if app.debug %}
<p>Request method: {{ app.request.method }}</p>
<p>Application Environment: {{ app.environment }}</p>
{% endif %}

PHP
<p>Username: <?php echo $app->getUser()->getUsername() ?></p>
<?php if ($app->getDebug()): ?>
<p>Request method: <?php echo $app->getRequest()->getMethod() ?></p>
<p>Application Environment: <?php echo $app->getEnvironment() ?></p>
<?php endif; ?>

Tip: You can add your own global template variables. See the cookbook example on Global Variables.

Configurare 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-configurato per funzionare automaticamente dentro a Symfony2. Pu
anche essere ulteriormente configurato nel file di configurazione dellapplicazione:
YAML
# app/config/config.yml
framework:
# ...
templating: { engines: [twig] }

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

2.1. Libro

95

Symfony2 documentation Documentation, Release 2

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

Sono disponibili diverse opzioni di configurazione, coperte nellAppendice: configurazione.


Note: Il motore twig obbligatorio per poter usare il webprofiler (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) 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 specifico per la nostra applicazione. Analizzando il controllore Blog di
AcmeBlogBundle, troviamo:
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 file 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.

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

96

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 file app/Resources/views/base.html.twig che contenga il layout principael 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 specifici alla
sezione blog;
{# src/Acme/BlogBundle/Resources/views/layout.html.twig #}
{% extends ::base.html.twig %}
{% block body %}
<h1>Applicazione blog</h1>
{% 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 (XSS).
Consideriamo questo classico esempio:
Twig

2.1. Libro

97

Symfony2 documentation Documentation, Release 2

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 finestra 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;

Lapproccio dei sistemi di template Twig e PHP a questo problema sono diversi. Se si usa Twig, lescape attivo
in modo predefinito 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 predefinito. Questo vuol dire che si protetti dalle conseguenze non intenzionali del codice inviato dallutente. Per impostazione predefinita, 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 affidabile che contiene markup. Supponiamo che gli utenti amministratori siano abilitati a scrivere articoli
che contengano codice HTML. Per impostazione predefinita, Twig mostrer larticolo con escape. Per renderlo
normalmente, aggiungere il filtro 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 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 predefinita, 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
New in version 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 configurazione:
98

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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" />
</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 predefinita, 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 file 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();

2.1. Libro

99

Symfony2 documentation Documentation, Release 2

return $this->render(AcmeBlogBundle:Blog:index..$format..twig);
}

Il metodo getRequestFormat delloggetto Request ha come valore predefinito html, ma pu restituire


qualsiasi altro formato, in base al formato richiesto dallutente. Il formato di richiesta spesso gestito dalle
rotte, quando una rotta configurata 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:
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 =>


versione PDF
</a>

Considerazioni finali
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 flessibile e mette a disposizione due sistemi di template: i tradizionali
template PHP e i potenti e raffinati 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, una libreria il cui unico
scopo quello di fornire potenti strumenti per facilitare tali compiti. In questo capitolo, si imparer la filosofia
alla base di Doctrine e si vedr quanto possa essere facile lavorare con un database.
Note: Doctrine totalmente disaccoppiato da Symfony e il suo utilizzo facoltativo. Questo capitolo tutto su
Doctrine, che si prefigge 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.
100

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Si possono anche persistere dati su MongoDB usando la libreria ORM Doctrine. Per ulteriori informazioni, leggere
la documentazione DoctrineMongoDBBundle.

Un semplice esempio: un prodotto


Il modo pi facile per capire come funziona Doctrine quello di vederlo in azione. In questa sezione, configureremo 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

Configurazione del database

Prima di iniziare, occorre configurare le informazioni sulla connessione al database. Per convenzione, questa
informazione solitamente configurata in un file 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

Note: La definizione della configurazione tramite parameters.yml solo una convenzione. I parametri
definiti in tale file sono riferiti dal file di configurazione 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 file a parte, si possono mantenere facilmente diverse versioni del file
su ogni server. Si possono anche facilmente memorizzare configurazioni di database (o altre informazioni sensibili) fuori dal proprio progetto, come per esempio dentro la configurazione di Apache. Per ulteriori informazioni,
vedere Configurare 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

101

Symfony2 documentation Documentation, Release 2

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.
Tip: 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(

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 configurazione che
dice esattamente a Doctrine come la classe Product e le sue propriet debbano essere mappate sul database.
Questi meta-dati possono essere specificati in diversi formati, inclusi YAML, XML o direttamente dentro la classe
Product, tramite annotazioni:
Note: Un bundle pu accettare un solo formato di definizione dei meta-dati. Per esempio, non possibile
mischiare definizioni di meta-dati in YAML con definizioni tramite annotazioni.
Annotations
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="product")
*/

102

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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">
<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>

2.1. Libro

103

Symfony2 documentation Documentation, Release 2

Tip: 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 book-doctrine-field-types.
See Also:
Si pu anche consultare la Documentazione di base sulla mappatura di Doctrine per tutti i dettagli sulla
mappatura. Se si usano le annotazioni, occorrer aggiungere a ogni annotazione il prefisso 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 prefisso ORM delle annotazioni.
Caution: 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 predefinito della tabella sar group, che causer un errore SQL in alcuni sistemi di database. Vedere la
Documentazione sulle parole riservate di SQL per sapere come fare correttamente escape di tali nomi.

Note: 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).
Di pi su doctrine:generate:entities
Con il comando doctrine:generate:entities si pu:
generare getter e setter,
generare classi repository configurate 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 file originale
Product.php, chiamata Product.php~. In alcuni casi, la presenza di questo file pu causare un errore
Cannot redeclare class. Il file 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:

104

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

php app/console doctrine:generate:entities AcmeStoreBundle


php app/console doctrine:generate:entities Acme

Note: 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

Tip: 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 affidabile.
Il proprio database ora ha una tabella product pienamente funzionante, con le colonne corrispondenti ai metadati specificati.
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:
1
2
3
4

// src/Acme/StoreBundle/Controller/DefaultController.php
use Acme\StoreBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
// ...

5
6
7
8
9
10
11

public function createAction()


{
$product = new Product();
$product->setName(Pippo Pluto);
$product->setPrice(19.99);
$product->setDescription(Lorem ipsum dolor);

12

$em = $this->getDoctrine()->getEntityManager();
$em->persist($product);
$em->flush();

13
14
15
16

return new Response(Creato prodotto con id .$product->getId());

17
18

2.1. Libro

105

Symfony2 documentation Documentation, Release 2

Note: 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.
Note: Di fatto, essendo Doctrine consapevole di tutte le proprie entit gestite, quando si chiama il metodo
flush(), esso calcola un insieme globale di modifiche ed esegue le query pi efficienti 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 efficienza.
Quando si creano o aggiornano oggetti, il flusso 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.
Tip: Doctrine fornisce una libreria che consente di caricare dati di test nel proprio progetto (le cosiddette fixture). Per informazioni, vedere DoctrineFixturesBundle.

Recuperare oggetti dal database

Recuperare un oggetto dal database ancora pi facile. Per esempio, supponiamo di aver configurato una rotta
per mostrare uno specifico Product, in base al valore del suo id:
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);

Note: 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
finch le proprie entit rimarranno sotto lo spazio dei nomi Entity del proprio bundle.
106

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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);

Note: Si possono ovviamente fare anche query complesse, su cui si pu avere maggiori informazioni nella
sezione book-doctrine-queries.
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)
);

Tip: 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 profiler, 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);

2.1. Libro

107

Symfony2 documentation Documentation, Release 2

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. modificare 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:
$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 finch 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();

108

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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();

Caution:
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 ufficiale di Doctrine
Doctrine Query Language.
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 dallautocompletamento 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)

2.1. Libro

109

Symfony2 documentation Documentation, Release 2

->setParameter(price, 19.99)
->orderBy(p.price, ASC)
->getQuery();
$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.
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 definizione 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.

110

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// 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();
}
}

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


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

Note: Quando si usa una classe repository personalizzata, si ha ancora accesso ai metodi predefiniti 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

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
{
// ...
/**
* @ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
protected $products;
public function __construct()

2.1. Libro

111

Symfony2 documentation Documentation, Release 2

{
$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 aggiutna 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.
Note: 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 flessibilit in pi. Se non sembra confortevole, niente paura. Si immagini solamente che sia un
array.

Tip: Il valore targetEntity, usato in precedenza sul decoratore, pu riferirsi a qualsiasi entit con uno spazio
dei nomi valido, non solo a entit definite nella stessa classe. Per riferirsi a entit definite 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
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
type: entity
# ...
manyToOne:
category:
targetEntity: Category
inversedBy: products
joinColumn:

112

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

name: category_id
referencedColumnName: id

Infine, 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.

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.

2.1. Libro

113

Symfony2 documentation Documentation, Release 2

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

Note: 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:


// ...
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 flusso 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();

114

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// ...
}

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.

Quello che importante il fatto che si ha facile accesso al prodotto correlato con la categoria, ma i dati della
categoria non sono recuperati finch 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

115

Symfony2 documentation Documentation, Release 2

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 fino a che non sia effettivamente necessario (cio fino 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).
Tip: 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)
{
$product = $this->getDoctrine()
->getRepository(AcmeStoreBundle:Product)
->findOneByIdJoinedToCategory($id);

116

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

$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.
Note:
Se si usano le annotazioni, occorrer aggiungere a tutte le annotazioni il prefisso 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 prefisso delle annotazioni ORM.

Configurazione
Doctrine altamente configurabile, sebbene probabilmente non si avr nemmeno bisogno di preoccuparsi di gran
parte delle sue opzioni. Per saperne di pi sulla configurazione 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()
{
$this->created = new \DateTime();
}

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

2.1. Libro

117

Symfony2 documentation Documentation, Release 2

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

Note: 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 significato 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
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 flessibile 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.

118

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 specifico 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 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 dei tipi.
Opzioni dei campi

Ogni campo pu avere un insieme di opzioni da applicare. Le opzioni disponibili includono type (predefinito
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

2.1. Libro

119

Symfony2 documentation Documentation, Release 2

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

Note: Ci sono alcune altre opzioni, non elencate qui. Per maggiori dettagli, vedere la Documentazione sulla
mappatura delle propriet

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 prefisso 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:


doctrine:ensure-production-settings - verifica se lambiente attuale sia configurato efficientemente 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.
Note: Per poter caricare fixture 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.

120

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 affidabili, 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.
Note: 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/

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

Test unitari
Un test unitario solitamente un test di una specifica 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 file CalculatorTest nella cartella Tests/Utility del proprio bundle:

2.1. Libro

121

Symfony2 documentation Documentation, Release 2

// 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);
}
}

Note: 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 il file
bootstrap.php.cache (come configurato in modo predefinito nel file phpunit.xml.dist).
Anche eseguire i test per un dato file o una data cartella molto facile:
# eseguire tutti i test nella cartella Utility
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/
# 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 verificano lintegrazione dei diversi livelli di unapplicazione (dalle rotte alle viste). Non differiscono dai test unitari per quello che riguarda PHPUnit, ma hanno un flusso di lavoro molto specifico:
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 file 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
file DemoControllerTest.php, che estende una classe speciale WebTestCase.
Per esempio, ledizione standard di Symfony2 fornisce un semplice test funzionale per il suo DemoController
(DemoControllerTest), fatto in questo modo:

122

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// 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);
}
}

Tip: 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 kenerl si trova in una cartella non
standard, occorre modificare il file phpunit.xml.dist e impostare nella variabile dambiente KERNEL_DIR
la cartella del proprio kernel:
<phpunit
<!-- ... -->
<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 sil 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.
Tip: 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 infine 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);

2.1. Libro

123

Symfony2 documentation Documentation, Release 2

Tip: Il form pu anche gestire caricamenti di file 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());

Di pi sul metodo request():


La firma 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 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));

124

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 file.
Tip: Gli oggetti Link e Form nel crawler saranno approfonditi nella sezione Crawler, pi avanti.
Il metodo request() pu anche essere usto per simulare direttamente linvio di form o per eseguire richieste
pi complesse:
// 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 requests e passare header HTTP
$client->request(
DELETE,
/post/12,
array(),
array(),
array(PHP_AUTH_USER => username, PHP_AUTH_PW => pa$$word)
);

Infine, 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();

2.1. Libro

125

Symfony2 documentation Documentation, Release 2

Accesso agli oggetti interni

Se si usa il client per testare la propria applicazione, si potrebbe voler accedere agli oggetti interni del client:
$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.
Tip: Se linformazione che occorre verificare disponibile nel profiler, si usi invece questultimo.

Accedere ai dati del profilatore

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

Per dettagli specifici sulluso del profilatore in un test, vedere la ricetta Come usare il profilatore 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 followRedirects():


$client->followRedirects();

126

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 specificato
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();

Tip:
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);
// 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)

2.1. Libro

127

Symfony2 documentation Documentation, Release 2

$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 filtro, restituisce un altro oggetto Crawler.
Una volta selezionato un collegamento, si ha accesso a uno speciale oggetto Link, che ha utili metodi specifici 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);

Note: 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
predefiniti:
$form = $buttonCrawlerNode->form(array(
name
=> Fabien,
my_form[subject] => Symfony spacca!,
));

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

128

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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);

Tip: Si possono ottenere i valori che saranno inviati, richiamando il metodo getValues(). I file 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).

Configurazione 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 modificare le impostazioni della propria
applicazione sperificatamente per i test.
Per esempio, swiftmailer configurato in modo predefinito per non inviare le email in ambiente test. Lo si pu
vedere sotto lopzione di configurazione swiftmailer:
YAML
# app/config/config_test.yml
# ...
swiftmailer:
disable_delivery: true

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

PHP

2.1. Libro

129

Symfony2 documentation Documentation, Release 2

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

Si pu anche cambiare lambiente predefinito (test) e sovrascrivere la modalit predefinita 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,
));

Tip: 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 ridefinire completamente il servizio, qualora se ne avesse
la necessit.

Configurazione di PHPUnit

Ogni applicazione ha la sua configurazione di PHPUnit, memorizzata nel file phpunit.xml.dist. Si pu


modificare tale file per cambiare i default, oppure creare un file phpunit.xml per aggiustare la configurazione
per la propria macchina locale.
Tip: Inserire il file phpunit.xml.dist nel proprio repository e ignorare il file phpunit.xml.
Per impostazione predefinita, solo i test memorizzati nei bundle standard sono eseguiti dal comando phpunit
(per standard si intendono i test sotto gli spazi dei nomi Vendor\*Bundle\Tests). Ma si possono facilmente
aggiungere altri spazi dei nomi. Per esempio, la configurazione 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 altri spazi dei nomi nella copertura del codice, modificare anche la sezione <filter>:
<filter>
<whitelist>

130

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

<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 profilatore 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 , che rende questo compito facile e trasparente. Questo componente
bastato sulle specifiche di validazione JSR303 Bean. Cosa? Specifiche 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
{
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 configurare una
lisa di regole (chiamate vincoli) che loggetto deve seguire per poter essere valido. Queste regole possono essere
specificate 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;

2.1. Libro

131

Symfony2 documentation Documentation, Release 2

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/s
<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());
}
}

Tip: Anche le propriet private e protette possono essere validati, cos come i metodi getter (vedere validatorconstraint-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 verificare se i dati delloggetto soddisfi 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

132

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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


Tip: 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:


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>

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

2.1. Libro

133

Symfony2 documentation Documentation, Release 2

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 flusso 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(),
));
}

Note: Questo esempio usa una classe AuthorType, non mostrata qui.
Per maggiori informazioni, vedere il capitolo sui Form.
Configurazione
La validazione in Symfony2 abilitata per configurazione predefinita, ma si devono abilitare esplicitamente le
annotazioni, se le si usano per specificare 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,
)));

134

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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
NotNull
Null
True
False
Type
Vincoli stringhe

Email
MinLength
MaxLength
Url
Regex
Ip
Vincoli numerici

Max
Min
Vincoli date

Date
DateTime
Time

2.1. Libro

135

Symfony2 documentation Documentation, Release 2

Vincoli di insiemi

Choice
Collection
UniqueEntity
Language
Locale
Country
Vincoli di file

File
Image
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.
Configurazione dei vincoli

Alcuni vincoli, come NotBlank, sono semplici, mentre altri, come Choice, hanno diverse opzioni di configurazione
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;
}

136

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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/s
<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>
</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, predefinita, al posto dellarray. Nel caso del vincolo Choice, lopzione
choices pu essere specificata 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;
}

2.1. Libro

137

Symfony2 documentation Documentation, Release 2

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/s
<class name="Acme\BlogBundle\Entity\Author">
<property name="gender">
<constraint name="Choice">
<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 configurazione delle opzioni pi comuni di un vincolo pi breve e rapida.
Se non si sicuri di come specificare unopzione, verificare 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 specificare 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 configurare 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

138

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @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

2.1. Libro

139

Symfony2 documentation Documentation, Release 2

// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @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);
}

Note: I lettori pi attenti avranno notato che il prefisso 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 specifici di questo vincolo vengono
semplicemente eseguiti, in modo che ognuno possa fornire una validazione personalizzata.

140

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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
definiti. 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>
</property>
<property name="password">
<constraint name="NotBlank">
<option name="groups">

2.1. Libro

141

Symfony2 documentation Documentation, Release 2

<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 configurazione, 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 specifico gruppo, passare uno o pi nomi di gruppo come secondo parametro
del metodo validate():
$errors = $validator->validate($author, array(registration));

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 verificare che una stringa sia un indirizzo email valido. Lo si pu fare molto facilmente. Da dentro a un

142

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 finali
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.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.
Note: 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 su Github.

2.1. Libro

143

Symfony2 documentation Documentation, Release 2

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 modificare 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;
}
}

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

Questa classe un vecchio-semplice-oggetto-PHP, perch finora 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 fine 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.
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

144

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

{
public function newAction(Request $request)
{
// crea un task fornendo alcuni dati fittizi per questo esempio
$task = new Task();
$task->setTask(Write a blog post);
$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(),
));
}
}

Tip: 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 predefiniti che verranno trattati a breve (see Tipi di campo predefiniti).
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 #}
<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 $


<?php echo $view[form]->widget($form) ?>
<input type="submit" />
</form>

2.1. Libro

145

Symfony2 documentation Documentation, Release 2

Note: 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 flessibile (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 Scrivi 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.
Tip: 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 pubblica, deve avere
un metodo getter e 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. Affinch
ci avvenga, i dati inviati dallutente devono essere associati al form. Aggiungere le seguenti funzionalit al
controllore:
// ...
public function newAction(Request $request)
{
// 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 nel database
return $this->redirect($this->generateUrl(task_success));
}
}

146

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// ...
}

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().
Note: 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 nel database) prima di rinviare lutente a
unaltra pagina (ad esempio una pagina thank you o success).
Note: 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
# 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()

2.1. Libro

147

Symfony2 documentation Documentation, Release 2

* @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());
$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 verifichino 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

Tip: Se non si usano i gruppi di validatori, possibile saltare questa sezione.

148

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Se il proprio oggetto si avvale dei gruppi di validatori, si avr bisogno di specificare 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.
Gruppi basati su dati inseriti

New in version 2.1: La possibilit di specificare un callback o una Closure in validation_groups stata
aggiunta nella versione 2.1 Se si ha bisogno di una logica avanzata per determinare i gruppi di validazione (p.e.
basandosi sui dati inseriti), si pu impostare lopzione validation_groups a un callback o a una Closure:

public function getDefaultOptions(array $options)


{
return array(
validation_groups => array(Acme\\AcmeBundle\\Entity\\Client, determineValidationGroup
);
}

Questo richiamer il metodo statico determineValidationGroups() della classe Client, dopo il bind
del form ma prima dellesecuzione della validazione. Loggetto Form passato come parametro del metodo
(vedere lesempio successivo). Si pu anche definire lintera logica con una Closure:
public function getDefaultOptions(array $options)
{
return array(
validation_groups => function(FormInterface $form) {
$data = $form->getData();
if (Entity\Client::TYPE_PERSON == $data->getType()) {
return array(person)
} else {
return array(company);
}
},
);
}

Tipi di campo predefiniti


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

149

Symfony2 documentation Documentation, Release 2

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
file
radio
Gruppi di campi

collection
repeated
Campi nascosti

hidden
csrf

150

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Campi di base

field
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 configurazione. Ad esempio, il campo
dueDate attualmente reso con 3 menu select. Tuttavia, il campo data pu essere configurato 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
specifiche 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
predefinita, lopzione required impostata a true e questo significa 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.

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:

2.1. Libro

151

Symfony2 documentation Documentation, Release 2

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.
Caution: Se il form utilizza un gruppo specifico 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.
Tip: 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).
max_length: Similmente a min_length, pu anche essere indovinata la lunghezza massima.
Note: 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 modificare 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 flessibilit:
Twig
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path(task_new) }}" method="post" {{ form_enctype(form) }}>

152

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

{{ 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 $


<?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 file, questo inserisce
lobbligatorio enctype="multipart/form-data";
form_errors(form) - Rende eventuali errori globali per lintero modulo (gli errori specifici 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 predefinita, 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 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 predefinita. Nella sezione Temi con i form, si
apprender come loutput di form_row possa essere personalizzato su diversi levelli.
Tip: 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 finale del codice che segue lo stesso di quando si
utilizzato lhelper form_row:

2.1. Libro

153

Symfony2 documentation Documentation, Release 2

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>
<?php echo $view[form]->rest($form) ?>

Se letichetta auto-generata di un campo non giusta, si pu specificarla 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 modificare 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

154

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

{{ 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:
// 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 identificatore 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);

2.1. Libro

155

Symfony2 documentation Documentation, Release 2

// ...
}

Porre la logica del form in una propria classe significa 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 sufficiente. Cos, anche se non sempre necessario, in genere una buona idea specificare
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,
);
}

Tip: 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 nel database interamente non correlato al tema dei form. Ma, se la classe Task stata configurata 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.

156

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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, sufficiente 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;
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 riflettere le nuove esigenze, creare una classe di form in modo che
loggetto Category possa essere modificato dallutente:
// src/Acme/TaskBundle/Form/Type/CategoryType.php
namespace Acme\TaskBundle\Form\Type;
use Symfony\Component\Form\AbstractType;

2.1. Libro

157

Symfony2 documentation Documentation, Release 2

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,
);
}
public function getName()
{
return category;
}
}

Lobiettivo finale quello di far si che la Category di un Task possa essere correttamente modificata 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 nel database o utilizzata quando serve.
158

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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.
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, modificare 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 file 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 file 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 definito 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

2.1. Libro

159

Symfony2 documentation Documentation, Release 2

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


<?php $view[form]->setTheme($form, array(AcmeTaskBundle:Form)) ?>
<form ...>

Il tag form_theme (in Twig) importa i frammenti definiti 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 predefinito 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 file 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.) definito in
un tema base, che in Twig una raccolta di blocchi e in PHP una collezione di file template.
In Twig, ogni blocco necessario definito in un singolo file template (form_div_layout.html.twig) che vive
allinterno di Twig Bridge. Dentro questo file, possibile ogni blocco necessario alla resa del form e ogni tipo
predefinito di campo.
In PHP, i frammenti sono file template individuali. Per impostazione predefinita sono posizionati nella cartella
Resources/views/Form del bundle framework (vedere su GitHub).
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 predefinita, 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)

Note: 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 predefiniti 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 (il tipo genitore di textarea field) e Symfony utilizza il frammento per il tipo del genitore se
il frammento di base non esiste.

160

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 predefiniti di tutti i campi, copiare e
personalizzare direttamente il frammento field_errors.
Tip: 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, modificare il file della configurazione 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 definire
loutput del form.

2.1. Libro

161

Symfony2 documentation Documentation, Release 2

Personalizzare tutti gli output del form in un singolo file 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 %}
{# import "_self" as the form theme #}
{% form_theme form _self %}
{# make the form fragment customization #}
{% block field_row %}
{# custom field row output #}
{% endblock field_row %}
{% block content %}
{# ... #}
{{ form_row(form.task) }}
{% endblock %}

Il tag {% form_theme form _self %} 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, modificare il
file con la configurazione 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(
templating => array(form =>
array(resources => array(
AcmeTaskBundle:Form,
)))

162

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// ...
));

Ogni frammento allinterno della cartella Acme/TaskBundle/Resources/views/Form ora usato globalmente per definire loutput del form.
Protezione da CSRF
CSRF, o 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 predefinita, Symfony integra e convalida i token CSRF automaticamente.
Questo significa 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 predefinita
_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 specificatamente 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 configurazione dei form.
Note: Lopzione intention opzionale, ma migliora notevolmente la sicurezza del token generato, rendendolo
diverso per ogni modulo.

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 finora 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

2.1. Libro

163

Symfony2 documentation Documentation, Release 2

// ...
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();
}
// ... render the form
}

Per impostazione predefinita, un form ipotizza che si voglia lavorare con array di dati, invece che con oggetti. Ci
sono due modi per modificare 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.
Tip: 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?
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

164

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

$form = $this->createFormBuilder(null, array(


validation_constraint => $collectionConstraint,
))->add(email, email)
// ...
;

Ora, richiamando $form->isValid(), i vincoli impostati sono eseguiti sui dati del form. Se si usa una classe form,
sovrascrivere il metodo getDefaultOptions per specificare 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 flessibilit 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 finali
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 modificare i dati. Il secondo obiettivo di
un form quello di prendere i dati inviati dallutente e ri-applicarli alloggetto.
Ci sono altre cose da imparare sul potente mondo dei form, ad esempio come gestire il caricamento di file 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 file con Doctrine
Riferimento del tipo di campo file
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. Libro

165

Symfony2 documentation Documentation, Release 2

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 identifica chi lutente, chiedendogli di presentare una sorta
di identificazione. Questultima chiamata autenticazione e significa 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 significa che il sistema verifica se
disponi dei privilegi per eseguire una certa azione.

Il modo migliore per imparare quello di vedere un esempio, vediamolo subito.


Note: Il componente della sicurezza 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 configurato attraverso la configurazione dellapplicazione. In realt, per
molte configurazioni standard di sicurezza basta solo usare la giusta configurazione. La seguente configurazione
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:

166

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

- { path: ^/admin, roles: ROLE_ADMIN }


providers:
in_memory:
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/
<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">
<memory>
<user name="ryan" password="ryanpass" roles="ROLE_USER" />
<user name="admin" password="kitten" roles="ROLE_ADMIN" />
</memory>
</provider>
<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(
memory => array(
users => array(
ryan => array(password => ryanpass, roles => ROLE_USER),
admin => array(password => kitten, roles => ROLE_ADMIN),

2.1. Libro

167

Symfony2 documentation Documentation, Release 2

),
),
),
),
encoders => array(
Symfony\Component\Security\Core\User\User => plaintext,
),
));

Tip: Una distribuzione standard di Symfony pone la configurazione di sicurezza in un file separato (ad esempio
app/config/security.yml). Se non si ha un file di sicurezza separato, possibile inserire la configurazione
direttamente nel file di configurazione principale (ad esempio app/config/config.yml).
Il risultato finale di questa configurazione 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 configurazione 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 specifica o URL.
Firewall (autenticazione)

Quando un utente effettua una richiesta a un URL che protetta da un firewall, viene attivato il sistema di sicurezza.
Il compito del firewall 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 firewall viene attivato quando lURL di una richiesta in arrivo corrisponde al valore pattern dellespressione
regolare del firewall configurato. In questo esempio, pattern (^/) corrisponder a ogni richiesta in arrivo. Il
fatto che il firewall venga attivato non significa 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.

168

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Questo funziona in primo luogo perch il firewall consente utenti anonimi, attraverso il parametro di configurazione anonymous. In altre parole, il firewall 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 firewall chieder sempre lautenticazione allutente.
Controlli sullaccesso (autorizzazione)

Se un utente richiede /admin/foo, il processo ha un diverso comportamento. Questo perch la sezione di


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

2.1. Libro

169

Symfony2 documentation Documentation, Release 2

Come prima, quando lutente effettua inizialmente la richiesta, il firewall non chiede nessuna identificazione.
Tuttavia, non appena il livello di controllo di accesso nega laccesso allutente (perch lutente anonimo non ha il
ruolo ROLE_ADMIN), il firewall 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 finestra 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.

170

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

In questo esempio, lutente ryan viene autenticato con successo con il firewall. Ma poich ryan non ha il ruolo
ROLE_ADMIN, viene ancora negato laccesso a /admin/foo. In definitiva, questo significa che lutente vedr
un qualche messaggio che indica che laccesso stato negato.
Tip: 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.
Infine, 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:

2.1. Libro

171

Symfony2 documentation Documentation, Release 2

Il flusso di richiesta quando un utente richiede una risorsa protetta semplice, ma incredibilmente flessibile. Come
si vedr in seguito, lautenticazione pu essere gestita in molti modi, come un form di login, un certificato X.509,
o da unautenticazione dellutente tramite Twitter. Indipendentemente dal metodo di autenticazione, il flusso 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 firewall autentica lutente;
5. Lutente autenticato riprova la richiesta originale.
Note: 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.

Tip: Pi avanti si imparer che in Symfony2 qualunque cosa pu essere protetto, tra cui controllori specifici,
oggetti, o anche metodi PHP.

Utilizzo di un form di login tradizionale


Finora, si visto come proteggere lapplicazione con un firewall e poi proteggere laccesso a determinate aree
tramite i ruoli. Utilizzando lautenticazione HTTP, si pu sfruttare senza fatica il box nativo nome utente/password

172

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

offerti da tutti i browser. Tuttavia, Symfony supporta nativamente molti meccanismi di autenticazione. Per i
dettagli su ciascuno di essi, vedere il Riferimento sulla configurazione 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 firewall:
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/
<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,
),
),
),
));

Tip: Se non necessario personalizzare i valori login_path o check_path (i valori usati qui sono i valori
predefiniti), possibile accorciare la configurazione:
YAML
form_login: ~

XML
<form-login />

PHP

2.1. Libro

173

Symfony2 documentation Documentation, Release 2

form_login => array(),

Ora, quando il sistema di sicurezza inizia il processo di autenticazione, rinvier lutente al form di login (/login
per impostazione predefinita). 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/r
<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;

Note: Non necessario implementare un controllore per lURL /login_check perch il firewall catturer ed
elaborer qualunque form inviato a questo URL.
New in version 2.1: A partire da Symfony 2.1, si devono avere rotte configurate per i propri URL login_path
(p.e. /login) e check_path (p.e. /login_check). Notare che il nome della rotta login non importante.
Quello che importante che lURL della rotta (/login) corrisponda al valore di configurazione 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;

174

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 verificati, ma il sistema di sicurezza stesso che si prende cura di verificare il nome utente e la
password inviati e di autenticare lutente.
Infine, 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" />
{#

Se si desidera controllare lURL a cui lutente viene rinviato in caso di successo (m


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

2.1. Libro

175

Symfony2 documentation Documentation, Release 2

<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 (m
<input type="hidden" name="_target_path" value="/account" />
-->
<input type="submit" name="login" />
</form>

Tip:
La
variabile
error
passata
nel
template

unistanza
di
Symfony\Component\Security\Core\Exception\AuthenticationException.
Potrebbe
contenere informazioni, anche sensibili, sullerrore di autenticazione: va quindi usata con cautela.
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 configurati).
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 firewall 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, verifica le credenziali inviate dallutente, autentica lutente se
sono corrette e, se non lo sono, lo rinvia al form di login.
Per impostazione predefinita, 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 specifico.
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.

176

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 definito correttamente le rotte /login e /login_check e che
corrispondano ai valori di configurazione login_path e check_path. Un errore di configurazione qui
pu significare 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 configurazione, 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 firewall non consente utenti anonimi, sar necessario creare un firewall 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>
<firewall name="secured_area" pattern="^/">
<form_login />
</firewall>

PHP
2.1. Libro
firewalls => array(
login_firewall => array(
pattern => ^/login$,

177

Symfony2 documentation Documentation, Release 2

Autorizzazione
Il primo passo per la sicurezza sempre lautenticazione: il processo di verificare 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 specifici 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 specifico di ruoli;
2. Una risorsa richiede un ruolo specifico 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 specifici 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 definire 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),
),
));

Tip: 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
178

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 prefisso ^/_internal per impostazione
predefinita nelledizione standard di Symfony (assumendo di aver scommentato queste linee dal file 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
),

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: htt

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_ch
),

2.1. Libro

179

Symfony2 documentation Documentation, Release 2

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. Se si sta utilizzando la distribuzione standard di Symfony, questo bundle disponibile per impostazione predefinita. 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 specifico.
Per ulteriori informazioni su come utilizzare il componente della sicurezza per proteggere servizi e metodi diversi
nellapplicazione, vedere Proteggere servizi e metodi di unapplicazione.
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 modificare i propri commenti, ma non quelli degli altri. Inoltre, come utente admin, si vuole essere
in grado di modificare 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 modificare 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).

180

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 file di configurazione, 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 file di configurazione e uno che carica gli utenti da una tabella di un database.
Definizione degli utenti in un file di configurazione Il modo pi semplice per specificare gli utenti direttamente in un file di configurazione. In effetti, questo si gi aver visto nellesempio di questo capitolo.
YAML
# app/config/security.yml
security:
# ...
providers:
default_provider:
memory:
users:
ryan: { password: ryanpass, roles: ROLE_USER }
admin: { password: kitten, roles: ROLE_ADMIN }

XML
<!-- app/config/security.xml -->
<config>
<!-- ... -->
<provider name="default_provider">
<memory>
<user name="ryan" password="ryanpass" roles="ROLE_USER" />
<user name="admin" password="kitten" roles="ROLE_ADMIN" />
</memory>
</provider>
</config>

PHP
// app/config/security.php
$container->loadFromExtension(security, array(
// ...
providers => array(
default_provider => array(
memory => array(
users => array(
ryan => array(password => ryanpass, roles => ROLE_USER),
admin => array(password => kitten, roles => ROLE_ADMIN),
),
),
),
),
));

2.1. Libro

181

Symfony2 documentation Documentation, Release 2

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).
Tip: Qualsiasi fornitore utenti pu caricare gli utenti direttamente dalla configurazione, specificando il parametro
di configurazione users ed elencando gli utenti sotto di esso.
Caution: 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 specificano 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 configurare. 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 configurando 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;
// ...
}

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 significa
che il concetto di utente pu essere qualsiasi cosa, purch implementi questa interfaccia. New in version 2.1.
Note: Loggetto utente verr serializzato e salvato nella sessione durante le richieste, quindi si consiglia di
implementare linterfaccia Serializable nel proprio oggetto utente. Ci particolarmente importante se la classe
User ha una classe genitore con propriet private.
Quindi, configurare 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

182

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

<!-- 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 => userna
),
),
));

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.
Note: 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.
Codificare la password dellutente

Finora, per semplicit, tutti gli esempi hanno memorizzato le password dellutente in formato testo (se tali utenti
sono memorizzati in un file di configurazione o in un qualche database). Naturalmente, in unapplicazione reale,
si consiglia per ragioni di sicurezza, di codificare le password degli utenti. Questo facilmente realizzabile
mappando la classe User in uno dei numerosi built-in encoder. Per esempio, per memorizzare gli utenti in
memoria, ma oscurare le lori password tramite sha1, fare come segue:
YAML

# app/config/security.yml
security:
# ...
providers:
in_memory:
memory:
users:
ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: ROLE
admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: ROLE
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">
<memory>
<user name="ryan" password="bb87a29949f3a1ee0559f8a57357487151281386" roles="ROLE

2.1. Libro

183

Symfony2 documentation Documentation, Release 2

<user name="admin" password="74913f5cd5f61ec0bcfdb775414c2fb3d161b620" roles="ROL


</memory>
</provider>

<encoder class="Symfony\Component\Security\Core\User\User" algorithm="sha1" iterations="1


</config>

PHP

// app/config/security.php
$container->loadFromExtension(security, array(
// ...
providers => array(
in_memory => array(
memory => array(
users => array(
ryan => array(password => bb87a29949f3a1ee0559f8a57357487151281386,
admin => array(password => 74913f5cd5f61ec0bcfdb775414c2fb3d161b620
),
),
),
),
encoders => array(
Symfony\Component\Security\Core\User\User => array(
algorithm
=> sha1,
iterations
=> 1,
encode_as_base64 => false,
),
),
));

Impostando le iterazioni a 1 e il encode_as_base64 a false, sufficiente eseguire una sola volta


lalgoritmo sha1 sulla password e senza alcuna codifica 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
Se si sta creando i propri utenti in modo dinamico (e memorizzarli in un database), possibile utilizzare algoritmi
di hash ancora pi complessi e poi contare su un oggetto encoder oggetto per aiutarti a codificare le password.
Per esempio, supponiamo che loggetto User sia Acme\UserBundle\Entity\User (come nellesempio
precedente). In primo, configurare lencoder per questo utente:
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(
// ...

184

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

encoders => array(


Acme\UserBundle\Entity\User => sha512,
),
));

In questo caso, si utilizza il pi forte algoritmo sha512. Inoltre, poich si semplicemente specificato lalgoritmo
(sha512) come stringa, il sistema per impostazione predefinita far lhash 5000 volte in un riga e poi la codificher in base64. In altre parole, la password stata notevolmente offuscata in modo che lhash della password
non pu essere decodificato (cio non possibile determinare la password dallhash della password).
Se si ha una qualche 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 configurato per
loggetto User, la password con hash pu essere determinata nel seguente modo da 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();
}

In un controllore, si pu usare una scorciatoia:


public function indexAction()
{
$user = $this->getUser();
}

Note: 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,
verificare il ruolo IS_AUTHENTICATED_FULLY.
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 predefinita, user il primo fornitore dichiarato. Ma cosa succede se si
desidera specificare alcuni utenti tramite configurazione e il resto degli utenti nel database? Questo possibile
attraverso la creazione di un nuovo fornitore, che li unisca:
YAML

2.1. Libro

185

Symfony2 documentation Documentation, Release 2

# app/config/security.yml
security:
providers:
chain_provider:
chain:
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">
<chain>
<provider>in_memory</provider>
<provider>user_db</provider>
</chain>
</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(
chain => array(
providers => array(in_memory, user_db),
),
),
in_memory => array(
users => array(
foo => array(password => test),
),
),
user_db => array(
entity => array(class => Acme\UserBundle\Entity\User, property => userna
),
),
));

Ora, tutti i meccanismi di autenticazione utilizzeranno il chain_provider, dal momento che il primo specificato. Il chain_provider, a sua volta, tenta di caricare lutente da entrambi i fornitori in_memory e
user_db.
Tip: Se no 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:

186

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

main_provider:
memory:
users:
foo: { password: test }
entity:
class: Acme\UserBundle\Entity\User,
property: username

XML
<!-- app/config/security.xml -->
<config>
<provider name=="main_provider">
<memory>
<user name="foo" password="test" />
</memory>
<entity class="Acme\UserBundle\Entity\User" property="username" />
</provider>
</config>

PHP

// app/config/security.php
$container->loadFromExtension(security, array(
providers => array(
main_provider => array(
memory => array(
users => array(
foo => array(password => test),
),
),
entity => array(class => Acme\UserBundle\Entity\User, property => userna
),
),
));

anche possibile configurare il firewall o meccanismi di autenticazione individuali per utilizzare un provider
specifico. Ancora una volta, a meno che un provider sia specificato 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"
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>

2.1. Libro

187

Symfony2 documentation Documentation, Release 2

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 predefinita per il firewall).
Per ulteriori informazioni su fornitori utenti e configurazione del firewall, vedere il Riferimento configurazione
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 definito ovunque, sufficiente iniziare a usarlo.
Note: Tutti i ruoli devono iniziare con il prefisso ROLE_ per poter essere gestiti da Symfony2. Se si definiscono
i propri ruoli con una classe Role dedicata (caratteristica avanzata), non bisogna usare il prefisso ROLE_.

I ruoli gerarchici

Invece di associare molti ruoli agli utenti, possibile definire 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

188

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// 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 configurazione 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 firewall pu gestire
automaticamente questo caso quando si attiva il parametro di configurazione 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="/" />
</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 configurato sotto il firewall, 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 definito
dal parametro target). Entrambi i parametri di configurazione path e target assumono come impostazione
predefinita ci che specificato qui. In altre parole, se non necessario personalizzarli, possibile ometterli
completamente e accorciare la configurazione:
YAML
logout: ~

2.1. Libro

189

Symfony2 documentation Documentation, Release 2

XML
<logout />

PHP
logout => array(),

Si noti che non necessario implementare un controllore per lURL /logout, perch il firewall 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/r
<route id="logout" pattern="/logout" />
</routes>

PHP
// 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 definito dal parametro target sopra (ad
esempio, la homepage). Per ulteriori informazioni sulla configurazione di logout, vedere il Riferimento della
configurazione 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; ?>

Note: Se si utilizza questa funzione e non si in un URL dove c un firewall attivo, viene lanciata uneccezione.
Anche in questo caso, quasi sempre una buona idea avere un firewall principale che copra tutti gli URL (come
si visto in questo capitolo).
190

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Verifica dellaccesso nei controllori


Quando si vuole verificare 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
}

Note: Un firewall deve essere attivo o verr lanciata uneccezione quando viene chiamato il metodo isGranted.
Vedere la nota precedente sui template per maggiori dettagli.

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 firewall:
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:
2.1. Libro

191

Symfony2 documentation Documentation, Release 2

http://example.com/indirizzo?_switch_user=_exit
Naturalmente, questa funzionalit deve essere messa a disposizione di un piccolo gruppo di utenti. Per impostazione predefinita, laccesso limitato agli utenti che hanno il ruolo ROLE_ALLOWED_TO_SWITCH. Il nome
di questo ruolo pu essere modificato tramite limpostazione role. Per maggiore sicurezza, anche possibile
modificare 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
<!-- 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_u
),
),
));

Autenticazione senza stato


Per impostazione predefinita, Symfony2 si basa su un cookie (Session) per persistere il contesto di sicurezza
dellutente. Ma se si utilizzano certificati 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 significa 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>

192

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(http_basic => array(), stateless => true),
),
));

Note: Se si usa un form di login, Symfony2 creer un cookie anche se si imposta stateless a true.

Considerazioni finali
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 firewall 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 specifica 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 efficiente possa essere la propria applicazione, ogni
richiesta conterr sempre overhead rispetto a quando si serve un file statico.
Per la maggior parte delle applicazione, 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 prefigge la cache.
La cache sulle spalle dei giganti
Il modo pi efficace 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,
definita nelle specifiche HTTP. Invence di inventare un altro metodo di cache, Symfony2 abbraccia lo standard che

2.1. Libro

193

Symfony2 documentation Documentation, Release 2

definisce 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.
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 predefinite appropriate e una potente
interfaccia per interagire con gli header di cache.
Passo 3: La scadenza e 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. Unaltra risorsa
importante il Cache Tutorial 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
richiesta-risposta 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, Squid in modalit
reverse proxy 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 traffico 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, affidabili e performanti.
Tip: Le gateway cache sono a volte chiamate reverse proxy cache, cache surrogate o anche acceleratori HTTP.

Note: I significati di cache privata e condivisa saranno pi chiari quando si parler di mettere in cache risposte
che contengono contenuti specifici per un singolo utente (p.e. informazioni sullaccount).

194

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 configurata in AppCache, che estende AppKernel. Il kernel della
cache il reverse proxy.
Per abilitare la cache, modificare 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.
Tip: 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 verifica della
strategia di cache:
error_log($kernel->getLog());

Loggetto AppCache una una configurazione predefinita 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,

Tip: A meno che non sia sovrascritta in getOptions(), lopzione debug sar impostata automaticamente al
valore di debug di AppKernel circostante.

2.1. Libro

195

Symfony2 documentation Documentation, Release 2

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 (predefinito: 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. (predefinito: Authorization e Cookie);
allow_reload: Specifica 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 (predefinito:
false);
allow_revalidate: Specifica 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
(predefinito: false);
stale_while_revalidate: Specifica il numero predefinito 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 (predefinito: 2); questa impostazione
sovrascritta dallestensione stale-while-revalidate Cache-Control di HTTP (vedere RFC
5861);
stale_if_error: Specifica il numero predefinito di secondi (la granularit il secondo) durante il
quale la cache pu servire una risposta vecchia quando si incontra un errore (predefinito: 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 modifica al codice della propria
applicazione. Si pu iniziare semplicemente con il reverse proxy di Symfony2 e aggiornare successivamente
a Varnish, quando il traffico aumenta.
Per maggiori informazioni sulluso di Varnish con Symfony2, vedere la ricetta Usare Varnish.

Note: 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.

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.
Tip: 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 specifica quattro header di cache per la risposta di cui ci occupiamo:
196

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Cache-Control
Expires
ETag
Last-Modified
Lheader pi importante e versatile lheader Cache-Control, che in realt un insieme di varie informazioni
sulla cache.
Note: 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 specifica 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!
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 predefinita 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:

2.1. Libro

197

Symfony2 documentation Documentation, Release 2

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 modificarla.
Regole e valori predefiniti della cache

HTTP 1.1 consente per impostazione predefinita 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 specifiche HTTP definiscono due modelli di cache:
Con il modello a scadenza, si specifica 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 finch 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 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. Lapplicazione usa un identificatore univoco
per la risposta (lheader Etag) e/o un timestamp (come lheader Last-Modified) per verificare 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 specifiche HTTP
Le specifiche HTTP definiscono un linguaggio semplice, ma potente, in cui client e server possono comunicare. Come sviluppatori web, il modello richiesta-risposta delle specifiche domina il nostro lavoro.
Sfortunatamente, il documento delle specifiche, la RFC 2616, pu risultare di difficile lettura.
C uno sforzo in atto (HTTP Bis) per riscrivere la RFC 2616. Non descrive una nuova versione di HTTP, ma
per lo pi chiarisce le specifiche HTTP originali. Anche lorganizzazione migliore, essendo le specifiche
separate in sette parti; tutto ci che riguarda la cache HTTP si trova in due parti dedicate (P4 - Richieste
condizionali e P6 - Cache: Browser e cache intermedie).
Come sviluppatori web, dovremmo leggere tutti le specifiche. Possiedono un chiarezza e una potenza, anche
dopo oltre dieci anni dalla creazione, inestimabili. Non ci si spaventi dalle apparenze delle specifiche, il
contenuto molto pi bello della copertina.

198

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Scadenza

Il modello a scadenza il pi efficiente 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, finch 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 specifiche 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

Note: Il metodo setExpires() converte automaticamente la data al fuso orario GMT, come richiesto dalle
specifiche.
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 specifiche stabiliscono che i server HTTP/1.1 non dovrebbero inviare header
Expires oltre un anno nel futuro.
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 specificare 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 modifica, il modello a scadenza non raggiunge lo scopo. Con il modello a scadenza, allapplicazione non sar chiesto di restituire
la risposta aggiornata, finch la cache non diventa vecchia.

2.1. Libro

199

Symfony2 documentation Documentation, Release 2

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).
Tip: Il codice di stato 304 significa non modificato. 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 identifica 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);
$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.
Tip:
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 specifiche HTTP, lheader


Last-Modified indica la data e lora in cui il server di origine crede che la rappresentazione sia stata modificata lultima volta. In altre parole, lapplicazione decide se il contenuto in cache sia stato modificato o meno, in
base al fatto se sia stato aggiornato o meno da quando la risposta stata messa in cache.

200

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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.
Note: 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.

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 efficiente:
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
);
}

2.1. Libro

201

Symfony2 documentation Documentation, Release 2

Quando la Response non stata modificata, isNotModified() imposta automaticamente il codice di stato
della risposta a 304, rimuove il contenuto e rimuove alcuni header che no devono essere presenti in una risposta
304 (vedere :method:Symfony\\Component\\HttpFoundation\\Response::setNotModified).
Variare la risposta

Finora abbiamo ipotizzato che ogni URI avesse esattamente una singola rappresentazione della risorsa interessata.
Per impostazione predefinita, 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:
Vary: Accept-Encoding, User-Agent

Tip: 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 beneficiare 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 verificare 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():

202

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// 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, o Edge Side Includes. Akama ha scritto le specifiche quasi dieci anni fa,
consentendo a determinate parti di una pagina di avere differenti strategie di cache rispetto alla pagina principale.
Le specifiche 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>

Note: Si noti nellesempio che ogni tag ESI ha un URL pienamente qualificato. 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 finale 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 configurazione dellapplicazione:
YAML
# app/config/config.yml
framework:
# ...
esi: { enabled: true }

XML
<!-- app/config/config.xml -->
<framework:config ...>
<!-- ... -->

2.1. Libro

203

Symfony2 documentation Documentation, Release 2

<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.
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 configurare 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 predefinito), 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.
Note: Symfony2 individua se una gateway cache supporta ESI tramite unaltra specifica di Akama, che
supportata nativamente dal reverse proxy di Symfony2.
Lazione inclusa ora pu specificare 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
204

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 definire la rotta _internal:
YAML
# app/config/routing.yml
_internal:
resource: "@FrameworkBundle/Resources/config/routing/internal.xml"
prefix:
/_internal

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/r

<import resource="@FrameworkBundle/Resources/config/routing/internal.xml" prefix="/_inter


</routes>

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

$collection->addCollection($loader->import(@FrameworkBundle/Resources/config/routing/interna
return $collection;

Tip: Poich questa rotta consente laccesso a tutte le azioni tramite URL, si potrebbe volerla proteggere usando il
firewall 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.
Note: 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 specificare 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 difficili 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
definizione; 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.
2.1. Libro

205

Symfony2 documentation Documentation, Release 2

Note:
Essendo linvalidazione un argomento specifico di ogni reverse proxy, se non ci si preoccupa
dellinvalidazione, si pu cambiare reverse proxy senza cambiare alcuna parte del codice della propria applicazione.
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 configurare 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;
}
}

Caution: 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 efficace. 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 specifici
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 significa 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
echo $translator->trans(Hello World);

206

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Note: 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 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 configurare 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 dellutente per la richiesta e, facoltativamente,
sullintera sessione.
Configurazione
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 configurazione:
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 definisce il locale da utilizzare quando una traduzione non esiste nel locale dellutente.
Tip: 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 richiesta. Tipicamente, impostato tramite un attributo
_locale in una rotta (vedere Il locale e gli URL).
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

207

Symfony2 documentation Documentation, Release 2

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 richiesta (o nella sessione, come
_locale);
Un catalogo di messaggi tradotti viene caricato dalle risorse di traduzione definite per il locale (ad es.
fr_FR). Vengono anche caricati i messaggi dal locale predefinito e aggiunti al catalogo, se non esistono
gi. Il risultato finale 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:

208

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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%

Note: Il segnaposto pu assumere qualsiasi forma visto che il messaggio ricostruito utilizzando la funzione
strtr 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 definiscono le traduzioni per ogni diverso locale.

2.1. Libro

209

Symfony2 documentation Documentation, Release 2

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 specifico 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 filesystem e vengono trovate da Symfony grazie ad alcune convenzioni.
Tip: 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 file dei messaggi (ad esempio le traduzioni) in due sedi:
Per i messaggi trovati in un bundle, i corrispondenti file con i messaggi dovrebbero trovarsi nella cartella
Resources/translations/ del bundle;
Per sovrascrivere eventuali traduzioni del bundle, posizionare i file con i messaggi nella cartella
app/Resources/translations.
importante anche il nome del file con le traduzioni, perch Symfony2 utilizza una convenzione per determinare i dettagli sulle traduzioni. Ogni file 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 predefinito 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 file (ad esempio xliff, php o yml).
Il caricatore pu essere il nome di un qualunque caricatore registrato. Per impostazione predefinita, Symfony
fornisce i seguenti caricatori:
xliff: file XLIFF;
php: file PHP;
yml: file YAML.
La scelta di quali caricatori utilizzare interamente a carico dello sviluppatore ed una questione di gusti.
Note:
anche possibile memorizzare le traduzioni in una base dati o in qualsiasi
altro
mezzo,
fornendo
una
classe
personalizzata
che
implementa
linterfaccia
Symfony\Component\Translation\Loader\LoaderInterface.
Vedere Caricatori per
le traduzioni personalizzati di seguito per imparare a registrare caricatori personalizzati.

Creazione delle traduzioni

La creazione di file di traduzione una parte importante della localizzazione (spesso abbreviata in L10n). Ogni
file costituito da una serie di coppie id-traduzione per il dato dominio e locale. Lid lidentificativo di una
traduzione individuale e pu essere il messaggio nel locale principale (ad es. Symfony is great) dellapplicazione
o un identificatore univoci (ad es. symfony2.great - vedere la barra laterale di seguito):
XML

210

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

<!-- 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 file 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

211

Symfony2 documentation Documentation, Release 2

Utilizzare messaggi reali o parole chiave


Questo esempio mostra le due diverse filosofie 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 predefinito (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 predefinito (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 file di
traduzione se decidiamo che il messaggio debba essere modificato in Symfony2 is really great nel locale
predefinito.
La scelta del metodo da utilizzare personale, ma il formato chiave spesso raccomandato.
Inoltre, i formati di file php e yaml supportano gli id nidificati, 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,
);

212

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Uso dei domini per i messaggi


Come abbiamo visto, i file dei messaggi sono organizzati nei diversi locale che vanno a tradurre. I file dei messaggi
possono anche essere organizzati in domini. Quando si creano i file dei messaggi, il dominio la prima parte del
nome del file. Il dominio predefinito 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 file per i messaggi:
messages.fr.xliff
admin.fr.xliff
navigation.fr.xliff
Quando si traducono stringhe che non sono nel dominio predefinito (messages), necessario specificare 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 richiesta ed accessibile tramite loggetto request:
// accesso alloggetto requesta in un controllore
$request = $this->getRequest();
$locale = $request->getLocale();
$request->setLocale(en_US);

anche possibile memorizzare il locale in sessione, invece che in ogni richiesta. Se lo si fa, ogni richiesta
successiva avr lo stesso locale.
$this->get(session)->set(_locale, en_US);

Vedere la sezione .. _book-translation-locale-url: sotto, sullimpostazione del locale tramite rotte.


Fallback e locale predefinito

Se il locale non stato impostato in modo esplicito nella sessione, sar utilizzato dal Translator il parametro
di configurazione fallback_locale. Il valore predefinito del parametro en (vedere Configurazione).
In alternativa, possibile garantire che un locale impostato sulla sessione dellutente definendo un
default_locale per il servizio di sessione:
YAML
# app/config/config.yml
framework:
default_locale: en

XML
<!-- app/config/config.xml -->
<framework:config>
<framework:default-locale>en</framework:default-locale>
</framework:config>

PHP

2.1. Libro

213

Symfony2 documentation Documentation, Release 2

// app/config/config.php
$container->loadFromExtension(framework, array(
default_locale => en,
));

New in version 2.1.


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 difficile, perch le regole possono essere complesse. Per
esempio, questa la rappresentazione matematica delle regole di pluralizzazione russe:

214

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

(($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.
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 influenzano 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 specifici (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

Gli intervalli seguono la notazione ISO 31-11. La suddetta stringa specifica 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 specifico, le regole standard hanno effetto dopo aver rimosso le regole esplicite:

2.1. Libro

215

Symfony2 documentation Documentation, Release 2

{0} There is no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %count%

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 finito 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 linfinito.
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 specifici 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%.
Tip: Se in una stringa necessario usare il carattere percentuale (%), escapizzarlo raddoppiandolo: {% trans
%}Percent: %percent%%%{% endtrans %}
inoltre possibile specificare 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 filtri trans e transchoice possono essere usati per tradurre variabili di testo ed espressioni complesse:
{{ message|trans }}
{{ message|transchoice(5) }}
{{ message|trans({%name%: Fabien}, "app") }}
{{ message|transchoice(5, {%name%: Fabien}, app) }}

216

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Tip: Utilizzare i tag di traduzione o i filtri ha lo stesso effetto, ma con una sottile differenza: lescape automatico
delloutput applicato solo alle variabili tradotte utilizzando un filtro. In altre parole, se necessario essere sicuri
che la variabile tradotta non venga escapizzata, necessario applicare il filtro raw dopo il filtro 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 specificare 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,
);

Tradurre contenuti da un database


La traduzione del contenuto di un database dovrebbero essere gestite da Doctrine attraverso lEstensione Translatable. Per maggiori informazioni, vedere la documentazione di questa libreria.

2.1. Libro

217

Symfony2 documentation Documentation, Release 2

Riassunto
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\\Trans
Tradurre ogni messaggio in pi locale creando dei file con i messaggi per la traduzione. Symfony2 scopre
ed elabora ogni file perch i suoi nomi seguono una specifica convenzione;
Gestire il locale dellutente, che memorizzato nella richiesta, ma pu anche essere 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 un database. 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, configurare e
usare qualsiasi oggetto in Symfony2. In gran parte, il contenitore dei servizi il pi grande contributore riguardo
la velocit e lestensibilit di Symfony2.
Infine, la configurazione e lutilizzo del contenitore di servizi semplice. Entro la fine 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 specifico
(ad esempio spedire email). Ogni servizio utilizzato in tutta lapplicazione ogni volta che si ha bisogno delle
funzionalit specifiche che fornisce. Non bisogna fare nulla di speciale per creare un servizio: sufficiente scrivere
una classe PHP con del codice che realizza un compito specifico. Congratulazioni, si appena creato un servizio!
Note: Come regola generale, un oggetto PHP u nservizio 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 un database un servizio.
Qual il discorso allora? Il vantaggio dei servizi che si comincia a pensare di semparare 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 configurato essendo separato dalle altre funzionalit dellapplicazione.
Questa idea si chiama architettura orientata ai servizi e non riguarda solo Symfony2 o il PHP. Strutturare la propria
applicazione con una serie di indipendenti classi di servizi una nota best-practice della programmazione a oggetti.
Queste conoscenze sono fondamentali per essere un buon sviluppatore in quasi tutti i linguaggi.

218

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 configurare 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 configurazione 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/Configurare servizi nel contenitore
Una soluzione migliore quella di lasciare che il contenitore di servizi crei loggetto Mailer per noi. Affinch
questo funzioni, bisogna insegnare al contenitore come creare il servizio Mailer. Questo viene fatto tramite la
configurazione, che pu essere specificata 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(
Acme\HelloBundle\Mailer,
array(sendmail)
));

Note: Durante linizializzazione di Symfony2, viene costruito il contenitore di servizi utilizzando la configurazione dellapplicazione (per impostazione predefinita app/config/config.yml). Il file esatto che
viene caricato indicato dal metodo AppKernel::registerContainerConfiguration(), che carica un file di configurazione specifico 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():
2.1. Libro

219

Symfony2 documentation Documentation, Release 2

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 significa che un servizio
non mai costruito fino a che non ce n bisogno. Se si definisce 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 significa anche che c un calo di prestazioni basso o inesistente quando si definiscono 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 flessibile e potente),
ma si imparer pi avanti come configurare 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 definire servizi pi organizzati e flessibili:
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
<!-- 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(

220

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

%my_mailer.class%,
array(%my_mailer.transport%)
));

Il risultato finale esattamente lo stesso di prima, la differenza solo nel come stato definito 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 definizione del servizio.
Lo scopo dei parametri quello di inserire informazioni dei servizi. Naturalmente non c nulla di sbagliato a
definire 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 definizioni 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 utilizzeranno sempre
perch rendono i servizi memorizzati nel contenitore pi configurabili. Per i servizi della propria applicazione,
tuttavia, potrebbe non essere necessaria la flessibilit 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:
- 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>

2.1. Libro

221

Symfony2 documentation Documentation, Release 2

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 configurazione del contenitore

Tip: In questa sezione, si far riferimento ai file di configurazione del servizio come risorse. Questo per sottolineare il fatto che, mentre la maggior parte delle risorse di configurazione saranno file (ad esempio YAML, XML,
PHP), Symfony2 cos flessibile che la configurazione 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 configurazione (per impostazione predefinita
app/config/config.yml). Tutte le altre configurazioni di servizi (comprese le configurazioni del nucleo
di Symfony2 e dei bundle di terze parti) devono essere importate da dentro questo file in un modo o nellaltro.
Questo d una assoluta flessibilit sui servizi dellapplicazione.
La configurazione 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 flessibile e privilegiato per importare la configurazione di servizi in bundle di terze parti.
Importare la configurazione con imports

Finora, si messo la definizione di contenitore del servizio my_mailer direttamente nel file di configurazione
dellapplicazione (ad esempio app/config/config.yml). Naturalmente, poich la classe Mailer stessa
vive allinterno di AcmeHelloBundle, ha pi senso mettere la definizione my_mailer del contenitore dentro
il bundle stesso.
In primo luogo, spostare la definizione my_mailer del contenitore, in un nuovo file 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>

222

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

</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 definizione, solo la sua posizione. Naturalmente il servizio contenitore non conosce il nuovo
file di risorse. Fortunatamente, si pu facilmente importare il file risorse utilizzando la chiave imports nella
configurazione dellapplicazione.
YAML
# app/config/config.yml
imports:
- { resource: @AcmeHelloBundle/Resources/config/services.yml }

XML
<!-- 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 configurazione per il contenitore di servizi
da qualsiasi altro posto (in genere da bundle). La locazione resource, per i file, il percorso assoluto al file
risorse. La speciale sintassi @AcmeHello risolve il percorso della cartella del bundle AcmeHelloBundle.
Questo aiuta a specificare il percorso alla risorsa senza preoccuparsi in seguito, se si sposta AcmeHelloBundle
in una cartella diversa.
Importare la configurazione attraverso estensioni del contenitore

Quando si sviluppa in Symfony2, si usa spesso la direttiva imports per importare la configurazione del contenitore dai bundle che sono stati creati appositamente per lapplicazione. Le configurazioni dei contenitori di bundle
di terze parti, includendo i servizi del nucleo di Symfony2, di solito sono caricati utilizzando un altro metodo che
pi flessibile e facile da configurare nellapplicazione.
Ecco come funziona. Internamente, ogni bundle definisce i propri servizi in modo molto simile a come si visto
finora. Un bundle utilizza uno o pi file di configurazione delle risorse (di solito XML) per specificare i parametri
e i servizi del bundle. Tuttavia, invece di importare ciascuna di queste risorse direttamente dalla configurazione
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 configurare i servizi per il bundle;
fornire una semplice configurazione semantica in modo che il bundle possa essere configurato senza interagire con i parametri piatti della configurazione del contenitore dei servizi del bundle.

2.1. Libro

223

Symfony2 documentation Documentation, Release 2

In altre parole, una estensione dei contenitore dei servizi configura i servizi per il bundle per voi. E, come si vedr
tra poco, lestensione fornisce una interfaccia sensibile e ad alto livello per configurare il bundle.
Si prenda il FrameworkBundle, il bundle del nucleo del framework Symfony2, come esempio. La presenza del
seguente codice nella configurazione 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 />
<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 configurazione, il contenitore cerca unestensione che sia in grado di gestire la direttiva
di configurazione framework. Lestensione in questione, che vive nel FrameworkBundle, viene invocata
e la configurazione del servizio per il FrameworkBundle viene caricata. Se si rimuove del tutto la chiave
framework dal file di configurazione 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 definiti.
In questo caso, lestensione consente di personalizzare la configurazione di charset, error_handler,
csrf_protection, router e di molte altre. Internamente, il FrameworkBundle usa le opzioni qui
specificate per definire e configurare i servizi a esso specifici. 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 configurazione. Come bonus aggiuntivo, la maggior parte delle estensioni dei contenitori di servizi
sono anche sufficientemente intelligenti da eseguire la validazione - notificando le opzioni mancanti o con un tipo
di dato sbagliato.
Durante linstallazione o la configurazione di un bundle, consultare la documentazione del bundle per per vedere
come devono essere installati e configurati i servizi per il bundle. Le opzioni disponibili per i bundle del nucleo si
possono trovare allinterno della Guida di riferimento.

224

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Note: Nativamente, il contenitore dei servizi riconosce solo le direttive parameters, services e imports.
Ogni altra direttiva gestita dallestensione del contenitore dei servizi.

Referenziare (iniettare) servizi


Finora, il servizio my_mailer semplice: accetta un solo parametro nel suo costruttore, che facilmente configurabile. 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:
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 modifiche. 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

2.1. Libro

225

Symfony2 documentation Documentation, Release 2

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->


<parameters>
<!-- ... -->
<parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</
</parameters>
<services>
<service id="my_mailer" ... >
<!-- ... -->
</service>
<service id="newsletter_manager" class="%newsletter_manager.class%">
<argument type="service" id="my_mailer"/>
</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\NewsletterM
$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 specificato
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 definite. In questo esempio, il servizio newsletter_manager ha bisogno del servizio
my_mailer per poter funzionare. Quando si definisce 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. Significa 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;
}
// ...
}

226

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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
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</
</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\NewsletterM
$container->setDefinition(my_mailer, ... );
$container->setDefinition(newsletter_manager, new Definition(
%newsletter_manager.class%
))->addMethodCall(setMailer, array(
new Reference(my_mailer)
));

Note: 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 significa che la dipendenza non richiesta
al fine di fare funzionare correttamente il servizio. Nellesempio precedente, il servizio my_mailer deve esistere, altrimenti verr lanciata uneccezione. Modificando la definizione del servizio newsletter_manager,
possibile rendere questo riferimento opzionale. Il contenitore inietter se esiste e in caso contrario non far nulla:

2.1. Libro

227

Symfony2 documentation Documentation, Release 2

YAML
# 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\NewsletterM
$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 configurano 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 predefinita non richiede che i controllori siano definiti 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)
{
$session = $this->get(session);
$session->set(foo, $bar);

228

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// ...
}

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 modificare NewsletterManager per usare il reale servizio
mailer di Symfony2 (al posto del finto 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 configurazione 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)
)
));

Il servizio newsletter_manager ora ha accesso ai servizi del nucleo mailer e templating. Questo
un modo comune per creare servizi specifici allapplicazione, in grado di sfruttare la potenza di numerosi servizi
presenti nel framework.
Tip: Assicurarsi che la voce swiftmailer appaia nella configurazione dellapplicazione. Come stato accennato in Importare la configurazione attraverso estensioni del contenitore, la chiave swiftmailer invoca
lestensione del servizio da SwiftmailerBundle, il quale registra il servizio mailer.
2.1. Libro

229

Symfony2 documentation Documentation, Release 2

Configurazioni avanzate del contenitore


Come si visto, definire servizi allinterno del contenitore semplice, in genere si ha bisogno della chiave di
configurazione service e di alcuni parametri. Tuttavia, il contenitore ha diversi altri strumenti disponibili che
aiutano ad aggiungere servizi per funzionalit specifiche, creare servizi pi complessi ed eseguire operazioni dopo
che il contenitore stato costruito.
Contrassegnare i servizi come pubblici / privati

Quando si definiscono i servizi, solitamente si vuole essere in grado di accedere a queste definizioni 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
definito solamente perch potrebbe essere usato come parametro per un altro servizio.
Note:
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);

Tuttavia, se un servizio stato contrassegnato come privato, si pu ancora farne lalias (vedere sotto) per accedere
a questo servizio (attraverso lalias).
Note: I servizi per impostazione predefinita sono pubblici.

230

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 significa 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 file

Ci potrebbero essere casi duso in cui necessario includere un altro file 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/path/to/file/foo.php

XML
<service id="foo" class="Acme\HelloBundle\Foo\Bar">
<file>%kernel.root_dir%/src/path/to/file/foo.php</file>
</service>

PHP
$definition = new Definition(Acme\HelloBundle\Foo\Bar);
$definition->setFile(%kernel.root_dir%/src/path/to/file/foo.php);
$container->setDefinition(foo, $definition);

Notare che symfony chiamer internamente la funzione PHP require_once il che significa che il file 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, i servizi
configurati nel contenitore possono anche loro essere etichettati. Nel contenitore dei servizi, un tag implica che si
2.1. Libro

231

Symfony2 documentation Documentation, Release 2

intende utilizzare il servizio per uno scopo specifico. 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 configurazione. Dando al
servizio il tag twig.extension, il bundle sa che il servizio foo.twig.extension dovrebbe essere registrato come estensione Twig con 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.filter
assetic.templating.php
data_collector
form.field_factory.guesser
kernel.cache_warmer
kernel.event_listener
monolog.logger
routing.loader
security.listener.factory
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
Definire i controllori come servizi

232

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

2.1.16 Prestazioni
Symfony2 veloce, senza alcuna modifica. 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 disponibili, alcune delle quali open source. La pi usata probabilmente
APC.
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 file sorgente. Questo assicura che, se la sorgente del
file 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 modifica dei file sorgente. Altrimenti, gli aggiornamenti eseguiti non saranno mostrati.
Per esempio, per disabilitare questi controlli in APC, aggiungere semplicemente apc.stat=0 al proprio file di
configurazione php.ini.
Usare un autoloader con caches (p.e. ApcUniversalClassLoader)
Per impostazione predefinita, Symfony2 standard edition usa UniversalClassLoader nel file autoloader.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 configurati per trovare un
particolare file, richiamando file_exists finch non trova il file cercato.
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 file 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);
// ...

Note: 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 prefisso, occorrer pulire la cache di APC. Altrimenti, lautoloader cercher ancora la
classe nella vecchia posizione per tutte le classi in quello spazio dei nomi.

2.1. Libro

233

Symfony2 documentation Documentation, Release 2

Usare i file di avvio


Per assicurare massima flessibilit 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 file a ogni richiesta pu risultate in
un overhead. Per ridurre tale overhead, Symfony2 Standard Edition fornisce uno script per generare i cosiddetti
file di avvio, che consistono in definizioni di molte classi in un singolo file. Includendo questo file (che contiene
una copia di molte classi del nucleo), Symfony non avr pi bisogno di includere alcuno dei file 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 file di avvio. Per assicurarsene, aprire il proprio
front controller (solitamente app.php) e verificare che sia presente la seguente riga:
require_once __DIR__./../app/bootstrap.php.cache;

Si noti che ci sono due svantaggi nelluso di un file di avvio:


il file 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 file di avvio.
Se si usa Symfony2 Standard Edition, il file 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 file di avvio, perch ci saranno
meno file 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 file 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.
Note: 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.
Tip:
Lauto-caricamento non viene gestito direttamente dal framework, ma indipendentemente,
con laiuto della classe Symfony\Component\ClassLoader\UniversalClassLoader e del file
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;

234

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 configurabilit ed estensibilit, grazie al componente Dependency Injection e a
un potente sistema di plugin (bundle).
See Also:
Approfondimento sul componente HttpKernel. 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 configurazione predefinita
adeguata e di convenzioni che facilitano la curva di apprendimento.
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 di
Symfony\Component\HttpKernel\Controller\ControllerResolverInterface:
public function getController(Request $request);
public function getArguments(Request $request, $controller);

Il metodo :method:Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController
restituisce il controllore (una funzione PHP) associato alla Request data. Limplementazionoe predefinita
(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).

2.1. Libro

235

Symfony2 documentation Documentation, Release 2

Tip: Limplementazione predefinita usa Symfony\Bundle\FrameworkBundle\EventListener\RouterListener


per definire 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 predefinita 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 definito, viene usato il valore del parametro predefinito, se specificato:
// 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 notifiche di eventi (vedere la prossima sezione
per maggiori informazioni sugli oggetti Event):
1. Prima di tutto, viene notificato 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 verifica 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 notificato levento kernel.exception e gli ascoltatori possono convertire leccezione in una risposta. Se funziona, viene notificato 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.

236

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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:
use Symfony\Component\HttpKernel\HttpKernelInterface;
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
// restituire immediatamente
return;
}

Tip: 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\FilterControllerEve


Questo evento non usato da FrameworkBundle, ma pu essere un punto di ingresso usato per modificare 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);
}

2.1. Libro

237

Symfony2 documentation Documentation, Release 2

Evento kernel.view Classe evento: Symfony\Component\HttpKernel\Event\GetResponseForControllerR


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);
}

Evento kernel.response Classe evento: Symfony\Component\HttpKernel\Event\FilterResponseEvent


Lo scopo di questo evento di consentire ad altri sistemi di modificare 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:
inserisce la barra di web debug;
Symfony\Component\HttpKernel\EventListener\ResponseListener:
Content-Type della risposta, in base al formato della richiesta;
Symfony\Component\HttpKernel\EventListener\EsiListener:
HTTP Surrogate-Control quando si deve cercare dei tag ESI nella risposta.

aggiusta

il

aggiunge un header

Evento kernel.exception Classe evento: Symfony\Component\HttpKernel\Event\GetResponseForExcept


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);

238

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

// 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 definite, il codice diventa pi flessibile e lo sviluppatore pu estendere le classi con delle sotto-classi, per
modificare il loro comportamento. Ma se si vogliono condividere le modifiche 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
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 in un modo semplice ed efficace, per rendere
possibili tutte queste cose e per rendere i propri progetti veramente estensibili.
Prendiamo un semplice esempio dal componente HttpKernel di Symfony2. Una volta che un oggetto Response
stato creato, potrebbe essere utile consentire ad altri elementi del sistema di modificarlo (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 notifica a (cio chiamat un metodo su) tutti gli ascoltatori dellevento kernel.response,
consentendo a ciascuno di essi di effettuare modifiche sulloggetto Response.
Eventi

Quando un evento viene distribuito, identificato 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 prefisso con uno spazio dei nomi, seguito da un punto (p.e. kernel.);
finisce 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 notifica 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.

2.1. Libro

239

Symfony2 documentation Documentation, Release 2

Spesso, occorre passare i dati su uno specifico 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.
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 notifica 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 notificato 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 fino a tre parametri:


Il nome dellevento (stringa) che questo ascoltatore vuole ascoltare;
Una funzione PHP, che sar notificata 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 predefinito 0). Se due ascoltatori hanno la stessa
priorit, sono eseguito nello stesso ordine con cui sono stati aggiunti al distributore.
Note: Una funzione PHP 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 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 fino a che levento non notificato.
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

240

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

{
// ...
public function allAzionePippo(Event $event)
{
// fare qualcosa
}
}

Tip: Se si usa il framework MVC di Symfony2 MVC, gli ascoltatori possono essere registrati tramite la configurazione. Come bonus aggiuntivo, gli oggetti ascoltatori sono istanziati solo alloccorrenza.
In alcuni casi, una sotto-classe speciale Event, specifica 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 flessibili 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 definire 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

241

Symfony2 documentation Documentation, Release 2

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 predefinita
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
notifica 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();
// fare qualcosa con lordine
}

242

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 notificare 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 fine 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.
Tip: Se si usa la dependency injection come negli esempi sopra, si pu usare il componente Dependency Injection
di Symfony2 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 notificato ogni volta che tali eventi sono distribuiti.
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;

2.1. Libro

243

Symfony2 documentation Documentation, Release 2

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.
Tip: Se si usa il framework MVC Symfony2, si possono registrare sottoscrittori tramite la propria configurazione.
Come bonus aggiuntivo, gli oggetti sottoscrittori sono istanziati solo quando servono.

Bloccare il flusso 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 notificare 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)
{
// ...
$event->stopPropagation();
}

Ora, tutti gli ascoltatori di negozio.ordine che non sono ancora stati richiamati non saranno richiamati.
Profiler
Se abilitato, il profiler di Symfony2 raccoglie informazioni utili su ogni richiesta fatta alla propria applicazione e
le memorizza per analisi successive. Luso del profiler in ambienti di sviluppo aiuta il debug del proprio codice e
244

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

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 profiler, visto che Symfony2 fornisce strumenti di visualizzazione,
come la barra di web debug e il profiler web. Se si usa Symfony2 Standard Edition, il profiler, la barra di web
debug e il profiler web sono gi configurati con impostazioni appropriate.
Note: Il profiler 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 profile associati (uno per ogni coppia richiesta/risposta esterna).

Visualizzare i dati di profile

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 profile, 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 profiler web.
Note: Se il token non cliccabile, vuol dire che le rotte del profiler non sono state registrate (vedere sotto per le
informazioni sulla configurazione).

Analizzare i dati di profile con il profiler web Il profiler web uno strumento di visualizzazione per i dati
di profile, 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 profiler in
uninterfaccia web.
Accedere alle informazioni di profile Non occorre usare il visualizzatore predefinito per accedere alle informazioni di profile. Ma come si possono recuperare informazioni di profile per una specifica richiesta, dopo che
accaduta? Quando il profiler 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);

Tip: Quando il profiler 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:
// 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 profile su macchine diverse da quella che ha generato le informazioni, usare i
metodi export() e import():

2.1. Libro

245

Symfony2 documentation Documentation, Release 2

// sulla macchina di produzione


$profile = $container->get(profiler)->loadProfile($token);
$data = $profiler->export($profile);
// sulla macchina di sviluppo
$profiler->import($data);

Configurazione La configurazione predefinita di Symfony2 ha delle impostazioni adeguate per il profiler, la


barra di web debug e il profiler web. Ecco per esempio la configurazione 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/
<!-- 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
$container->loadFromExtension(web_profiler, array(
toolbar => true,
intercept-redirects => true,
verbose => true,
));

Quando only-exceptions impostato a true, il profiler raccoglie dati solo quando lapplicazione solleva
uneccezione.
Quando intercept-redirects impostata true, il profiler 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 profiler web, occorre anche montare le rotte del profiler:

246

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

YAML
_profiler:
resource: @WebProfilerBundle/Resources/config/routing/profiler.xml
prefix:
/_profiler

XML

<import resource="@WebProfilerBundle/Resources/config/routing/profiler.xml" prefix="/_profile

PHP

$collection->addCollection($loader->import("@WebProfilerBundle/Resources/config/routing/profi

Poich il profiler aggiunge un po di sovraccarico, probabilmente lo si abiliter solo in alcune circostanze in


ambiente di produzione. Limpostazione only-exceptions limita il profile alle pagine 500, ma che succede
se si vogliono pi informazioni quando il client ha uno specifico 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
<!-- 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" -->

2.1. Libro

247

Symfony2 documentation Documentation, Release 2

<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),
),
));

Imparare di pi dal ricettario


Come usare il profilatore 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 firma 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 modifica 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.
248

Chapter 2. Libro

Symfony2 documentation Documentation, Release 2

Tip: 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
Yaml
Symfony2 e fondamenti di HTTP
Symfony2 contro PHP puro
Installare e configurare 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

2.1. Libro

249

Symfony2 documentation Documentation, Release 2

Symfony2 e fondamenti di HTTP


Symfony2 contro PHP puro
Installare e configurare 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

250

Chapter 2. Libro

CHAPTER

THREE

RICETTARIO
3.1 Ricettario
3.1.1 Come creare e memorizzare un progetto Symfony2 in git
Tip: Sebbene questa guida riguardi nello specifico 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.
Preparazione del progetto
Per iniziare, occorre scaricare Symfony e inizializzare il repository locale:
1. Scaricare Symfony2 Standard Edition senza venditori.
2. Scompattare la distribuzione. Questo creer una cartella chiamata Symfony con la struttura del nuovo
progetto, i file di configurazione, ecc. Si pu rinominare la cartella a piacere.
3. Creare un nuovo file chiamato .gitignore nella radice del nuovo progetto (ovvero vicino al file deps)
e copiarvi le righe seguenti. I file corrispondenti a questi schemi saranno ignorati da git:
/web/bundles/
/app/bootstrap*
/app/cache/*
/app/logs/*
/vendor/
/app/config/parameters.yml

4. Copiare app/config/parameters.yml in app/config/parameters.yml.dist. Il file


parameters.yml ignorato da git (vedi sopra), quindi le impostazioni specifiche della macchina, come
le password del database, non saranno inviate. Creando il file parameters.yml.dist, i nuovi sviluppatori potranno clonare rapidamente il progetto, copiando questo file in parameters.yml e personalizzandolo.
5. Inizializzare il proprio repository git:
$ git init

6. Aggiungere tutti i file in git:


$ git add .

7. Creare un commit iniziale con il nuovo progetto:

251

Symfony2 documentation Documentation, Release 2

$ git commit -m "Commit iniziale"

8. Infine, 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 modifiche al proprio repository git.
Tip: Dopo aver eseguito il comando:
$ php bin/vendors install

il progetto conterr la cronologia completa di tutt i bundle e le librerie definite nel file 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 definiti nel file 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 configurare e
sviluppare la propria applicazione.
Tip: Symfony2 Standard Edition distribuito con alcuni esempi di funzionamento. Per rimuovere il codice di
esempio, seguire le istruzioni nel file Readme di Standard Edition.

Venditori e sotto-moduli

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. Non c nulla di sbagliato in questo approccio, ma il sistema deps la via
ufficiale per risolvere questo problema e i sotto-moduli di git possono a volte creare delle difficolt.
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. 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 e
usando quello. Una libreria che pu aiutare in tal senso Gitolite.

3.1.2 Come creare e memorizzare un progetto Symfony2 in Subversion


Tip: Questa voce specifica 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, ma qualcuno

252

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

preferisce usare Subversion, che va totalmente bene! In questa ricetta, vedremo come gestire il proprio progetto
usando svn, in modo simile a quanto si farebbe con git.
Tip: 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/

Tip: La maggior parte degli host con subversion dovrebbero seguire questa pratica. Questo lo schema raccomandato in Controllo di versione con Subversion 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, con o senza venditori.
2. Scompattare la distribuzione. Questo creer una cartella chiamata Symfony, con la struttura del nuovo
progetto, i file di configurazione, 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 e che si chiami mioprogetto:
$ svn checkout http://mioprogetto.googlecode.com/svn/trunk mioprogetto

4. Copiare i file 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 file
(come la cache) sono generati e altri (come la configurazione del database) devono essere personalizzati su
ciascuna macchina. Ci implica luso della propriet svn:ignore, che consente di ignorare specifici file.
$ 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/

6. Tutti gli altri file possono essere aggiunti al progetto:


$ svn add --force .
$ svn ci -m "aggiunta Symfony Standard 2.X.Y"

3.1. Ricettario

253

Symfony2 documentation Documentation, Release 2

7. Copiare app/config/parameters.ini su app/config/parameters.ini.dist. Il file


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 file parameters.ini.dist, i nuovi
sviluppatori possono prendere subito il progetto, copiare questo file in parameters.ini, personalizzarlo e iniziare a sviluppare.
8. Infine, scaricare tutte le librerie dei venditori:
$ php bin/vendors install

Tip: git 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 configurare e
sviluppare la propria applicazione.
Tip: La Standard Edition di Symfony2 ha alcune funzionalit di esempio. Per rimuovere il codice di esempio,
seguire le istruzioni nel Readme della Standard Edition.

Gestire le librerie dei venditori con bin/vendors e deps


Ogni progetto Symfony usa un gruppo di librerie di venditori. In un modo o nellaltro, lo scopo scaricare tali
file nella propria cartella vendor/ e, idealmente, avere un modo tranquillo per gestire lesatta versione necessaria
per ciascuno.
Per impostazione predefinita, tali librerie sono scaricate eseguendo uno script scaricatore php bin/vendors
install. Questo script legge dal file 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 file 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 file non tracciati, che sono scaricati dallo script bin/vendors nella cartella vendor/. Ma,
poich ogni informazione necessaria a scaricare tali file nei file 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 file, 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).

254

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Caution:
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 specificate in deps e registrandole nel
file deps.lock.

Soluzioni di hosting subversion


La differenza maggiore tra git e svn che Subversion necessita di un repository centrale per funzionare. Ci sono
diverse soluzioni:
Hosting autonomo: creare il proprio repository e accedervi tramite filesystem o tramite rete. Per maggiori
informazioni, leggere Controllo di versione con Subversion.
Hosting di terze parti: ci sono molte buone soluzioni di hosting gratuito a disposizione, come GitHub,
Google code, SourceForge o Gna. 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 predefinito delle eccezioni TwigBundle::Exception:show con il proprio
controllore e gestirlo come si vuole (vedere exception_controller nella guida di riferimento di Twig);
Tip: 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 predefinito che mostrato allutente finale, 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>
</body>
</html>

Tip: 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.

3.1. Ricettario

255

Symfony2 documentation Documentation, Release 2

In aggiunta alla pagina di errore standard HTML, Symfony fornisce una pagina di errore predefinita 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 file 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 specifici 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).
Tip:
Per vedere lelenco completo dei template di errore predefiniti, 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 modificarlo.

Note: 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 Definire 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 specificati come servizi.
Per fare rifermento a un controllore definito come servizio, usare la notazione con un solo due punti (:). Per
esempio, si supponga di aver definito 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 definisce il valore _controller della rotta:
mio_controllore:
pattern:
/
defaults: { _controller: mio_controllore:indexAction }

Per usare un controllore in questo modo, deve essere definito nella configurazione del contenitore di servizi. Per
ulteriori informazioni, si veda il capitolo Contenitore di servizi.
Quando si usa un controllore definito 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.
Note: Specificare un controllore come servizio richiede un po pi di lavoro. Il vantaggio principale che
lintero controllore o qualsiasi servizio passato al controllore possono essere modificati tramite la configurazione

256

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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 specificando 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/r
<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,
)));
return $collection;

La configurazione 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.

3.1. Ricettario

257

Symfony2 documentation Documentation, Release 2

Note: 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 definiti 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 predefinita, /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 modificare una rotta in modo che /hello/Fabien/Kris corrisponda alla rotta
/hello/{name}, dove {name} vale Fabien/Kris.
Configurare la rotta
Per impostazione predefinita, il componente delle rotte di symfony richiede che i parametri corrispondano alla
seguente espressione regolare: [^/]+. Questo significa che tutti i caratteri sono permessi eccetto /.
Bisogna consentire esplicitamente che il carattere / possa far parte del parametro specificando 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"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/r
<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

258

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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 filtri. Le risorse sono file come CSS, JavaScript e file di immagini. I
filtri sono cose che possono essere applicate a questi file prima di essere serviti al browser. Questo permette una
separazione tra i file delle risorse memorizzati nellapplicazione e i file effettivamente presentati allutente.
Senza Assetic, basta servire direttamente i file 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 significa che si pu:
Minimizzare e combinare tutti i file CSS e JS
Eseguire tutti (o solo alcuni) dei file 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 file. I file 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; ?>

3.1. Ricettario

259

Symfony2 documentation Documentation, Release 2

Tip: 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 file 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>

Note: Questo un punto fondamentale: una volta che si lascia gestire le risorse ad Assetic, i file vengono serviti
da una posizione diversa. Questo pu causare problemi con i file CSS che fanno riferimento a immagini tramite il
loro percorso relativo. Comunque, il problema pu essere risolto utilizzando il filtro cssrewrite, che aggiorna
i percorsi nei file CSS per riflettere la loro nuova posizione.

Combinare le risorse

anche possibile combinare pi file 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 file pi facilmente, dividendoli in gruppi maggiormente gestibili. Questo pu contribuire alla riusabilit in quanto si possono facilmente dividere file specifici
del progetto da quelli che possono essere utilizzati in altre applicazioni, ma servendoli ancora come un unico file:
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 file 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.
Tip: 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
260

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

di proposito. Per informazioni dettagliate sullutilizzo di Assetic in ambiente prod, vedere Copiare i file delle
risorse.
La combinazione dei file non si applica solo ai propri file. Si pu anche utilizzare Assetic per combinare risorse
di terze parti (come jQuery) con i propri, in un singolo file:
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; ?>

Filtri
Una volta che vengono gestite da Assetic, possibile applicare i filtri alle proprie risorse prima che siano servite.
Questi includono filtri che comprimono loutput delle proprie risorse per ottenere file di dimensioni inferiori
(e migliore ottimizzazione nel frontend). Altri filtri possono compilare i file JavaScript da file CoffeeScript e
processare SASS in CSS. Assetic ha una lunga lista di filtri disponibili.
Molti filtri non fanno direttamente il lavoro, ma usano librerie di terze parti per fare il lavoro pesante. Questo
significa che spesso si avr la necessit di installare una libreria di terze parti per usare un filtro. Il grande vantaggio
di usare Assetic per invocare queste librerie (invece di utilizzarle direttamente) che invece di doverle eseguire
manualmente dopo aver lavorato sui file, sar Assetic a prendersene cura, rimuovendo del tutto questo punto dal
processo di sviluppo e di pubblicazione.
Per usare un filtro, necessario specificarlo nella configurazione di Assetic. Laggiunta di un filtro qui non significa
che venga utilizzato: significa solo che disponibile per luso.
Per esempio, per usare il compressore JavaScript YUI bisogna aggiungere la configurazione 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(

3.1. Ricettario

261

Symfony2 documentation Documentation, Release 2

yui_js => array(


jar => %kernel.root_dir%/Resources/java/yuicompressor.jar,
),
),
));

Ora, per utilizzare effettivamente il filtro su un gruppo di file 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/*),
array(yui_js)) as $url): ?>
<script src="<?php echo $view->escape($url) ?>"></script>
<?php endforeach; ?>

Una guida pi dettagliata sulla configurazione e lutilizzo dei filtri di Assetic, oltre a dettagli della modalit di
debug di Assetic, si trova in Minimizzare i file 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; ?>

Note: Symfony contiene anche un metodo per accelerare la cache, in cui lURL finale generato da Assetic
contiene un parametro di query che pu essere incrementato tramite la configurazione di ogni pubblicazione. Per
ulteriori informazioni, vedere lopzione di configurazione assets_version.

Copiare i file delle risorse


Nellambiente dev, Assetic genera persorsi a file CSS e JavaScript che non esistono fisicamente sul computer.
Ma vengono resi comunque perch un controllore interno di Symfony apre i file e restituisce indietro il contenuto
(dopo aver eseguito eventuali filtri).
262

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Questo tipo di pubblicazione dinamica delle risorse che sono state elaborate, ottima perch significa che si pu
immediatamente vedere il nuovo stato di tutti i file delle risorse modificate. anche un male, perch pu essere
molto lento. Se si stanno usando molti filtri, potrebbe essere addirittura frustrante.
Fortunatamente, Assetic fornisce un modo per copiare le proprie risorse in file reali, anzich farli generare dinamicamente.
Copiare i file delle risorse nellambiente prod

Nellambiente prod, i file JS e CSS sono rappresentati da un unico tag. In altre parole, invece di vedere ogni file
JavaScript che che si sta includendo nei sorgenti, probabile che si veda qualcosa di questo tipo:
<script src="/app_dev.php/js/abcd123.js"></script>

Questo file in realt non esiste, n viene reso dinamicamente da Symfony (visto che i file di risorse sono
nellambiente dev). Lasciare generare a Symfony questi file 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 fisicamente e scriver ogni file 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 file.
Copiare i file delle risorse nellambiente dev

Per impostazione predefinita, ogni percorso generato della risorsa nellambiente dev gestito dinamicamente
da Symfony. Questo non ha alcun svantaggio ( possibile visualizzare immediatamente le modifiche), 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 file in modo dinamico. Fare la seguente
modifica nel file 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 fisicamente tutti i file 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:

3.1. Ricettario

263

Symfony2 documentation Documentation, Release 2

php app/console assetic:dump --watch

Dal momento che lesecuzione di questo comando nellambiente dev pu generare molti file, di solito una
buona idea far puntare i file con le risorse generate in una cartella separata (ad esempio /js/compiled), per
mantenere ordinate le cose:
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; ?>

3.1.8 Minimizzare i file JavaScript e i fogli di stile con YUI Compressor


Yahoo! mette a disposizione un eccellente strumento per minimizzare i file JavaScipt e i fogli di stile, che cos
possono viaggiare pi velocemente sulla rete: lo 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 file JAR e salvarlo in
app/Resources/java/yuicompressor.jar.
Configurare i filtri per YUI
necessario configurare due filtri Assetic allinterno dellapplicazione. Uno per minimizzare i file 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"

264

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

jar="%kernel.root_dir%/Resources/java/yuicompressor.jar" />
</assetic:config>

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 filtri di Assetic: yui_css e yui_js. Questi filtri utilizzeranno
YUI Compressor per minimizzare, rispettivamente, i fogli di stile e i file JavaScript.
Minimizzare le risorse
YUI Compressor stato configurato, ma, prima di poter vedere i risultati, necessario applicare i filtri 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; ?>

Note: Il precedente esempio presuppone che ci sia un bundle chiamato AcmeFooBundle e che i file JavaScript
si trovino nella cartella Resources/public/js allinterno del bundle. comunque possibile includere file
JavaScript che si trovino in posizioni differenti.
Con laggiunta del filtro yui_js dellesempio precedente, i file 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; ?>

3.1. Ricettario

265

Symfony2 documentation Documentation, Release 2

Disabilitare la minimizzazione in modalit debug


I file JavaScript e i fogli di stile minimizzati sono difficili da leggere e ancora pi difficili da correggere. Per questo
motivo Assetic permette di disabilitare determinati filtri quando lapplicazione viene eseguita in modalit debug.
Mettendo il prefisso punto interrogativo ? al nome dei filtri, 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 filtri 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 grafico per ogni modifica. Il
risultato dei filtri pu essere messo in cache e usato in fase di produzione, in modo da eliminare problemi di
prestazioni per lutente finale.
Usare Jpegoptim
Jpegoptim uno strumento per ottimizzare i file JPEG. Per poterlo usare, si aggiunge il seguente codice alla
configurazione 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,
),
),
));

266

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Note: 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 filtro rimuove solamente le meta-informazioni contenute nel file. 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,
),
),
));

3.1. Ricettario

267

Symfony2 documentation Documentation, Release 2

Diminuire la qualit massima

Senza ulteriori opzioni, la qualit dellimmagine JPEG non viene modificata. per possibile ridurre ulteriormente la dimensione del file, configurando 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 modificando la configurazione, come di seguito:
YAML
# app/config/config.yml
assetic:
filters:
jpegoptim:
bin: percorso/per/jpegoptim
twig:
functions:
jpegoptim: ~

XML
<!-- app/config/config.xml -->
<assetic:config>
<assetic:filter
name="jpegoptim"
bin="percorso/per/jpegoptim" />
<assetic:twig>
<assetic:twig_function
name="jpegoptim" />

268

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

</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 modificato nel seguente modo:


<img src="{{ jpegoptim(@AcmeFooBundle/Resources/public/images/esempio.jpg) }}"
alt="Esempio"/>

possibile specificare 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
// 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. Ricettario

269

Symfony2 documentation Documentation, Release 2

),
));

3.1.10 Applicare i filtri di Assetic a file con specifiche estensioni


I filtri di Assetic possono essere applicati a singoli file, gruppi di file o anche, come vedremo, a file che hanno una
specifica estensione. Per mostrare lutilizzo di ogni opzione, supponiamo di voler usare il filtro CoffeeScript di
Assetic che compila i file CoffeeScript in Javascript.
La configurazione prevede semplicemente di definire i percorsi per coffee e per node. I valori predefiniti 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,
),
),
));

Filtrare un singolo file


In questo modo sar possibile inserire un singolo file 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): ?>

270

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

<script src="<?php echo $view->escape($url) ?>" type="text/javascript"></script>


<?php endforeach; ?>

Questo tutto quel che serve per compilare il file CoffeeScript e restituirlo come un normale JavaScript.
Filtrare file multpili
anche possibile combinare diversi file CoffeeScript in un singolo file:
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 file verranno restituiti e compilati in un unico, regolare file JavaScript.


Filtrare in base allestensione del file
Uno dei grandi vantaggi nellutilizzo di Assetic quello di ridurre il numero di file di risorse, riducendo cos
le richieste HTTP. Per massimizzarne i vantaggi, sarebbe utile combinare insieme tutti i file JavaScript e quelli
CoffeeScript in uno unico, visto che verranno tutti serviti come file JavaScript. Sfortunatamente non possibile aggiungere semplicemente un file JavaScript ai file precedenti, per via del fatto che il file JavaScript non
supererebbe la compilazione di CoffeeScript.
Questo problema pu essere ovviato utilizzando lopzione apply_to nella configurazione, in modo da specificare che il filtro dovr essere applicato solo ai file con una determinata estensione. In questo caso si dovr
specificare che il filtro Coffee dovr applicarsi a tutti e soli i file .coffee:
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>

3.1. Ricettario

271

Symfony2 documentation Documentation, Release 2

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 specificare il filtro coffee nel template. anche possibile elencare i normali
file JavaScript, i quali verranno combinati e restituiti come un unico file JavaScript (e in modo tale che i soli file
.coffee venagano elaborati dal filtro 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.11 Come gestire il caricamento di file con Doctrine


La gestione del caricamento dei file tramite le entit di Doctrine non diversa da qualsiasi altro tipo di caricamento.
In altre parole si liberi di spostare il file nel controllore dopo aver gestito linvio tramite una form. Per alcuni
esempi in merito fare riferimento alla pagina dedicata ai file type.
Volendo anche possibile integrare il caricamento del file nel ciclo di vita di unentit (creazione, modifica e
cancellazione). In questo caso, nel momento in cui lentit viene creata, modificata, o cancellata da Doctrine,
il caricamento del file 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

272

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

*/
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()
{
// 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 vi
return uploads/documents;
}
}

Lentit Document ha un nome che viene associato al file. La propriet path contiene il percorso relativo al file
e viene memorizzata sul database. Il metodo getAbsolutePath() un metodo di supporto che restituisce il
percorso assoluto al file mentre il getWebPath() un altro metodo di supporto che restituisce il percorso web
che pu essere utilizzato nei template per collegare il file caricato.
Tip: Se non gi stato fatto, si consiglia la lettura della documentazione relativa ai file type per comprendere
meglio come funziona il caricamento di base.

Note: Se si stanno utilizzando le annotazioni per specificare le regole di validazione (come nellesempio proposto), assicurarsi di abilitare la validazione tramite annotazioni (confrontare configurazione della validazione).
Per gestire il file 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()
{

3.1. Ricettario

273

Symfony2 documentation Documentation, Release 2

// ...
$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;
// ...
}

Note: Grazie al fatto che si utilizza il vincolo File, Symfony2 ipotizzer automaticamente che il campo del
form sia un file 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());
}

274

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Note: 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 file e la propriet path sar vuota.
Un modo semplice per gestire il caricamento del file 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 file, nella classe Document:
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$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 verifica un problema
mentre lentit viene memorizzata? Il file potrebbe gi essere stato spostato nella sua posizione finale anche se la
propriet path dellentit non fosse stata impostata correttamente.
Per evitare questo tipo di problemi, necessario modificare limplementazione in modo tale da rendere atomiche
le azioni del database e dello spostamento del file: se si verificasse un problema durante la memorizzazione
dellentit, o se il file non potesse essere spostato, allora non dovrebbe succedere niente.

3.1. Ricettario

275

Symfony2 documentation Documentation, Release 2

Per fare questo, necessario spostare il file 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
* @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 file univoco prima della memorizzazione, sposta il file
dopo la memorizzazione, rimuove il file se lentit viene eliminata.
276

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Note: 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 modificata.
Caution: I callback PreUpdate e PostUpdate scattano solamente se c una modifica a uno dei campi
dellentit memorizzata. Questo significa che, se si modifica 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
modificare manualmente in caso di necessit per la sostituzione del file.

Usare id come nome del file


Volendo usare lid come nome del file, limplementazione leggermente diversa, dato che sarebbe necessario
memorizzare lestensione nella propriet path, invece che nellattuale nome del file:
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

277

Symfony2 documentation Documentation, Release 2

}
}

public function getAbsolutePath()


{
return null === $this->path ? null : $this->getUploadRootDir()./.$this->id...$this->pa
}
}

3.1.12 Estensioni di Doctrine: Timestampable: Sluggable, Translatable, ecc.


Doctrine2 molto flessibile 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 fornisce integrazione con una libreria di estensioni, che offre i
comportamenti Sluggable, Translatable, Timestampable, Loggable e Tree.
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, significa la possibilit di creare servizi arbitrari e dire a Doctrine di notificare 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 defininsce due tipi di oggetti che possono ascoltare eventi: ascoltatori e sottoscrittori. Sono simili tra
loro, ma gli ascoltatori sono leggermente pi semplificati. Per approfondimenti, vedere The Event System sul sito
di Doctrine.
Configurare 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 specifica 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:
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 }

278

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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 configurato 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
}
}
}

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 specifico di entit (p.e. unentit Product, ma non unentit BlogPost), si dovrebbe
verificare il nome della classe dellentit nel proprio metodo (come precedentemente mostrato).

3.1. Ricettario

279

Symfony2 documentation Documentation, Release 2

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 modificabile. Per fortuna, Doctrine dispone di molti strumenti
che aiutano a generare classi del modello da una base dati esistente.
Note: Come dice la documentazione sugli strumenti di Doctrine, 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 specifiche 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 CASCAD
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Prima di addentrarsi nella ricetta, ci si assicuri di aver configurato correttamente i propri parametri di connessione,
nel file app/config/parameters.yml (o in qualsiasi altro posto in cui la configurazione 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 file dei meta-dati corrispondenti. I file dei meta-dati descrivono le classi entit da generare in base ai campi delle tabelle.

php app/console doctrine:mapping:convert xml ./src/Acme/BlogBundle/Resources/config/doctrine/metad

Questo comando del terminale chiede a Doctrine lintrospezione della base dati e la generazione dei file di metadati XML sotto la cartella src/Acme/BlogBundle/Resources/config/doctrine/metadata/orm
del bundle.
Tip: Le classi dei meta-dati possono anche essere generate in YAML, modificando il primo parametro in yml.
Il file 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"/>

280

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

</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 file 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")
*/
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)
*/

3.1. Ricettario

281

Symfony2 documentation Documentation, Release 2

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


Note: 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 un livello posto sopra
PDO e offre unAPI intuitiva e flessibile per comunicare con i database relazionali pi diffusi. In altre parole, la
libreria DBAL facilita lesecuzione delle query ed esegue altre azioni sul database.
Tip: Leggere la documentazione di Doctrine DBAL Documentation per conoscere tutti i dettagli e le capacit
della libreria DBAL di Doctrine.
Per iniziare, configurare 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

282

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

// 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 configurazione, vedere Configurazione 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 configurazione di Symfony. Saranno aggiunti
a tutte le configurazioni configurate. Per maggiori informazioni sui tipi di mappatura personalizzati, leggere la
sezione Custom Mapping Types della documentazione di Doctrine.
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/
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/
<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(

3.1. Ricettario

283

Symfony2 documentation Documentation, Release 2

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 configurazione.
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
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/
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/

<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,
),
),
));

284

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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.
Note: 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 configurazione seguente mostra come configurare due gestori di entit:
YAML
doctrine:
orm:
default_entity_manager:
default
entity_managers:
default:
connection:
default
mappings:
AcmeDemoBundle: ~
AcmeStoreBundle: ~
customer:
connection:
customer
mappings:
AcmeCustomerBundle: ~

In questo caso, sono stati definiti 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 predefinito (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 specificare funzioni DQL personalizzate. Per maggiori informazioni sullargomento, leggere
la ricetta di Doctrine DQL User Defined Functions.
In Symfony, si possono registrare funzioni DQL personalizzate nel modo seguente:
YAML
# app/config/config.yml
doctrine:

3.1. Ricettario

285

Symfony2 documentation Documentation, Release 2

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"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
http://symfony.com/schema/dic/doctrine http://symfony.com/schema/dic/

<doctrine:config>
<doctrine:orm>
<!-- ... -->
<doctrine:entity-manager name="default">
<!-- ... -->
<doctrine:dql>
<doctrine:string-function name="test_string>Acme\HelloBundle\DQL\StringFu
<doctrine:string-function name="second_string>Acme\HelloBundle\DQL\Second
<doctrine:numeric-function name="test_numeric>Acme\HelloBundle\DQL\Numeri
<doctrine:datetime-function name="test_datetime>Acme\HelloBundle\DQL\Date
</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,
),
),
),
),
),
));

286

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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:
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 modificato
a diversi livelli. Per maggiori informazioni sulla resa dei form in generale, disponibile Rendere un form in un
template.

3.1. Ricettario

287

Symfony2 documentation Documentation, Release 2

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 definiti 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.
Symfony ha un tema predefinito (form_div_layout.html.twig in Twig e FrameworkBundle:Form in PHP),
che definisce 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 predefinita il blocco integer_widget dal template form_div_layout.html.twig.
In
PHP

il
file
integer_widget.html.php
FrameworkBundle/Resources/views/Form.

posizionato

nella

cartella

Limplementazione del frammento integer_widget sar simile a:


Twig
{% block integer_widget %}
{% set type = type|default(number) %}
{{ block(field_widget) }}
{% endblock integer_widget %}

PHP
<!-- integer_widget.html.php -->
<?php echo $view[form]->renderBlock(field_widget, array(type => isset($type) ? $type :

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

288

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

type="<?php echo isset($type) ? $view->escape($type) : "text" ?>"


value="<?php echo $view->escape($value) ?>"
<?php echo $view[form]->renderBlock(attributes) ?>
/>

Il punto che il frammento detta loutput HTML di ogni parte del form. Per personalizzare loutput del form,
necessario soltanto identificare 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 file di template e i frammente sono dei blocchi definiti in questo file.
In PHP un tema una cartella e i frammenti sono singoli file 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. Specificamente dato che il tipo text esteso dal tipo field, il componente del form
guarder per prima cosa al tipo-specifico 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) }}

3.1. Ricettario

289

Symfony2 documentation Documentation, Release 2

</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 specifiche 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 finale 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 specificato 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 file di tema, in modo simile al secondo metodo adottato per Twig.
Bisogna nominare il file del tema dopo il frammento. Bisogna creare il file 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 : "
</div>

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:

290

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

<?php $view[form]->setTheme($form, array(AcmeDemoBundle:Form)) ;?>


<?php $view[form]->widget($form[age]) ?>

Quando il widget form.age viene reso,


Symfony utilizzer il
integer_widget.html.php e il tag input sar contenuto in un elemento div.

tema

personalizzato

Referenziare blocchi di form (specifico per Twig)


Finora, per sovrascrivere un particolare blocco del form, il metodo migliore copiare il blocco di default da
form_div_layout.html.twig, 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, il blocco integer_widget chiamato


base_integer_widget. Questo significa che quando viene ridefinito 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 %}

Note: Non possibile referenziare il blocco base quando si usa PHP come motore di template. Bisogna copiare
manualmente il contenuto del blocco base nel nuovo file di template.

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 configurazione
dellapplicazione:

3.1. Ricettario

291

Symfony2 documentation Documentation, Release 2

Twig

Utilizzando la seguente configurazione, 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 -->
<twig:config ...>
<twig:form>
<resource>form_table_layout.html.twig</resource>
</twig:form>
<!-- ... -->
</twig:config>

PHP
// app/config/config.php

292

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

$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 file 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 configurazione 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
// app/config/config.php
// PHP
$container->loadFromExtension(framework, array(
templating => array(form =>
array(resources => array(
AcmeDemoBundle:Form,
)))
// ...
));

Per impostazione predefinita, 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

3.1. Ricettario

293

Symfony2 documentation Documentation, Release 2

# 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 file 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.
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

294

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

<!-- 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 definisce il template da utilizzare per il campo del quale lid
product_name (e il nome product[name]).
Tip: 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">
<?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 specifico 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

Note: 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.

3.1. Ricettario

295

Symfony2 documentation Documentation, Release 2

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
<!-- 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 ?>

Tip: Si veda Temi del Form per come applicare questa personalizzazione.
anche possibile personalizzare loutput dellerrore per uno specifico tipo di campo. Per esempio, alcuni errori
che sono globali al form (es. non specifici a un singolo campo) sono resi separatamente, di solito allinizio del
form:
Twig
{{ form_errors(form) }}

PHP
<?php echo $view[form]->render($form); ?>

296

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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 file 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 modificarlo, 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>

Tip: 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 modificare 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>

3.1. Ricettario

297

Symfony2 documentation Documentation, Release 2

{% 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 ?>

Tip: 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 modificare il tag use e
aggiungere le seguenti righe:
{% 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 ?>

298

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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)) ?>

Tip: Si veda Temi del Form per sapere come applicare questa configurazione.

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?
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
configurazione 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.
);

3.1. Ricettario

299

Symfony2 documentation Documentation, Release 2

}
public function getParent(array $options)
{
return text;
}
public function getName()
{
return issue_selector;
}
}

Tip: possibile utilizzare i transformer senza necessariamente creare un nuovo form personalizzato invocando
la funzione appendClientTransformer su qualsiasi field builder:
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 ;
}

300

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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 => $va

if (null === $issue) {


throw new TransformationFailedException(sprintf(Un rilascio con numero %s non esiste
}
return $issue;
}
}

Infine, 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
3.1. Ricettario

301

Symfony2 documentation Documentation, Release 2

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.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;
}
}

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 modificato (es. un prodotto
ottenuto da database).
Si supponga ora, di non voler abilitare lutente alla modifica del campo nome una volta che loggetto stato
creato. Per fare ci si pu dare unocchiata al Event Dispatcher sistema che analizza loggetto e modifica il form
basato sull oggetto prodotto. In questa voce, si imparer come aggiungere questo livello di flessibilit 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 ad 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);
}
public function getName()

302

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

{
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 notificata 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

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;

}
// controlla se loggetto Prodotto nuovo
if (!$data->getId()) {
$form->add($this->factory->createNamed(text, name));
}
}
}

Caution: facile fraintendere lo scopo dellistruzione if (null === $data) dellevento sottoscrittore.
Per comprendere appieno il suo ruolo, bisogna dare uno sguardo alla classe Form e prestare attenzione a dove
setData() invocato alla fine del costruttore, nonch al metodo setData() stesso.

3.1. Ricettario

303

Symfony2 documentation Documentation, Release 2

La riga FormEvents::PRE_SET_DATA viene attualmente risolta nella stringa form.pre_set_data. La


classe FormEvents 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 modificato da nessun altro sottoscrittore o ascoltatore. Questo perch
form.pre_set_data passa alloggetto DataEvent invece delloggetto FilterDataEvent passato dallevento
form.set_data. DataEvent, a differenza del suo figlio FilterDataEvent, non ha il metodo setData().
Note: possibile consultare la lista completa degli eventi del form tramite la classe FormEvents, 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 modificare/creare/cancellare oggetti Tag connessi a questo Task,
allinterno dello stesso form.
Note: 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();
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
}
public function getTags()
{
return $this->tags;
}
public function setTags(ArrayCollection $tags)

304

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

{
$this->tags = $tags;
}
}

Note: ArrayCollection specifica 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 verificare, un Task pu avere pi oggetti Tag:
// src/Acme/TaskBundle/Entity/Tag.php
namespace Acme\TaskBundle\Entity;
class Tag
{
public $name;
}

Tip: 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 modificato 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)
{
return array(
data_class => Acme\TaskBundle\Entity\Tag,
);
}
public function getName()
{
return tag;
}
}

Questo sufficiente per rendere un form tag. Ma dal momento che lobiettivo finale permettere la modifica 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

3.1. Ricettario

305

Symfony2 documentation Documentation, Release 2

{
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)
{
$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);
// fare qualche processo del form qui, 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
fittizio cos da poterlo vedere in azione (dato che un Task non ha tags appena viene creato).
Twig

306

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

{# 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) ?>
<!-- ... -->

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 modifica 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 significa 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 flessibile 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,

3.1. Ricettario

307

Symfony2 documentation Documentation, Release 2

));
}

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


</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:
function addTagForm() {
// Ottieni il div che detiene la collezione di tag
var collectionHolder = $(#task_tags);
// prendi il data-prototype
var prototype = collectionHolder.attr(data-prototype);
// Sostituisci $$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);
}
// Aggiungi 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 specifica 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 guardi
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 specifico. 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

308

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

tipo scelta. Questa sezione spiega come il campo definito, come si pu personalizzare il layout e, infine, come
possibile registrarlo per utilizzarlo nellapplicazione.
Definizione 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 file 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)
{
return array(
choices => array(
m => Male,
f => Female,
)
);
}
public function getParent(array $options)
{
return choice;
}
public function getName()
{
return gender;
}
}

Tip: La cartella di memorizzazione di questo file 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 significa che di default, sono ereditate tutte le logiche e la resa di queto tipo di campo. Per vedere alcune
logiche, controlla la classe ChoiceType. Ci sono tre metodi che sono particolarmente importanti:
buildForm() - Ogni tipo di campo possiede un metodo buildForm, che permette di configurare 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, 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 definisce 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), ma possibile crearne altre, quante sono necessarie.
Tip: Se si sta creando un campo che consiste di molti campi, assicurarsi di impostare come padre un tipo come

3.1. Ricettario

309

Symfony2 documentation Documentation, Release 2

form o qualcosaltro che estenda form. Nello stesso modo, se occorre modificare la vista di ogni sottotipo che
estende il proprio tipo, utilizzare il metodo buildViewBottomUp().
Il metodo getName() restituisce un identificativo 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 fissa le choices con la lista dei generi.
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 %}

Note: Assicurarsu che il prefisso del widget utilizzato sia corretto. In questo esempio il nome dovrebbe essere
gender_widget, in base al valore restituito da getName. Inoltre, il file principale di configurazione 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;

310

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class AuthorType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$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 configurazione 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 configurazione 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 configurazione:
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 definire 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>

3.1. Ricettario

311

Symfony2 documentation Documentation, Release 2

Tip: Assicurarsi che il file dei servizi sia importato. Leggere Importare la configurazione con imports per dettagli.
Assicurarsi che lattributo alias di tags corrisponda al valore restituito dal metodo getName definito 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 configurazione 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 configurazione ed registrato come servizio.
In quanto nella configurazione 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 configurazione 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;

312

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

/**
* @Annotation
*/
class Url extends Constraint
{
public $message = This value is not a valid URL;
public $protocols = array(http, https, ftp, ftps);
}

Note: 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 definita dal metodo del
vincolo validatedBy(), che usa una semplice logica predefinita:
// 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 configurarlo 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 }

XML

<service id="validator.unique.nome_proprio_validatore" class="Nome\Pienamente\Qualificato\Del


<argument type="service" id="doctrine.orm.default_entity_manager" />

3.1. Ricettario

313

Symfony2 documentation Documentation, Release 2

<tag name="validator.constraint_validator" alias="nome_alias" />


</service>

PHP

$container
->register(validator.unique.nome_proprio_validatore, Nome\Pienamente\Qualificato\Della
->addTag(validator.constraint_validator, array(alias => nome_alias))
;

La classe del vincolo dovr utilizzare lalias appena definito 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 suffisso Validator. Se il proprio validatore di vincoli definito come servizio, importante che si
faccia loverride del metodo validatedBy() in modo tale che restituisca lalias utilizzato nella definizione 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 configurazioni che determinano come il codice
dovr lavorare. La configurazione pu definire 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 configurazioni. Per esempio, lambiente dev dovrebbe usare una configurazione che renda
lo sviluppo semplice e ricco di informazioni, mentre lambiente prod dovrebbe usare un insieme di configurazioni
che ottimizzino la velocit.
Ambienti differenti, differenti file di configurazione
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 configurazioni. Non dovrebbe
destare sorpresa il fatto che ogni ambiente carichi i suoi propri file di configurazione. Se si utilizza il formato di
configurazione YAML, verranno utilizzati i seguenti file:
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 predefinito allinterno della classe AppKernel:
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
// ...
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 file di configurazione caricare. Questo permette di avere ambienti differenti in modo elegante, efficace e trasparente.
314

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Ovviamente, in realt, ogni ambiente differisce solo per alcuni aspetti dagli altri. Generalmente, gli ambienti
condividono gran parte della loro configurazione. Aprendo il file di configurazione 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 configurazione comune, i file di configurazione di ogni ambiente importano, per iniziare, un
file di configurazione comune (config.yml). Il resto del file potr deviare dalla configurazione predefinita,
sovrascrivendo i singoli parametri. Ad esempio, nellambiente dev, la barra delle applicazioni viene attivata
modificando, nel file di configurazione di dev, il relativo parametro predefinito:
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"
# ...
/>

PHP
// app/config/config_dev.php
$loader->import(config.php);
$container->loadFromExtension(web_profiler, array(
toolbar => true,
// ..
));

3.1. Ricettario

315

Symfony2 documentation Documentation, Release 2

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*

Note: Le precedenti URL presuppongono che il server web sia configurato in modo da usare la cartella web/
dellapplicazione, come radice. Per approfondire, si legga Installare Symfony2.
Guardando il contenuto di questi file, si vede come lambiente utilizzato da entrambi, sia definito in modo esplicito:
1

<?php

2
3
4

require_once __DIR__./../app/bootstrap_cache.php;
require_once __DIR__./../app/AppCache.php;

5
6

use Symfony\Component\HttpFoundation\Request;

7
8
9

$kernel = new AppCache(new AppKernel(prod, false));


$kernel->handle(Request::createFromGlobals())->send();

Si pu vedere come la chiave prod specifica 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.
Note: 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 file, per il
front controller, del tipo app_test.php.

316

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Modalit debug
Importante, ma non collegato allargomento ambienti, il valore false in riga 8 del precedente
front controller. Questo valore specifica se lapplicazione dovr essere eseguit in modalit debug o meno. Indipendentemente dallambiente, unapplicazione Symfony2 pu essere eseguita con
la modalit debug configurata 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 file di configurazione 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 preconfigurati per gestire la maggior parte dei casi.
Ovviamente, visto che un ambiente non nientaltro che una stringa che corrisponde ad un insieme di configurazioni, 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 configurazione 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 file di configurazione:
YAML
# app/config/config_benchmark.yml
imports:
- { resource: config_prod.yml }
framework:
profiler: { only_exceptions: false }

XML
<!-- app/config/config_benchmark.xml -->

3.1. Ricettario

317

Symfony2 documentation Documentation, Release 2

<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 modifiche, lapplicazione supporta un nuovo ambiente chiamato benchmark.
Questa nuova configurazione importa la configurazione dellambiente prod e la modifica. Cos si garantice che
lambiente sia identico a quello prod eccetto per le modifiche espressamente inserite in configurazione.
Siccome sar necessario che lambiente sia accessibile tramite browser, sar necessario creare un apposito front
controller. Baster copiare il file web/app.php nel file web/app_benchmark.php e modificare 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

Note: 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 infor
}

Gli ambienti e la cartella della cache


Symfony2 sfrutta la cache in diversi modi: la configurazione dellapplicazione, la configurazione delle rotte, i
template di Twig vengono tutti immagazzinati in oggetti PHP e salvati su file nella cartella della cache.
Normalmente questi file sono conservati principalmente nella cartella app/cache. Comunque ogni ambiente
usa il suo proprio insieme di file della cache:
app/cache/dev
app/cache/prod

318

- cartella per la cache dellambiente *dev*


- cartella per la cache dellambiente *prod*

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Alcune volte, durante il debug, pu essere utile poter controllare i file 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 file:
appDevDebugProjectContainer.php - il contenitore di servizi salvato in cache che rappresenta
la configurazione dellapplicazione;
appdevUrlGenerator.php - la classe PHP generata a partire dalla configurazione 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 Configurare parametri esterni nel contenitore dei servizi.

3.1.25 Configurare parametri esterni nel contenitore dei servizi


Nel capitolo Come padroneggiare e creare nuovi ambienti, si visto come gestire la configurazione
dellapplicazione. Alle volte potrebbe essere utile, per lapplicazione, salvare alcune credenziali al di fuori del
codice del progetto. Ad esempio la configurazione dellaccesso alla base dati. La flessibilit del contenitore dei
servizi di symfony permette di farlo in modo agevole.
Variabili dambiente
Symfony recupera qualsiasi variabile dambiente, il cui prefisso 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 configurate utilizzando la
seguente configurazione 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>

Note: Il precedente esempio relativo alla configurazione di Apache e utilizza la direttiva SetEnv. Comunque,
lo stesso concetto si applica a qualsiasi server web che supporti la configurazione 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

3.1. Ricettario

319

Symfony2 documentation Documentation, Release 2

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 prefisso 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
<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 definirne 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"
>

<parameters>
<parameter key="valore.costante.globale" type="constant">COSTANTE_GLOBALE</parameter>
<parameter key="mia_classe.valore.constante" type="constant">Mia_Classe::NOME_COSTANT
</parameters>
</container>

Note: Per funzionare necessario che la configurazione usi lXML. Se non si sta usando lXML, per sfruttare
questa funzionalit, basta importarne uno:
// app/config/config.yml
imports:
- { resource: parametri.xml }

320

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Configurazioni varie
La direttiva import pu essere usata per importare parametri conservati in qualsiasi parte. Importare un file PHP
permette di avere la flessibilit di aggiungere qualsiasi cosa sia necessaria al contenitore. Il seguente esempio
importa un file 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);

Note: Un file 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 configuri. Questo specialmente
utile nel caso si voglia importare una configurazione con formato non standard. Il seguente esempio importa la
configurazione 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);

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 specificare sia i parametri da passare al costruttore, sia i metodi di chiamata, che i parametri di configurazione. Alle volte, per, questo non sufficiente 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 specifico metodo nel factory, invece che inizializzare direttamente loggetto.
Supponiamo di avere un factory che configura e restituisce un oggetto GestoreNewsletter:
namespace Acme\HelloBundle\Newsletter;
class NewsletterFactory
{
public function get()
{
$gestoreNewsletter = new GestoreNewsletter();
// ...
return $gestoreNewsletter;
}
}

3.1. Ricettario

321

Symfony2 documentation Documentation, Release 2

Per rendere disponibile, in forma di servizio, loggetto GestoreNewsletter, possibile configurare 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</
<parameter key="newsletter_factory.class">Acme\HelloBundle\Newsletter\NewsletterFactory</
</parameters>
<services>
<service id="gestore_newsletter"
class="%gestore_newsletter.class%"
factory-class="%newsletter_factory.class%"
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\GestoreNews
$container->setParameter(newsletter_factory.class, Acme\HelloBundle\Newsletter\NewsletterF
$container->setDefinition(gestore_newsletter, new Definition(
%gestore_newsletter.class%
))->setFactoryClass(
%newsletter_factory.class%
)->setFactoryMethod(
get
);

Quando si specifica 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 configurare 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:

322

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

class:
factory_service:
factory_method:

%gestore_newsletter.class%
newsletter_factory
get

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->


<parameters>
<!-- ... -->
<parameter key="gestore_newsletter.class">Acme\HelloBundle\Newsletter\GestoreNewsletter</
<parameter key="newsletter_factory.class">Acme\HelloBundle\Newsletter\NewsletterFactory</
</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\GestoreNews
$container->setParameter(newsletter_factory.class, Acme\HelloBundle\Newsletter\NewsletterF
$container->setDefinition(newsletter_factory, new Definition(
%newsletter_factory.class%
))
$container->setDefinition(gestore_newsletter, new Definition(
%gestore_newsletter.class%
))->setFactoryService(
newsletter_factory
)->setFactoryMethod(
get
);

Note: Il servizio del factory viene specificato 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%

3.1. Ricettario

323

Symfony2 documentation Documentation, Release 2

gestore_newsletter:
class:
factory_service:
factory_method:
arguments:
-

%gestore_newsletter.class%
newsletter_factory
get
@templating

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->


<parameters>
<!-- ... -->
<parameter key="gestore_newsletter.class">Acme\HelloBundle\Newsletter\GestoreNewsletter</
<parameter key="newsletter_factory.class">Acme\HelloBundle\Newsletter\NewsletterFactory</
</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" />
</service>
</services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;

// ...
$container->setParameter(gestore_newsletter.class, Acme\HelloBundle\Newsletter\GestoreNews
$container->setParameter(newsletter_factory.class, Acme\HelloBundle\Newsletter\NewsletterF
$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 configurare le proprie dipendenze:
namespace Acme\HelloBundle\Mail;
use Acme\HelloBundle\Mailer;
use Acme\HelloBundle\FormattatoreMail;
class GestoreNewsletter
{
protected $mailer;

324

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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;
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 configurazione 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] ]

3.1. Ricettario

325

Symfony2 documentation Documentation, Release 2

XML

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->


<parameters>
<!-- ... -->
<parameter key="gestore_newsletter.class">Acme\HelloBundle\Mail\GestoreNewsletter</parame
<parameter key="gestore_biglietto_auguri.class">Acme\HelloBundle\Mail\GestoreBigliettoAug
</parameters>
<services>
<service id="mio_mailer" ... >
<!-- ... -->
</service>
<service id="mio_formattatore_mail" ... >
<!-- ... -->
</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\GestoreBigl
$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 configurazione. Questo vuol dire che se qualcosa viene cambiato, ad esempio le classi Mailer o FormattatoreMail che dovranno essere iniettate tramite il costruttore,
sar necessario modificare la configurazione in due posti. Allo stesso modo, se si volesse modificare il metodo
326

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

setter, sarebbe necessario modificare 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
{
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 configurazione in modo da poter ridurre le ripetizioni specificando 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] ]

3.1. Ricettario

327

Symfony2 documentation Documentation, Release 2

gestore_newsletter:
class:
%gestore_newsletter.class%
parent: gestore_mail
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</parame
<parameter key="gestore_biglietto_auguri.class">Acme\HelloBundle\Mail\GestoreBigliettoAug
<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="g
</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\GestoreBigl
$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(
%gestore_newsletter.class%

328

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

);
$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 figli. Nello specifico, i metodi setter definiti nel servizio padre verranno
chiamati quando i servizi figli saranno istanziati.
Note: Rimuovendo la chiave di configurazione parent i servizi verranno comunque istanziati e estenderanno
comunque la classe GestoreMail. La differenza che, omettendo la chiave di configurazione parent, le
chiamate definite nel servizio gestore_mail non saranno eseguite quando i servizi figli saranno istanziati.
La classe padre astratta e dovrebbe essere istanziata direttamente. Configurarla come astratta nel file di configurazione, 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 figlio.
Fortunatamente, aggiungendo la configurazione della chiamata al metodo per il servizio figlio, le dipendenze
configurate nella classe padre verranno sostituite. Perci, nel caso si volesse passare una dipendenza diversa solo
per la classe GestoreNewsletter, la configurazione 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 ] ]
gestore_biglietto_auguri:
class:
%gestore_biglietto_auguri.class%
parent: gestore_mail

XML

3.1. Ricettario

329

Symfony2 documentation Documentation, Release 2

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->


<parameters>
<!-- ... -->
<parameter key="gestore_newsletter.class">Acme\HelloBundle\Mail\GestoreNewsletter</parame
<parameter key="gestore_biglietto_auguri.class">Acme\HelloBundle\Mail\GestoreBigliettoAug
<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="g
</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\GestoreBigl
$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(
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)

330

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

));
$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 definizione del padre e una nella definizione del figlio. Nel precedente esempio la cosa va bene, visto
che la chiamata al secondo setMailer sostituisce loggetto mailer configurato 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 configurazione:


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:
# ...
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
3.1. Ricettario

331

Symfony2 documentation Documentation, Release 2

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->


<parameters>
<!-- ... -->
<parameter key="gestore_newsletter.class">Acme\HelloBundle\Mail\GestoreNewsletter</parame
<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(
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 filtri nella sotto classe. Ma se si volesse sostituire il filtro passato alla sotto classe, la rimozione della configurazione della classe padre eviter che la
classe base chiami il metodo setFiltro.

332

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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 predefinito): 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 definisce 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.
Scope e dipendenze
Si immagini di aver configurato un servizio posta. Non stato configurato 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.

Note: Ovviamente, un servizio pu dipendere senza alcun problema da un altro servizio che abbia uno scope pi
ampio, .

Impostare lo scope nella definizione


Lo scope di un servizio definito nella definizione del servizio stesso:
YAML

3.1. Ricettario

333

Symfony2 documentation Documentation, Release 2

# 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" sco
</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);

Se non si specifica 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
}
}

Caution: 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 configurazione del servizio per questa classe assomiglia a questa:
334

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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>
<!-- ... -->
<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))
));

Note: 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 definisce 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 notifiche 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 fino a che una non risulti efficace. 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 fino a che uno non abbia successo. Questo articolo si focalizza fondamentalmente
sullargomento delliniezione di dipendenze.
3.1. Ricettario

335

Symfony2 documentation Documentation, Release 2

Per iniziare si definisce la classe della ListaDiTrasporto:


namespace Acme\MailerBundle;
class ListaDiTrasporto
{
private $trasporti;
public function __construct()
{
$this->trasporti = array();
}
public function aggiungiTrasporto(\Swift_Transport
{
$this->trasporti[] = $trasporto;
}

$trasporto)

Quindi si definisce 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</pa
</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\ListaDiTrasp

$container->setDefinition(acme_mailer.lista_trasporto, new Definition(%acme_mailer.lista_t

Definire 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

336

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

arguments:
- %mailer_host%
tags:
- { name: acme_mailer.transport }
acme_mailer.transport.sendmail:
class: \Swift_SendmailTransport
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 definire 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:

3.1. Ricettario

337

Symfony2 documentation Documentation, Release 2

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 definizione 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.
Note: Per convenzione, in nomi di etichette sono formati dal nome del bundle(minuscolo con il trattino basso
come separatore), seguito dal punto e, infine, dal nome reale: perci, letichetta transport di AcmeMailerBundle sar acme_mailer.transport.

Definizione 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 file /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\ListaDiTra
$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 file. Solitamente, i siti web di dimensioni medio grandi utilizzano la basi dati, invece dei file, per salvare i dati di sessione.
Questo perch le basi dati sono pi semplici da utilizzare e sono pi scalabili in ambienti multi-webserver.
Symfony2 ha, al suo interno, una soluzione per larchiviazione delle sessioni su base dati, chiamata
Symfony\Component\HttpFoundation\SessionStorage\PdoSessionStorage. Per utilizzarla
sufficiente cambiare alcuni parametri di config.yml (o del proprio formato di configurazione):

338

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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\


<argument type="service" id="pdo" />
<argument>%session.storage.options%</argument>
<argument>%pdo.db_options%</argument>
</service>
</services>

PHP
// app/config/config.yml
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$container->loadFromExtension(framework, array(

3.1. Ricettario

339

Symfony2 documentation Documentation, Release 2

// ...
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\PdoSessi


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 configurazione, i parametri della connessione alla base dati sono definiti 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 configurazione della base dati:
YAML
pdo:
class: PDO
arguments:
- "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

340

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

$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 definita, che pu ospitare qualsiasi cosa, dalle classi ai controllori
e alle risorse web. Anche se i bundle sono molto flessibili, 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 tecnici di interoperabilit di PHP 5.3 per gli spazi dei nomi e i nomi delle classi: inizia con un segmento del venditore, seguito da
zero o pi segmenti di categoria e finisce con il nome breve dello spazio dei nomi, che deve terminare col suffisso
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 prefisso con il nome del venditore (e, opzionalmente, con gli spazi dei nomi della categoria);
Aggiungere il suffisso Bundle.

3.1. Ricettario

341

Symfony2 documentation Documentation, Release 2

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.
Note: Se si condivide pubblicamente il bundle, si deve usare il nome della classe Bundle per il repository
(AcmeBlogBundle e non BlogBundle, per esempio).

Note: I bundle del nucleo di Symfony2 non hanno il prefisso 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 riflettono la struttura dello spazio dei nomi del bundle.
I seguenti file sono obbligatori:
HelloBundle.php;
Resources/meta/LICENSE: La licenza completa del codice;
Resources/doc/index.rst: Il file iniziale della documentazione del bundle.
Note: Queste convenzioni assicurano che strumenti automatici possano appoggiarsi a tale struttura predefinita
nel loro funzionamento.
La profondit delle sotto-cartelle va mantenuta al minimo per le classi e i file pi usati (massimo 2 livelli). Ulteriori
livelli possono essere definiti per file meno usati e non strategici.
La cartella del bundle in sola lettura. Se occorre scrivere file temporanei, memorizzarli sotto le cartelle cache/
o log/ dellapplicazione. Degli strumenti possono generare file nella cartella del bundle, ma solo se i file generati
devono far parte del repository.
Le seguenti classi e i seguenti file hanno postazioni specifiche:

342

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Tipo
Comandi
Controllori
Estensioni del contenitore
Ascoltatori di eventi
Configurazione
Risorse Web
File di traduzione
Template
Test unitari e funzionali

Cartella
Command/
Controller/
DependencyInjection/
EventListener/
Resources/config/
Resources/public/
Resources/translations/
Resources/views/
Tests/

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 qualificato della classe Bundle\HelloBundle\Controller\HelloController.
Tutte le classi e i file 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 suffisso 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;
I test funzionali vanno usati solo per testare la risposta e alcune informazioni di profile, se se ne hanno;
La copertura del codice deve essere almeno del 95%.
Note:
Una suite di test non deve contenere script come AllTests.php, ma appoggiarsi a un file
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 file Resources/doc/index.rst lunico file obbligatorio e deve essere il
punto di ingresso della documentazione.

3.1. Ricettario

343

Symfony2 documentation Documentation, Release 2

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 .
Note: 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 prefisso lalias del bundle. Per esempio, per AcmeBlogBundle,
tutte le rotte devono avere come prefisso 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 definiti in formato XLIFF; il dominio deve avere il
nome del bundle (bundle.hello).
Un bundle non deve sovrascrivere messaggi esistenti in altri bundle.
Configurazione
Per fornire maggiore flessibilit, un bundle pu fornire impostazioni configurabili, usando i meccanismi di Symfony2.
Per semplici impostazioni di configurazione, appoggiarsi alla voce predefinita parameters della configurazione
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).
Lutente finale pu fornire valori in qualsiasi file di configurazione:
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);

344

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

INI
[parameters]
acme_hello.email.from = fabien@example.com

Recuperare i parametri di configurazione nel proprio codice dal contenitore:


$container->getParameter(acme_hello.email.from);

Pur essendo questo meccanismo abbastanza semplice, si consiglia caldamente luso della configurazione semantica, descritta nel ricettario.
Note: Se si definiscono servizi, deve avere anche essi come prefisso lalias del bundle.

Imparare di pi dal ricettario


Come esporre una configurazione 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 file in
quel bundle con un file del proprio bundle. Symfony fornisce un modo molto conveniente per sovrascrivere cose
come controllori, template, traduzioni e altri file nella cartella Resources/ di un bundle.
Per esempio, si supponga di aver installato 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 file 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()
{
return FOSUserBundle;
}
}

Con questa semplice modifica, si possono ora sovrascrivere diverse parti di FOSUserBundle, semplicemente
creando un file 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, ridefinire 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()

3.1. Ricettario

345

Symfony2 documentation Documentation, Release 2

{
$response = parent::registerAction();
// do custom stuff
return $response;
}
}

Tip:
A seconda di quanto si vuole cambiare il comportamento, si potrebbe voler richiamare
parent::registerAction() oppure sostituirne completamente la logica con una propria.

Note: 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 file 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 file si trova in
Resources/views/layout.html.twig di FOSUserBundle, si pu creare il proprio file nello stesso
posto in AcmeUserBundle. Symfony ignorer completamente il file dentro FOSUserBundle e user il nuovo
file al suo posto.
Lo stesso vale per i file delle rotte, della configurazione della validazione e di altre risorse.
Note:
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.
Caution: I file delle traduzioni non funzionano nel modo descritto sopra. Tutti i file di traduzione sono
raggruppati in un insieme di domini di pool (uno per ciascuno). Symfony carica i file 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 & configurazione
Entit & mappatura di entit
Form
Validazione
346

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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 configurazione semantica per un bundle


Se si apre il file di configurazione della propria applicazione (di solito app/config/config.yml), si vedranno un certo numero di spazi di nomi di configurazioni, come framework, twig e doctrine. Ciasucno
di questi configura uno specifico bundle, consentendo di configurare cose ad alto livello e quindi lasciando al
bundle tutte le modifiche complesse e di basso livello.
Per esempio, il codice seguente dice a FrameworkBundle di abilitare lintegrazione con i form, che implica la
definizione di alcuni servizi, cos come anche lintegrazione di altri componenti correlati:
YAML
framework:
# ...
form:

true

XML
<framework:config>
<framework:form />
</framework:config>

PHP
$container->loadFromExtension(framework, array(
// ...
form
=> true,
// ...
));

Quando si crea un bundle, si hanno due scelte sulla gestione della configurazione:
1. Normale configurazione di servizi (facile):
Si possono specificare i propri servizi in un file di configurazione (p.e. services.yml) posto
nel proprio bundle e quindi importarlo dalla configurazione principale della propria applicazione.
Questo molto facile, rapido ed efficace. Se si usano i parametri, si avr ancora la flessibilit di
personalizzare il bundle dalla configurazione della propria applicazione. Vedere Importare la
configurazione con imports per ulteriori dettagli.
2. Esporre una configurazione semantica (avanzato):
Questo il modo usato per la configurazione dei bundle del nucleo (come descritto sopra). Lidea
di base che, invece di far sovrascrivere allutente i singoli parametri, lasciare che ne configuri
alcune opzioni create specificatamente. Lo sviluppatore del bundle deve quindi analizzare tale
configurazione e caricare i servizi allinterno di una classe Extension. Con questo metodo, non
si avr bisogno di importare alcuna risorsa di configurazione dallappplicazione principale: la
classe Extension pu gestire tutto.
La seconda opzione, di cui parleremo, molto pi flessibili, 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 definire semplici parametri: un valore specifico di unopzione pu scatenare la
creazioni di molte definizioni di servizi;
Possibilit di avere una gerarchia di configurazioni

3.1. Ricettario

347

Symfony2 documentation Documentation, Release 2

Fusione intelligente quando diversi file di configurazione (p.e. config_dev.yml e config.yml)


sovrascrivono le proprie configurazioni a vicenda;
Validazione della configurazione (se si usa una classe di configurazione);
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
configurabile sia presente nella configurazione messa a disposizione da tale classe. In altre parole, la classe
Extension definisce tutte le impostazioni supportate pubblicamente, per i quali sar mantenuta una retrocompatibilit.

Creare una classe Extension


Se si sceglie di esporre una configurazione 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 postfisso Bundle del nome della classe
del bundle con Extension. Per esempio, la classe Extension di AcmeHelloBundle si chiamerebbe
AcmeHelloExtension:
// 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/;
}
}

Note: I metodi getXsdValidationBasePath e getNamespace servono solo se il bundle fornisce degli


XSD facoltativi per la configurazione.
La presenza della classe precedente vuol dire che si pu definire uno spazio dei nomi acme_hello in un qualsiasi file di configurazione. 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 specificando la configurazione sotto questo spazio dei nomi:
YAML
# app/config/config.yml
acme_hello: ~

348

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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
<acme_hello:config />
...
</container>

PHP
// app/config/config.php
$container->loadFromExtension(acme_hello, array());

Tip: 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 configurazione (cio se la voce acme_hello non appare mai), il metodo load() sar
richiamato, passandogli un array $configs vuoto. Si possono comunque fornire valori predefiniti 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 file di configurazione, la configurazione
sotto di esso viene aggiunta a un array di configurazioni e passata al metodo load() dellestensione (Symfony2
converte automaticamente XML e YAML in array).
Si prenda la seguente configurazione:
YAML
# app/config/config.yml
acme_hello:
foo: fooValue
bar: barValue

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
<acme_hello:config foo="fooValue">
<acme_hello:bar>barValue</acme_hello:bar>
</acme_hello:config>
</container>

PHP
// app/config/config.php
$container->loadFromExtension(acme_hello, array(
foo => fooValue,

3.1. Ricettario

349

Symfony2 documentation Documentation, Release 2

bar => barValue,


));

Larray passato al metodo load() sar simile a questo:


array(
array(
foo => fooValue,
bar => barValue,
)
)

Si noti che si tratta di un array di array, non di un semplice array di valori di configurazione. stato fatto intenzionalmente. Per esempio, se acme_hello appare in un altro file di configurazione, come config_dev.yml,
con valori diversi sotto di esso, larray in uscita sar simile a questo:
array(
array(
foo
bar
),
array(
foo
baz
),
)

=> fooValue,
=> barValue,

=> fooDevValue,
=> newConfigEntry,

Lordine dei due array dipende da quale stato definito prima. compito di chi sviluppa il bundle, quindi, decidere
in che modo tali configurazioni 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 Configuration, 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
}

Caution: 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 configurazione 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 configurare ogni metodo o servizio necessario per il
proprio bundle.
Caricare risorse di configurazioni esterne

Una cosa che si fa di solito caricare un file di configurazione esterno, che potrebbe contenere i servizi necessari al proprio bundle. Per esempio, si supponga di avere un file services.xml, che contiene molte delle
configurazioni di servizio del proprio bundle:

350

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

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);
}

Lo si potrebbe anche con una condizione, basata su uno dei valori di configurazione. 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);
}
}

Configurare servizi e impostare parametri

Una volta caricati alcune configurazioni di servizi, si potrebbe aver bisogno di modificare la configurazione in base
ad alcuni valori inseriti. Per esempio, si supponga di avere un servizio il cui primo parametro una stringa type,
che sar usata internamente. Si vorrebbe che fosse facilmente configurata dallutente del bundle, quindi nella
proprio file di configurazione del servizio (services.xml), si definisce questo servizio e si usa un parametro
vuoto, come acme_hello.my_service_type, come primo parametro:

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->


<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/servi
<parameters>
<parameter key="acme_hello.my_service_type" />
</parameters>
<services>
<service id="acme_hello.my_service" class="Acme\HelloBundle\MyService">
<argument>%acme_hello.my_service_type%</argument>
</service>
</services>
</container>

Ma perch definire un parametro vuoto e poi passarlo al proprio servizio? La risposa che si imposter questo
parametro nella propria classe Extension, in base ai valori di configurazione in entrata. Si supponga, per esempio,
di voler consentire allutente di definire questa opzione type sotto una chiave di nome my_type. Aggiungere al
metodo load() il codice seguente:
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);

3.1. Ricettario

351

Symfony2 documentation Documentation, Release 2

if (!isset($config[my_type])) {
throw new \InvalidArgumentException(The "my_type" option must be set);
}
$container->setParameter(acme_hello.my_service_type, $config[my_type]);
}

Lutente ora in grado di configurare effettivamente il servizio, specificando il valore di configurazione my_type:
YAML
# app/config/config.yml
acme_hello:
my_type: foo
# ...

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
<acme_hello:config my_type="foo">
<!-- ... -->
</acme_hello:config>
</container>

PHP
// app/config/config.php
$container->loadFromExtension(acme_hello, array(
my_type => foo,
// ...
));

Parametri globali

Quando si configura il contenitore, si hanno a disposizione i seguenti parametri globali:


kernel.name
kernel.environment
kernel.debug
kernel.root_dir
kernel.cache_dir
kernel.logs_dir
kernel.bundle_dirs
kernel.bundles
kernel.charset
Caution: Tutti i nomi di parametri e di servizi che iniziano con _ sono riservati al framework e non se ne
dovrebbero definire altri nei bundle.

352

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Validazione e fusione con una classe Configuration


Finora, la fusione degli array di configurazione stata fatta a mano, verificando la presenza di valori di configurazione con la funzione isset() di PHP. Un sistema opzionale Configuration disponibile, per aiutare nella
fusione, nella validazione, con i valori predefiniti e per la normalizzazione dei formati.
Note: La normalizzazione dei formati riguarda alcuni formati, soprattutto XML, che offrono array di configurazione leggermente diversi, per cui tali array hanno bisgno di essere normalizzati, per corrispondere a tutti gli
altri.
Per sfruttare questo sistema, si creer una classe Configuration e si costruir un albero, che definisce la
propria configurazione in tale classe:
// src/Acme/HelloBundle/DependencyExtension/Configuration.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root(acme_hello);
$rootNode
->children()
->scalarNode(my_type)->defaultValue(bar)->end()
->end()
;
return $treeBuilder;
}

Questo un esempio molto semplice, ma si pu ora usare questa classe nel proprio metodo load(), per fondere
la propria configurazione e forzare la validazione. Se viene passata unopzione che non sia my_type, lutente
sar avvisato con uneccezione del passaggio di unopzione non supportata:
use Symfony\Component\Config\Definition\Processor;
// ...
public function load(array $configs, ContainerBuilder $container)
{
$processor = new Processor();
$configuration = new Configuration();
$config = $processor->processConfiguration($configuration, $configs);
// ...
}

Il metodo processConfiguration() usa lalbero di configurazione definito nella classe Configuration


per validare, normalizzare e fondere tutti gli array di configurazione insieme.
La classe Configuration pu essere molto pi complicata di quanto mostrato qui, poich supporta nodi array,
nodi prototipo, validazione avanzata, normalizzazione specifica di XML e fusione avanzata. Il modo migliore
per vederla in azione guardare alcune classi Configuration del nucleo, come quella FrameworkBundle o di
TwigBundle.

3.1. Ricettario

353

Symfony2 documentation Documentation, Release 2

Esportare la configurazione predefinita

New in version 2.1: Il comando config:dump-reference stato aggiunto in Symfony 2.1 Il comando
config:dump-reference consente di mostrare nella console, in formato YAML, la configurazione predefinita di un bundle.
Il comando funziona automaticamente solo se la configurazione del bundle si trova nella posizione standard (MioBundle\DependencyInjection\Configuration) e non ha un __constructor().
Se si ha qualcosa di diverso, la propria classe Extension dovr sovrascrivere il metodo
Extension::getConfiguration() e restituire unistanza di Configuration.
Si possono aggiungere commenti ed esempi alla configurazione, usando i metodi ->setInfo() e
->setExample():
// src/Acme/HelloBundle/DependencyExtension/Configuration.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root(acme_hello);
$rootNode
->children()
->scalarNode(mio_tipo)
->defaultValue(bar)
->setInfo(cosa configura mio_tipo)
->setExample(impostazione di esempio)
->end()
->end()
;
return $treeBuilder;
}

Il testo apparir come commenti YAML nelloutput del comando config:dump-reference.


Convenzioni per lestensione
Quando si crea unestensione, seguire queste semplici convenzioni:
Lestensione deve trovarsi nel sotto-spazio dei nomi DependencyInjection;
lestensione deve avere lo stesso nome del bundle, ma con Extension (AcmeHelloExtension per
AcmeHelloBundle);
Lestensione deve fornire uno schema XSD.
Se si seguono queste semplici convenzioni, la propria estensione sar registrata automaticamente da Symfony2. In
caso contrario, sovrascrivere il metodo :method:Symfony\\Component\\HttpKernel\\Bundle\\Bundle::build
nel proprio bundle:
use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass;
class AcmeHelloBundle extends Bundle
{
public function build(ContainerBuilder $container)
{

354

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

parent::build($container);
// registrare a mano estensioni che non seguono le convenzioni
$container->registerExtension(new UnconventionalExtensionClass());
}
}

In questo caso, la classe Extension deve implementare anche un metodo getAlias() e restituire un alias univoco, con nome che dipende dal bundle (p.e. acme_hello). Questo perch il nome della classe non segue le
convenzioni e non finisce per Extension.
Inoltre, il metodo load() dellestensione sar richiamato solo se lutente specifica lalias acme_hello in
almeno un file di configurazione. Ancora, questo perch la classe Extension non segue le convenzioni viste sopra,
quindi non succede nulla in modo automatico.

3.1.35 Come spedire unemail


Spedire le email un delle azioni classiche di ogni applicazione web ma rappresenta anche lorigine di potenziali problemi e complicazioni. Invece di reinventare la ruota, una soluzione per linvio di email luso di
SwiftmailerBundle, il quale sfrutta la potenza della libreria Swiftmailer .
Note: Non dimenticatevi di abilitare il bundle allinterno del kernel prima di utilizzarlo:
public function registerBundles()
{
$bundles = array(
// ...
new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
);
// ...
}

Configurazione
Prima di utilizzare Swiftmailer, assicuratevi di includerne la configurazione. Lunico parametro obbligatorio della
configurazione il parametro transport:
YAML
# app/config/config.yml
swiftmailer:
transport: smtp
encryption: ssl
auth_mode: login
host:
smtp.gmail.com
username:
tuo_nome_utente
password:
tua_password

XML
<!-- app/config/config.xml -->

<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmail
-->
<swiftmailer:config
transport="smtp"

3.1. Ricettario

355

Symfony2 documentation Documentation, Release 2

encryption="ssl"
auth-mode="login"
host="smtp.gmail.com"
username="tuo_nome_utente"
password="tua_password" />

PHP
// app/config/config.php
$container->loadFromExtension(swiftmailer, array(
transport => "smtp",
encryption => "ssl",
auth_mode => "login",
host
=> "smtp.gmail.com",
username
=> "tuo_nome_utente",
password
=> "tua_password",
));

La maggior parte della configurazione di Swiftmailer relativa al come i messaggi debbano essere inoltrati.
Sono disponibili i seguenti parametri di configurazione:
transport (smtp, mail, sendmail, o gmail)
username
password
host
port
encryption (tls, o ssl)
auth_mode (plain, login, o cram-md5)
spool
type (come accodare i messaggi: attualmente solo lopzione file supportata)
path (dove salvare i messaggi)
delivery_address (indirizzo email dove spedire TUTTE le email)
disable_delivery (impostare a true per disabilitare completamente linvio)
Linvio delle email
Per lavorare con la libreria Swiftmailer dovrete creare, configurare e quindi spedire oggetti di tipo
Swift_Message. Il mailer il vero responsabile dellinvio dei messaggi ed accessibile tramite il servizio
mailer. In generale, spedire unemail abbastanza intuitivo:
public function indexAction($name)
{
$messaggio = \Swift_Message::newInstance()
->setSubject(Hello Email)
->setFrom(mittente@example.com)
->setTo(destinatario@example.com)
->setBody($this->renderView(HelloBundle:Hello:email.txt.twig, array(nome => $nome)))
;
$this->get(mailer)->send($messaggio);
return $this->render(...);
}

Per tenere i vari aspetti separati, il corpo del messaggio stato salvato in un template che viene poi restituito
tramite il metodo renderView().
356

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Loggetto $messaggio supporta molte altre opzioni, come laggiunta di allegati, linserimento di HTML e molto
altro. Fortunatamente la documentazione di Swiftmailer affronta questo argomento dettagliatamente nel capitolo
sulla Creazione di Messaggi .
Tip: Diversi altri articoli di questo ricettario spiegano come spedire le email grazie Symfony2:
Come usare Gmail per linvio delle email
email/dev_environment
email/spool

3.1.36 Come usare Gmail per linvio delle email


In fase di sviluppo, invece di utilizzare un normale server SMTP per linvio delle email, potrebbe essere pi
semplice e pratico usare Gmail. Il bundle Swiftmailer ne rende facilissimo lutilizzo.
Tip: Invece di usare un normale account di Gmail, sarebbe meglio crearne uno da usare appositamente per questo
scopo.
Nel file di configurazione dellambiente di sviluppo, si assegna al parametro transport lozione gmail e ai
parametri username e password le credenziali dellaccount di Google:
YAML
# app/config/config_dev.yml
swiftmailer:
transport: gmail
username: nome_utente_gmail
password: password_gmail

XML
<!-- app/config/config_dev.xml -->

<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmail
-->
<swiftmailer:config
transport="gmail"
username="nome_utente_gmail"
password="password_gmail" />

PHP
// app/config/config_dev.php
$container->loadFromExtension(swiftmailer, array(
transport => "gmail",
username => "nome_utente_gmail",
password => "password_gmail",
));

E il gioco fatto!
Note: Lattributo di trasporto gmail in realt una scorciatoia che imposta a smtp il trasporto, e modifica
encryption, auth_mode e host in modo da poter comunicare con Gmail.

3.1. Ricettario

357

Symfony2 documentation Documentation, Release 2

3.1.37 Lavorare con le email durante lo sviluppo


Durante lo sviluppo di applicazioni che inviino email, non sempre desiderabile che le email vengano inviate
alleffettivo destinatario del messaggio. Se si utilizza SwiftmailerBundle con Symfony2, possibile evitarlo
semplicemente modificano i parametri di configurazione, senza modificare alcuna parte del codice. Ci sono due
possibili scelte quando si tratta di gestire le email in fase di sviluppo: (a) disabilitare del tutto linvio delle email
o (b) inviare tutte le email a uno specifico indirizzo.
Disabilitare linvio
possibile disabilitare linvio delle email, ponendo true nellopzione disable_delivery. Questa la
configurazione predefinita per lambiente test della distribuzione Standard. Facendo questa modifica nellambiente
test le email non verranno inviate durante lesecuzione dei test ma continueranno a essere inviate negli ambienti
prod e dev:
YAML
# app/config/config_test.yml
swiftmailer:
disable_delivery: true

XML
<!-- app/config/config_test.xml -->

<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmail
-->
<swiftmailer:config
disable-delivery="true" />

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

Se si preferisce disabilitare linvio anche nellambiente dev, baster aggiungere la stessa configurazione nel file
config_dev.yml.
Invio a uno specifico indirizzo
possibile anche scegliere di inviare le email a uno specifico indirizzo, invece che a quello effettivamente specificato nellinvio del messaggio. Ci si pu fare tramite lopzione delivery_address:
YAML
# app/config/config_dev.yml
swiftmailer:
delivery_address: dev@example.com

XML
<!-- app/config/config_dev.xml -->

<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmail
-->

358

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

<swiftmailer:config
delivery-address="dev@example.com" />

PHP
// app/config/config_dev.php
$container->loadFromExtension(swiftmailer, array(
delivery_address => "dev@example.com",
));

Supponiamo di inviare unemail a destinatario@example.com.


public function indexAction($name)
{
$message = \Swift_Message::newInstance()
->setSubject(Email di saluto)
->setFrom(mittente@example.com)
->setTo(destinatario@example.com)
->setBody($this->renderView(HelloBundle:Hello:email.txt.twig, array(name => $name)))
;
$this->get(mailer)->send($message);
return $this->render(...);
}

Nellambiente dev, lemail verr in realt inviata a dev@example.com. Swiftmailer aggiunger unulteriore
intestazione nellemail, X-Swift-To, contenente lindirizzo sostituito, cos da poter vedere a chi sarebbe stata
inviata lemail in realt.
Note: Oltre alle email inviate allindirizzo to, questa configurazione blocca anche quelle inviate a qualsiasi indirizzo CC e BCC. Swiftmailer aggiunger ulteriori intestazioni contenenti gli
indirizzi ignorati. Le intestazioni usate saranno X-Swift-Cc e X-Swift-Bcc
rispettivamente per gli indirizzi in CC e per quelli in BCC.

Visualizzazione tramite Web Debug Toolbar


Utilizzando la Web Debug Toolbar possibile visualizzare le email inviate durante la singola risposta
nellambiente dev. Licona dellemail apparir nella barra mostrando quante email sono state spedite. Cliccandoci sopra, un report mostrer il dettaglio delle email inviate.
Se si invia unemail e immediatamente si esegue un redirect a unaltra pagina, la barra di debug del web non
mostrer n licona delle email n alcun report nella pagina finale.
per possibile, configurando a true lopzione intercept_redirects nel file config_dev.yml, fermare il redirect in modo da permettere la visualizzazione del report con il dettaglio delle email inviate.
Tip: Alternativamente possibile aprire il profiler in seguito al redirect e cercare lURL utilizzato nella richiesta
precedente (p.e. /contatti/gestione). Questa funzionalit di ricerca del profiler permette di ottenere
informazioni relative a qualsiasi richiesta pregressa.
YAML
# app/config/config_dev.yml
web_profiler:
intercept_redirects: true

XML

3.1. Ricettario

359

Symfony2 documentation Documentation, Release 2

<!-- app/config/config_dev.xml -->

<!-- xmlns:webprofiler="http://symfony.com/schema/dic/webprofiler" -->


<!-- xsi:schemaLocation="http://symfony.com/schema/dic/webprofiler http://symfony.com/schema/
<webprofiler:config
intercept-redirects="true"
/>

PHP
// app/config/config_dev.php
$container->loadFromExtension(web_profiler, array(
intercept_redirects => true,
));

3.1.38 Lo spool della posta


Quando si utilizza SwiftmailerBundle per linvio delle email da unapplicazione Symfony2, queste vengono
inviate immediatamente. per possibile evitare il rallentamento dovuto dalla comunicazione tra Swiftmailer
e il servizio di trasporto delle email che potrebbe mettere lutente in attesa del caricamento della pagina durante
linvio. Per fare questo basta scegliere di mettere le email in uno spool invece di spedirle direttamente. Questo
vuol dire che Swiftmailer non cerca di inviare le email ma invece salva i messaggi in qualche posto come, ad
esempio, in un file. Unaltro processo potrebbe poi leggere lo spool e prendersi lincarico di inviare le email in
esso contenute. Attualmente Swiftmailer supporta solo lo spool tramite file.
Per usare lo spool, si usa la seguente configurazione:
YAML
# app/config/config.yml
swiftmailer:
# ...
spool:
type: file
path: /percorso/file/di/spool

XML
<!-- app/config/config.xml -->

<!-xmlns:swiftmailer="http://symfony.com/schema/dic/swiftmailer"
http://symfony.com/schema/dic/swiftmailer http://symfony.com/schema/dic/swiftmailer/swiftmail
-->
<swiftmailer:config>
<swiftmailer:spool
type="file"
path="/percorso/file/di/spool" />
</swiftmailer:config>

PHP
// app/config/config.php
$container->loadFromExtension(swiftmailer, array(
// ...
spool => array(
type => file,
path => /percorso/file/di/spool,
)
));

360

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Tip: Per creare lo spool allinterno delle cartelle del progetto, possibile usare il paramtreo %kernel.root_dir%
per indicare la cartella radice del progetto:
path: %kernel.root_dir%/spool

Fatto questo, quando unapplicazione invia unemail, questa non verr inviata subito ma aggiunta allo spool.
Linvio delle email dallo spool viene fatto da un processo separato. Sar un comando della console a inviare i
messaggi dallo spool:
php app/console swiftmailer:spool:send

possibili limitare il numero di messaggi da inviare con unapposita opzione:


php app/console swiftmailer:spool:send --message-limit=10

anche possibile indicare un limite in secondi per linvio:


php app/console swiftmailer:spool:send --time-limit=10

Ovviamente questo comando non dovr essere eseguito manualmente. Il comando dovrebbe perci essere eseguito, a intervalli regolari, come un lavoro di cron o come unoperazione pianificata.

3.1.39 Come simulare unautenticazione HTTP in un test funzionale


Se la propria applicazione necessita di autenticazione HTTP, passare il nome utente e la password come variabili
di createClient():
$client = static::createClient(array(), array(
PHP_AUTH_USER => nome_utente,
PHP_AUTH_PW
=> pa$$word,
));

Si possono anche sovrascrivere per ogni richiesta:


$client->request(DELETE, /post/12, array(), array(
PHP_AUTH_USER => nome_utente,
PHP_AUTH_PW
=> pa$$word,
));

3.1.40 Come testare linterazione con diversi client


Se occorre simulare uninterazionoe tra diversi client (si pensi a una chat, per esempio), creare tanti client:
$harry = static::createClient();
$sally = static::createClient();
$harry->request(POST, /say/sally/Hello);
$sally->request(GET, /messages);
$this->assertEquals(201, $harry->getResponse()->getStatusCode());
$this->assertRegExp(/Hello/, $sally->getResponse()->getContent());

Questo non funziona se il proprio codice mantiene uno stato globale o se dipende da librerie di terze parti che
abbiano un qualche tipo di stato globale. In questo caso, si possono isolare i client:
$harry = static::createClient();
$sally = static::createClient();
$harry->insulate();
$sally->insulate();

3.1. Ricettario

361

Symfony2 documentation Documentation, Release 2

$harry->request(POST, /say/sally/Hello);
$sally->request(GET, /messages);
$this->assertEquals(201, $harry->getResponse()->getStatusCode());
$this->assertRegExp(/Hello/, $sally->getResponse()->getContent());

Client isolati possono eseguire trasparentemente le loro richieste in un processo PHP dedicato e pulito, evitando
quindi effetti collaterali.
Tip: Essendo un client isolato pi lento, si pu mantenere un client nel processo principale e isolare gli altri.

3.1.41 Come usare il profilatore nei test funzionali


caldamente raccomandato che un test funzionale testi solo la risposta. Ma se si scrivono test funzionali che
monitorano i propri server di produzione, si potrebbe voler scrivere test sui dati di profilazione, che sono un
ottimo strumento per verificare varie cose e controllare alcune metriche.
Il profilatore di Symfony2 raccoglie diversi dati per ogni richiesta. Usare questi dati per verificare il numero
di chiamate al database, il tempo speso nel framework, eccetera. Ma prima di scrivere asserzioni, verificare sempre
che il profilatore sia effettivamente una variabile ( abilitato per impostazione predefinita in ambiente test):
class HelloControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request(GET, /hello/Fabien);
// Scrivere asserzioni sulla risposta
// ...
// Check that the profiler is enabled
if ($profile = $client->getProfile()) {
// verificare il numero di richieste
$this->assertTrue($profile->getCollector(db)->getQueryCount() < 10);
// verifica il tempo speso nel framework
$this->assertTrue( $profile->getCollector(timer)->getTime() < 0.5);
}
}
}

Se un test fallisce a causa dei dati di profilazione (per esempio, troppe query al DB), si potrebbe voler usare il
profilatore web per analizzare la richiesta, dopo che i test sono finiti. facile, basta inserire il token nel messaggio
di errore:
$this->assertTrue(
$profile->get(db)->getQueryCount() < 30,
sprintf(Verifica che ci siano meno di 30 query (token %s), $profile->getToken())
);

Caution: I dati del profilatore possono essere differenti, a seconda dellambiente (specialmente se si usa
SQLite, che configurato in modo predefinito).

Note: Le informazioni del profilatore sono disponibili anche se si isola client o se se si usa un livello HTTP per i
propri test.

362

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Tip: Leggere le API dei raccoglitori di dati per saperne di pi sulle loro interfacce.

3.1.42 Come testare i repository Doctrine


I test unitari dei repository Doctrine in un progetto Symfony non sono un compito facile. Infatti, per caricare un
repository occorre caricare le entit, un gestore di entit e un po di altre cose, come una connessione.
Per testare i propri repository, ci sono due opzioni diverse:
1. Test funzionali: includono luso di una vera connessione al database, con veri oggetti del database. Sono
facili da preparare e possono testare tutto, ma sono lenti da eseguire. Vedere Test funzionali.
2. Test unitari: i test unitari sono pi veloci da eseguire e pi precisi su cosa testare. Richiedono un po
pi di preparazione, come vedremo in questo documento. Possono testare solo metodi che, per esempio,
costruiscono query, non metodi che le eseguono effettivamente.
Test unitari
Poich Symfony e Doctrine condividono lo stesso framework di test, facile implementare test unitari nel proprio
progetto Symfony. LORM ha il suo insieme di strumenti, che facilitano i test unitari e i mock di ogni cosa di cui si
abbia bisogno, come una connessione, un gestore di entit, ecc. Usando i componenti dei test forniti da Dcotrine,
con un po di preparazione di base, si possono sfruttare gli strumenti di Doctrine per testare i propri repository.
Si tenga a mente che, se si vuole testare la reale esecuzione delle proprie query, occorrer un test funzionale
(vedere Test funzionali). I test unitari consentono solo di tesare un metodo che costruisce una query.
Preparazione

Inannzitutto, occorre aggiungere lo spazio dei nomi Doctrine\Tests al proprio autoloader:


// app/autoload.php
$loader->registerNamespaces(array(
//...
Doctrine\\Tests
));

=> __DIR__./../vendor/doctrine/tests,

Poi, occorrer preparare un gestore di entit in ogni test, in modo che Doctrine possa caricare le entit e i repository.
Poich Doctrine da solo non in grado di caricare i meta-dati delle annotazioni dalle entit, occorrer configurare
il lettore di annotazioni per poter analizzare e caricare le entit:
// src/Acme/ProductBundle/Tests/Entity/ProductRepositoryTest.php
namespace Acme\ProductBundle\Tests\Entity;
use
use
use
use

Doctrine\Tests\OrmTestCase;
Doctrine\Common\Annotations\AnnotationReader;
Doctrine\ORM\Mapping\Driver\DriverChain;
Doctrine\ORM\Mapping\Driver\AnnotationDriver;

class ProductRepositoryTest extends OrmTestCase


{
private $_em;
protected function setUp()
{
$reader = new AnnotationReader();
$reader->setIgnoreNotImportedAnnotations(true);
$reader->setEnableParsePhpImports(true);
$metadataDriver = new AnnotationDriver(

3.1. Ricettario

363

Symfony2 documentation Documentation, Release 2

$reader,
// fornisce lo spazio dei nomi delle entit che si vogliono testare
Acme\\ProductBundle\\Entity
);
$this->_em = $this->_getTestEntityManager();
$this->_em->getConfiguration()
->setMetadataDriverImpl($metadataDriver);
// consente di usare la sintassi AcmeProductBundle:Product
$this->_em->getConfiguration()->setEntityNamespaces(array(
AcmeProductBundle => Acme\\ProductBundle\\Entity
));
}
}

Guardando il codice, si pu notare:


Si estende da \Doctrine\Tests\OrmTestCase, che fornisce metodi utili per i test unitari;
Occorre preparare AnnotationReader per poter analizzare e caricare le entit;
Si crea il gestore di entit, richiamando _getTestEntityManager, che restituisce il mock di un gestore
di entit, con il mock di una connessione.
Ecco fatto! Si pronti per scrivere test unitari per i repository Doctrine.
Scrivere i test unitari

Tenere a mente che i metodi dei repository Doctrine possono essere testati solo se costruiscono e restituiscono una
query (senza eseguirla). Si consideri il seguente esempio:
// src/Acme/StoreBundle/Entity/ProductRepository
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function createSearchByNameQueryBuilder($name)
{
return $this->createQueryBuilder(p)
->where(p.name LIKE :name)
->setParameter(name, $name);
}
}

In questo esempio, il metodo restituisce unistanza di QueryBuilder. Si pu testare il risultato di questo


metodo in molti modi:
class ProductRepositoryTest extends \Doctrine\Tests\OrmTestCase
{
/* ... */
public function testCreateSearchByNameQueryBuilder()
{
$queryBuilder = $this->_em->getRepository(AcmeProductBundle:Product)
->createSearchByNameQueryBuilder(foo);
$this->assertEquals(p.name LIKE :name, (string) $queryBuilder->getDqlPart(where));
$this->assertEquals(array(name => foo), $queryBuilder->getParameters());

364

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

}
}

In questo test, si disseziona loggetto QueryBuilder, cercando che ogni parte sia come ci si aspetta. Se si
aggiungessero altre cose al costruttore di query, si potrebbero verificare le parti DQL: select, from, join,
set, groupBy, having o orderBy.
Se si ha solo un oggetto Query grezzo o se si preferisce testare la vera query, si pu testare direttamente la query
DQL:
public function testCreateSearchByNameQueryBuilder()
{
$queryBuilder = $this->_em->getRepository(AcmeProductBundle:Product)
->createSearchByNameQueryBuilder(foo);
$query = $queryBuilder->getQuery();
// testa la DQL
$this->assertEquals(
SELECT p FROM Acme\ProductBundle\Entity\Product p WHERE p.name LIKE :name,
$query->getDql()
);
}

Test funzionali
Se occorre eseguire effettivamente una query, occorrer far partire il kernel, per ottenere una connessione valida.
In questo caso, si estender WebTestCase, che rende tutto alquanto facile:
// src/Acme/ProductBundle/Tests/Entity/ProductRepositoryFunctionalTest.php
namespace Acme\ProductBundle\Tests\Entity;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ProductRepositoryFunctionalTest extends WebTestCase
{
/**
* @var \Doctrine\ORM\EntityManager
*/
private $_em;
public function setUp()
{
$kernel = static::createKernel();
$kernel->boot();
$this->_em = $kernel->getContainer()
->get(doctrine.orm.entity_manager);
}
public function testProductByCategoryName()
{
$results = $this->_em->getRepository(AcmeProductBundle:Product)
->searchProductsByNameQuery(foo)
->getResult();
$this->assertEquals(count($results), 1);
}
}

3.1. Ricettario

365

Symfony2 documentation Documentation, Release 2

3.1.43 Come aggiungere la funzionalit ricordami al login


Una volta che lutente autenticato, le sue credenziali sono solitamente salvate nella sessione. Questo vuol dire
che quando la sessione finisce, lutente sar fuori dal sito e dovr inserire nuovamente le sue informazioni di login,
la prossima volta che vorr accedere allapplicazione. Si pu consentire agli utenti di scegliere di rimanere dentro
pi a lungo del tempo della sessione, usando un cookie con lopzione remember_me del firewall. Il firewall ha
bisogno di una chiave segreta configurata, usata per codificare il contenuto del cookie. Ci sono anche molte altre
opzioni, con valori predefiniti mostrati di seguito:
YAML
# app/config/security.yml
firewalls:
main:
remember_me:
key:
aSecretKey
lifetime: 3600
path:
/
domain:
~ # Defaults to the current domain from $_SERVER

XML
<!-- app/config/security.xml -->
<config>
<firewall>
<remember-me
key="aSecretKey"
lifetime="3600"
path="/"
domain="" <!-- Defaults to the current domain from $_SERVER -->
/>
</firewall>
</config>

PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(remember_me => array(
key
=> /login_check,
lifetime
=> 3600,
path
=> /,
domain
=> , // Defaults to the current domain from $_SERVER
)),
),
));

una buona idea dare allutente la possibilit di usare o non usare la funzionalit ricordami, perch non sempre
appropriata. Il modo usuale per farlo laggiunta di un checkbox al form di login. Dando al checkbox il
nome _remember_me, il cookie sar automaticamente impostato quando il checkbox spuntato e lutente entra.
Quindi, il proprio form di login potrebbe alla fine assomigliare a questo:
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">Nome utente:</label>
<input type="text" id="username" name="_username" value="{{ last_username }}" />

366

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
<input type="checkbox" id="remember_me" name="_remember_me" checked />
<label for="remember_me">Ricordami</label>
<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">Nome utente:</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" />
<input type="checkbox" id="remember_me" name="_remember_me" checked />
<label for="remember_me">Ricordami</label>
<input type="submit" name="login" />
</form>

Lutente sar quindi automaticamente autenticato nelle sue visite successive, finch il cookie resta valido.
Costringere lutente ad autenticarsi di nuovo prima di accedere ad alcune risorse
Quando lutente torna sul sito, viene autenticato automaticamente in base alle informazioni memorizzate nel
cookie ricordami. Ci consente allutente di accedere a risorse protette, come se si fosse effettivamente autenticato prima di entrare nel sito.
In alcuni casi, si potrebbe desiderare di costringere lutente ad autenticarsi nuovamente, prima di accedere ad
alcune risorse. Per esempio, si potrebbe voler consentire un ricordami per vedere le informazioni di base di un
account, ma poi richiedere uneffettiva autenticazione prima di modificare le informazioni stesse.
Il componente della sicurezza fornisce un modo facile per poterlo fare. In aggiunta ai ruoli esplicitamente assegnati
loro, agli utenti viene dato automaticamente uno dei seguenti ruoli, a seconda di come si sono autenticati:
IS_AUTHENTICATED_ANONYMOUSLY - assegnato automaticamente a un utente che si trova in una parte
del sito protetta dal firewall, ma che non si effettivamente autenticato. Ci possibile solo se consentito
laccesso anonimo.
IS_AUTHENTICATED_REMEMBERED - assegnato automaticamente a un utente che si autenticato
tramite un cookie ricordami.
IS_AUTHENTICATED_FULLY - assegnato automaticamente a un utente che ha fornito le sue informazioni
di autenticazione durante la sessione corrente.
Si possono usare questi ruoli, oltre a quelli espliciti, per controllare laccesso.
Note:
Se si ha il ruolo IS_AUTHENTICATED_REMEMBERED, si ha anche il ruolo
IS_AUTHENTICATED_ANONYMOUSLY. Se si ha il ruolo IS_AUTHENTICATED_FULLY, si hanno anche gli
altri due ruoli. In altre parole, questi ruoli rappresentano tre livelli incrementali della forza dellautenticazione.

3.1. Ricettario

367

Symfony2 documentation Documentation, Release 2

Si possono usare questi ruoli addizionali per affinare il controllo sugli accessi a parti di un sito. Per esempio, si
potrebbe desiderare che lutente sia in grado di vedere il suo account in /account se autenticato con cookie,
ma che debba fornire le sue informazioni di accesso per poterlo modificare. Lo si pu fare proteggendo specifiche
azioni del controllore, usando questi ruoli. Lazione di modifica del controllore potrebbe essere messa in sicurezza
usando il contesto del servizio.
Nel seguente esempio, lazione consentita solo se lutente ha il ruolo IS_AUTHENTICATED_FULLY.
use Symfony\Component\Security\Core\Exception\AccessDeniedException
// ...
public function editAction()
{
if (false === $this->get(security.context)->isGranted(
IS_AUTHENTICATED_FULLY
)) {
throw new AccessDeniedException();
}
// ...
}

Si pu anche installare opzionalmente JMSSecurityExtraBundle, che pu mettere in sicurezza il controllore


tramite annotazioni:
use JMS\SecurityExtraBundle\Annotation\Secure;
/**
* @Secure(roles="IS_AUTHENTICATED_FULLY")
*/
public function editAction($name)
{
// ...
}

Tip: Se si avesse anche un controllo di accesso nella propria configurazione della sicurezza, che richiede
allutente il ruolo ROLE_USER per poter accedere allarea dellaccount, si avrebbe la seguente situazione:
Se un utente non autenticato (o anonimo) tenta di accedere allarea dellaccount, gli sar chiesto di autenticarsi.
Una volta inseriti nome utente e password, ipotizzando che lutente riceva il ruolo ROLE_USER in base alla
configurazione, lutente avr il ruolo IS_AUTHENTICATED_FULLY e potr accedere a qualsiasi pagina
della sezione account, incluso il controllore editAction.
Se la sessione scade, quando lutente torna sul sito, potr accedere a ogni pagina della sezione account,
tranne per quella di modifica, senza doversi autenticare nuovamente. Tuttavia, quando prover ad accedere
al controllore editAction, sar costretto ad autenticarsi di nuovo, perch non ancora pienamente autenticato.
Per maggiori informazioni sulla messa in sicurezza di servizi o metodi con questa tecnica, vedere Proteggere
servizi e metodi di unapplicazione.

3.1.44 Come implementare i propri votanti per una lista nera di indirizzi IP
Il componente della sicurezza di Symfony2 fornisce diversi livelli per autenticare gli utenti. Uno dei livelli
chiamato voter. Un votante una classe dedicata a verificare che lutente abbia i diritti per connettersi
allapplicazione. Per esempio, Symfony2 fornisce un livello che verifica se lutente pienamente autenticato
oppure se ha dei ruoli.
A volte utile creare un votante personalizzato, per gestire un caso specifico, non coperto dal framework. In
questa sezione, si imparer come creare un votante che consenta di mettere gli utenti una lista nera, in base al loro
368

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

IP.
Linterfaccia Voter

Un votante personalizzato deve implementare Symfony\Component\Security\Core\Authorization\Voter\VoterI


che richiede i seguenti tre metodi:
interface VoterInterface
{
function supportsAttribute($attribute);
function supportsClass($class);
function vote(TokenInterface $token, $object, array $attributes);
}

Il metodo supportsAttribute() usato per verificare che il votante supporti lattributo utente dato (p.e.:
un ruolo, unACL, ecc.)
Il metodo supportsClass() usato per verificare che il votante supporti lattuale classe per il token
dellutente.
Il metodo vote() deve implementare la logica di business che verifica se lutente possa avere accesso o meno.
Questo metodo deve restituire uno dei seguenti valori:
VoterInterface::ACCESS_GRANTED: Lutente pu accedere allapplicazione
VoterInterface::ACCESS_ABSTAIN: Il votante non pu decidere se lutente possa accedere o meno
VoterInterface::ACCESS_DENIED: Lutente non pu accedere allapplicazione
In questo esempio, verificheremo la corrispondenza dellindirizzo IP dellutente con una lista nera di indirizzi. Se
lIP dellutente nella lista nera, restituiremo VoterInterface::ACCESS_DENIED, altrimenti restituiremo
VoterInterface::ACCESS_ABSTAIN, perch lo scopo del votante solo quello di negare laccesso, non
di consentirlo.
Creare un votante personalizzato
Per inserire un utente nella lista nera in base al suo IP, possiamo usare il servizio request e confrontare
lindirizzo IP con un insieme di indirizzi IP in lista nera:
namespace Acme\DemoBundle\Security\Authorization\Voter;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class ClientIpVoter implements VoterInterface
{
public function __construct(ContainerInterface $container, array $blacklistedIp = array())
{
$this->container
= $container;
$this->blacklistedIp = $blacklistedIp;
}
public function supportsAttribute($attribute)
{
// non verifichiamo lattributo utente, quindi restituiamo true
return true;
}
public function supportsClass($class)
{
// il nostro votante supporta ogni tipo di classe token, quindi restituiamo true
return true;

3.1. Ricettario

369

Symfony2 documentation Documentation, Release 2

}
function vote(TokenInterface $token, $object, array $attributes)
{
$request = $this->container->get(request);
if (in_array($this->request->getClientIp(), $this->blacklistedIp)) {
return VoterInterface::ACCESS_DENIED;
}
return VoterInterface::ACCESS_ABSTAIN;
}
}

Ecco fatto! Il votante pronto. Il prossimo passo consiste nelliniettare il votante dentro al livello della sicurezza.
Lo si pu fare facilmente tramite il contenitore di servizi.
Dichiarare il votante come servizio
Per iniettare il votante nel livello della sicurezza, dobbiamo dichiararlo come servizio e taggarlo come security.voter:
YAML
# src/Acme/AcmeBundle/Resources/config/services.yml
services:
security.access.blacklist_voter:
class:
Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter
arguments: [@service_container, [123.123.123.123, 171.171.171.171]]
public:
false
tags:
{ name: security.voter }

XML
<!-- src/Acme/AcmeBundle/Resources/config/services.xml -->
<service id="security.access.blacklist_voter"
class="Acme\DemoBundle\Security\Author