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\Authorization\Voter\ClientIpVoter" public="false">
<argument type="service" id="service_container" strict="false" />
<argument type="collection">
<argument>123.123.123.123</argument>
<argument>171.171.171.171</argument>
</argument>
<tag name="security.voter" />
</service>

PHP
// src/Acme/AcmeBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$definition = new Definition(
Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter,
array(
new Reference(service_container),
array(123.123.123.123, 171.171.171.171),
),
);
$definition->addTag(security.voter);
$definition->setPublic(false);

370

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

$container->setDefinition(security.access.blacklist_voter, $definition);

Tip: Assicurarsi di importare questo file di configurazione dal proprio file di configurazione principale (p.e.
app/config/config.yml). Per ulteriori informazioni, vedere Importare la configurazione con imports. Per
saperne di pi sulla definizione di servizi in generale, vederre il capitolo Contenitore di servizi.

Cambiare la strategia decisionale per laccesso


Per far s che il votante abbia effetto, occorre modificare la strategia decisionale predefinita per laccesso, che, per
impostazione predefinita, consente laccesso se uno qualsiasi dei votanti consente laccesso.
Nel nostro caso, sceglieremo la strategia unanimous. A differenza della strategia affirmative (quella
predefinita), con la strategia unanimous, laccesso allutente negato se anche solo uno dei votanti (p.e.
ClientIpVoter) lo nega.
Per poterlo fare, sovrascrivere la sezione access_decision_manager del file di configurazione della propria
applicazione con il codice seguente.
YAML
# app/config/security.yml
security:
access_decision_manager:
# Strategy can be: affirmative, unanimous or consensus
strategy: unanimous

Ecco fatto! Ora, nella decisione sullaccesso di un utente, il nuovo votante negher laccesso a ogni utente nella
lista nera degli IP.

3.1.45 Access Control List (ACL)


In applicazioni complesse, ci si trova spesso con il problema di decisioni di accesso che non possono essere basate
solo sulla persona che lo richiede (il cosiddetto Token), ma che coinvolgono anche un oggetto del dominio per
cui laccesso viene richiesto. Qui entrano in gioco le ACL.
Si immagini di progettare un sistema di blog, in cui gli utenti possano commentare i post. Ora, si vuole che
un utente sia in grado di modificare i propri commenti, ma non quelli degli altri utenti; inoltre, si vuole che
lamministratore possa modificare tutti i commenti. In questo scenario, Comment sarebbe loggetto del dominio
a cui si vuole restringere laccesso. Si possono usare diversi approcci per ottenere questo scopo in Symfony2, due
approcci di base (non esaustivi) sono:
Gestire la sicurezza nei metodi: Di base, questo vuol dire mantenere un riferimento in ogni oggetto
Comment a tutti gli utenti che vi hanno accesso, e quindi confrontare tali utenti con quello del Token.
Gestire la sicurezza con i ruoli: In questo approccio, si aggiunge un ruolo per ogni oggetto Comment, p.e.
ROLE_COMMENT_1, ROLE_COMMENT_2, ecc.
Entrambi gli approcci sono perfettamente validi. Tuttavia, accoppiano la logica di autorizzazione al proprio codice,
il che li rende meno riutilizzabili altrove, e inoltre incrementano le difficolt nei test unitari. Daltra parte, si
potrebbero avere problemi di prestazioni, se molti utenti avessero accesso a un singolo oggetto del dominio.
Per fortuna, c un modo migliore, di cui ora parleremo.
Preparazione
Prima di entrare in azione, ci occorre una breve preparazione. Innanzitutto, occorre configurare la connessione al
sistema ACL da usare:
YAML

3.1. Ricettario

371

Symfony2 documentation Documentation, Release 2

# app/config/security.yml
security:
acl:
connection: default

XML
<!-- app/config/security.xml -->
<acl>
<connection>default</connection>
</acl>

PHP
// app/config/security.php
$container->loadFromExtension(security, acl, array(
connection => default,
));

Note: Il sistema ACL richiede almeno una connessione di Doctrine configurata. Tuttavia, questo non significa
che si debba usare Doctrine per mappare i propri oggetti del dominio. Si pu usare qualsiasi mapper si desideri
per i propri oggetti, sia esso lORM Doctrine, lODM Mongo, Propel o anche SQL puro, la scelta lasciata allo
sviluppatore.
Dopo aver configurato la connessione, occorre importare la struttura del database. Fortunatamente, c un task per
farlo. Basta eseguire il comando seguente:
php app/console init:acl

Iniziare
Tornando al piccolo esempio iniziale, implementiamo una ACL.
Creare una ACL e aggiungere un ACE
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
use Symfony\Component\Security\Acl\Permission\MaskBuilder;
// ...
// BlogController.php
public function addCommentAction(Post $post)
{
$comment = new Comment();
// preparazione di $form e collegamento dei dati
// ...
if ($form->isValid()) {
$entityManager = $this->get(doctrine.orm.default_entity_manager);
$entityManager->persist($comment);
$entityManager->flush();
// creazione dellACL
$aclProvider = $this->get(security.acl.provider);
$objectIdentity = ObjectIdentity::fromDomainObject($comment);
$acl = $aclProvider->createAcl($objectIdentity);

372

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

// recupero dellidentit di sicurezza dellutente attuale


$securityContext = $this->get(security.context);
$user = $securityContext->getToken()->getUser();
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// lutente pu accedere
$acl->insertObjectAce($securityIdentity, MaskBuilder::MASK_OWNER);
$aclProvider->updateAcl($acl);
}
}

In questo pezzo di codice ci sono alcune importanti decisioni implementative. Per ora, ne mettiamo in evidenza
solo due:
Prima di tutto, il metodo ->createAcl() non accetta direttamente oggetti del dominio, ma solo implementazioni di ObjectIdentityInterface. Questo passo aggiuntivo consente di lavorare con le ACL, anche
se non si hanno veri oggetti del dominio a portata di mano. Questo pu essere molto utile quando si vogliono
verificare i permessi di un gran numero di oggetti, senza dover idratare gli oggetti stessi.
Laltra parte interessante la chiamata a ->insertObjectAce(). Nel nostro esempio, stiamo consentendo
laccesso come proprietario del commento allutente corrente. La costante MaskBuilder::MASK_OWNER un
intero predefinito; non ci si deve preoccupare, perch il costruttore di maschere astrae la maggior parte dei dettagli
tecnici, ma usando questa tecnica si possono memorizzare molti permessi diversi in una singola riga di database,
che fornisce un considerevole vantaggio in termini di prestazioni.
Tip: Lordine in cui gli ACE sono verificati significativo. Come regola generale, si dovrebbero mettere le voci
pi specifiche allinizio.

Verifica dellaccesso
// BlogController.php
public function editCommentAction(Comment $comment)
{
$securityContext = $this->get(security.context);
// verifica per laccesso in modifica
if (false === $securityContext->isGranted(EDIT, $comment))
{
throw new AccessDeniedException();
}
// recuperare loggetto commento e fare le modifiche
// ...
}

In questo esempio, verifichiamo se lutente abbia il permesso EDIT. Internamente, Symfony2 mappa i permessi a
diversi interi e verifica se luente possieda uno di essi.
Note: Si possono definire fino a 32 permessi base (a seconda del proprio sistema operativo, pu variare tra 30 e
32). Inoltre, si possono anche definire dei permessi cumulativi.

Permessi cumulativi
Nel nostro primo esempio, abbiamo assegnato alutente solo il permesso di base OWNER. Sebbene questo consenta
effettivamente allutente di eseguire qualsiasi operazione sulloggetto del dominio, come vedere, modificare, ecc.,
ci sono dei casi in cui si vuole assegnare tali permessi in modo esplicito.
MaskBuilder pu essere usato per creare facilmente delle maschere, combinando diversi permessi di base:
3.1. Ricettario

373

Symfony2 documentation Documentation, Release 2

$builder = new MaskBuilder();


$builder
->add(view)
->add(edit)
->add(delete)
->add(undelete)
;
$mask = $builder->get(); // int(15)

Questa maschera pu quindi essere usata per assegnare allutente i permessi di base aggiunti in precedenza:
$acl->insertObjectAce(new UserSecurityIdentity(johannes), $mask);

Ora lutente ha il permesso di vedere, modificare, cancellare e ripristinare gli oggetti.

3.1.46 Concetti avanzati su ACL


Lo scopo di questa ricetta approfondire il sistema ACL, nonch spiegare alcune decisioni progettuali che ne
stanno alle spalle.
Concetti progettuali
Le capacit di sicurezza degli oggetti di Symfony2 sono basate sul concetto di Access Control List. Ogni istanza
di un oggetto del dominio ha la sua ACL. Listanza ACL contiene una lista dettagliata di Access Control Entry
(ACE), usate per prendere decisioni sugli accessi. Il sistema ACL di Symfony2 si focalizza su due obiettivi
principali:
fornire un modo per recuperare in modo efficiente grosse quantit di ACL/ACE per gli oggetti del dominio
e modificarli;
fornire un modo per prendere facilmente decisioni sulla possibilit o meno che qualcuno esegua unazione
su un oggetto del dominio.
Come indicato nel primo punto, una delle capacit principali del sistema ACL di Symfony2 il modo altamente
performante con cui recupera ACL e ACE. Questo molto importante, perch ogni ACL pu avere tanti ACE,
nonch ereditare da altri ACL, come in un albero. Quindi, non ci appoggiamo specificatamente ad alcun ORM,
ma limplementazione predefinita interagisce con la connessione usando direttamente il DBAL di Doctrine.
Identit degli oggetti

Il sistema ACL interamente disaccoppiato dagli oggetti del dominio. Non devono nemmeno essere nello stesso
database o nello stesso server. Per ottenere tale disaccoppiamento, nel sistema ACL gli oggetti sono rappresentati
tramite oggetti identit di oggetti. Ogni volta che si vuole recuperare lACL per un oggetto del dominio, il sistema
ACL creer prima un oggetto identit a partire dalloggetto del dominio, quindi passer tale oggetto identit al
fornitore ACL per ulteriori processi.
Identit di sicurezza

analoga allidentit degli oggetti, ma rappresenta un utente o un ruolo nellapplicazione. Ogni ruolo, o utente,
ha la sua identit di sicurezza.
Struttura delle tabelle del database
Limplementazione predefinita usa cinque tabelle del database, elencate sotto. Le tabelle sono ordinate dalla pi
piccola alla pi grande, in una tipica applicazione:

374

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

acl_security_identities: questa tabella registra tutte le identit di sicurezza (SID) che contengono
ACE. Limplementazione predefinita ha due identit di sicurezza, RoleSecurityIdentity e
UserSecurityIdentity
acl_classes: questa tabella mappa i nomi delle classi con identificatori univoci, a cui possono fare riferimento le altre tabelle
acl_object_identities: ogni riga in questa tabella rappresebta unistanza di un singolo oggetto del dominio
acl_object_identity_ancestors: questa tabella consente di determinare tutti gli antenati di un ACL in modo
molto efficiente
acl_entries: questa tabella contiene tutti gli ACE. Questa tipicamente la tabella con pi righe. Pu contenerne decine di milioni, senza impattare in modo significativo sulle prestazioni
Visibilit degli Access Control Entry
Gli ACE possono avere visibilit diverse in cui applicarsi. In Symfony2, ci sono di base due visibilit:
di classe: questi ACE si applicano a tutti gli oggetti della stessa classe
di oggetto: questa lunica visibilit usata nel capitolo precedente e si applica solo a uno specifico oggetto
A volte, si avr bisogno di applicare un ACE solo a uno specifico campo delloggetto. Si supponga di volere
che lID sia visibile da un amministratore, ma non dal servizio clienti. Per risolvere questo problema comune,
abbiamo aggiunto altre due sotto-visibilit:
di campo di classe: questi ACE si applicano a tutti gli oggetti della stessa classe, ma solo a un campo
specifico
di campo di oggetto: questi ACE si applicano a uno specifico oggetto e solo a uno specifico campo di tale
oggetto
Decisioni pre-autorizzazione
Per decisioni pre-autorizzazione, che sono decisioni da prendere prima dellinvocazione di un metodo o di
unazione sicura, ci si appoggia sul servizio AccessDecisionManager, usato anche per prendere decisioni di
autorizzazione sui ruoli. Proprio come i ruoli, il sistema ACL aggiunge molti nuovi attributi, che possono essere
usati per verificare diversi permessi.

3.1. Ricettario

375

Symfony2 documentation Documentation, Release 2

Mappa predefinita dei permessi

Attributo
VIEW

Significato inteso

Maschere di bit

Se consentito vedere loggetto del dominio

EDIT

Se consentito modificare loggetto del dominio

VIEW, EDIT,
OPERATOR, MASTER o
OWNER
EDIT, OPERATOR,
MASTER o OWNER
CREATE, OPERATOR,
MASTER o OWNER
DELETE, OPERATOR,
MASTER o OWNER
UNDELETE,
OPERATOR, MASTER o
OWNER
OPERATOR, MASTER o
OWNER

CRESe consentito creare loggetto del dominio


ATE
DELETE Se consentito eliminare loggetto del dominio
UNSe consentito ripristinare un precedente oggetto del dominio
DELETE
OPERATOR
MASTER
OWNER

Se consentito eseguire tutte le azioni precedenti

Se consentito eseguire tutte le azioni precedenti e inoltre


consentito concedere uno dei permessi precedenti ad altri
Se si possiede loggetto del dominio. Il proprietario pu eseguire
tutte le azioni precedenti e concedere i permessi master e owner

MASTER o OWNER
OWNER

Attributi dei permessi o maschere di bit dei permessi

Gli attributi sono usati da AccessDecisionManager, cos come i ruoli sono attributi usati da
AccessDecisionManager. Spesso, tali attributi rappresentano di f atto un aggregato di maschere di bit.
Le maschere di bit, daltro canto, sono usate internamente dal sistema ACL per memorizzare in modo efficiente i
permessi degli utenti sul database e verificare gli accessi, usando operazioni di bit molto veloci.
Estensibilit

La mappa dei permessi vista sopra non affatto statica e in teoria pu essere sostituita totalmente. Tuttavia,
dovrebbe essere in grado di coprire la maggior parte dei problemi che si incontrano e, per interoperabilit con altri
bundle, si raccomanda di mantenere i significati che gli abbiamo attribuito.
Decisioni post-autorizzazione
Le decisioni post-autorizzazione sono eseguite dopo che un metodo sicuro stato invocato e coinvolgono solitamente oggetti del dominio restituiti da tali metodi. Dopo linvocazione, i fornitori consentono anche di modificare
o filtrare gli oggetti del dominio, prima che siano restituiti.
A causa di limitazioni del linguaggio PHP, non ci sono capacit di post-autorizzazione predefinite nel componente
della sicurezza. Tuttavia, c un bundle sperimentale, JMSSecurityExtraBundle, che aggiunge tali capacit. Si
veda la documentazione del bundle per maggiori informazioni sulla loro implementazione.
Processo di determinazione dellautorizzazione
La classe ACL fornisce due metodi per determinare se unidentit di sicurezza abbia i bit richiesti, isGranted e
isFieldGranted. Quando lACL riceve una richiesta di autorizzazione tramite uno di questi metodi, delega la
richiesta a unimplementazione di PermissionGrantingStrategy. Questo consente di sostituire il modo
in cui sono prese le decisioni di accesso, senza dover modificare la classe ACL stessa.

376

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

PermissionGrantingStrategy verifica prima tutti gli ACE con visibilit di oggetto. Se nessuno applicabile, verifica gli ACE con visibilit di classe. Se nessuno applicabile, il processo viene ripetuto con gli ACE
dellACL genitore. Se non esiste alcun ACL genitore, viene sollevata uneccezione.

3.1.47 Come forzare HTTPS o HTTP per URL diversi


Si possono forzare aree del proprio sito a usare il protocollo HTTPS nella configurazione della sicurezza. Lo
si pu fare tramite le regole access_control, usando lopzione requires_channel. Per esempio, se si
vogliono forzare tutti gli URL che iniziano per /secure a usare HTTPS, si pu usare la seguente configurazione:
YAML
access_control:
- path: ^/secure
roles: ROLE_ADMIN
requires_channel: https

XML
<access-control>
<rule path="^/secure" role="ROLE_ADMIN" requires_channel="https" />
</access-control>

PHP
access_control => array(
array(path => ^/secure,
role => ROLE_ADMIN,
requires_channel => https
),
),

Il form di login deve consentire laccesso anonimo, altrimenti lutente sarebbe impossibilitato ad autenticarsi. Per forzarlo a usare HTTPS, si possono usare ancora le regole access_control con il ruolo
IS_AUTHENTICATED_ANONYMOUSLY:
YAML
access_control:
- path: ^/login
roles: IS_AUTHENTICATED_ANONYMOUSLY
requires_channel: https

XML
<access-control>
<rule path="^/login"
role="IS_AUTHENTICATED_ANONYMOUSLY"
requires_channel="https" />
</access-control>

PHP
access_control => array(
array(path => ^/login,
role => IS_AUTHENTICATED_ANONYMOUSLY,
requires_channel => https
),
),

anche possibile specificare luso di HTTPS nella configurazione delle rotte, vedere Come forzare le rotte per
utilizzare sempre HTTPS per maggiori dettagli.

3.1. Ricettario

377

Symfony2 documentation Documentation, Release 2

3.1.48 Come personalizzare il form di login


Luso di un form di login per lautenticazione un metodo comune e flessibile per gestire lautenticazione in
Symfony2. Quasi ogni aspetto del form personalizzabile. La configurazione predefinita e completa mostrata
nella prossima sezione.
Riferimento di configurazione del form di login
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
# the user is redirected here when he/she needs to login
login_path:
/login
# if true, forward the user to the login form instead of redirecting
use_forward:
false
# submit the login form here
check_path:

/login_check

# by default, the login form *must* be a POST, not a GET


post_only:
true
# login success redirecting options (read further below)
always_use_default_target_path: false
default_target_path:
/
target_path_parameter:
_target_path
use_referer:
false
# login failure redirecting options (read further below)
failure_path:
null
failure_forward:
false
# field names for the username and password fields
username_parameter:
_username
password_parameter:
_password
# csrf token options
csrf_parameter:
intention:

_csrf_token
authenticate

XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
check_path="/login_check"
login_path="/login"
use_forward="false"
always_use_default_target_path="false"
default_target_path="/"
target_path_parameter="_target_path"
use_referer="false"
failure_path="null"
failure_forward="false"
username_parameter="_username"
password_parameter="_password"

378

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

csrf_parameter="_csrf_token"
intention="authenticate"
post_only="true"
/>
</firewall>
</config>

PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
check_path
=> /login_check,
login_path
=> /login,
user_forward
=> false,
always_use_default_target_path => false,
default_target_path
=> /,
target_path_parameter
=> _target_path,
use_referer
=> false,
failure_path
=> null,
failure_forward
=> false,
username_parameter
=> _username,
password_parameter
=> _password,
csrf_parameter
=> _csrf_token,
intention
=> authenticate,
post_only
=> true,
)),
),
));

Rinvio dopo il successo


Si pu modificare il posto in cui il form di login rinvia dopo un login eseguito con successo, usando le varie opzioni di configurazione.
Per impostazione predefinita, il form rinvier allURL
richiesto dallutente (cio lURL che ha portato al form di login).
Per esempio, se lutente ha
richiesto http://www.example.com/admin/post/18/edit, sar successivamente rimandato indietro
a http://www.example.com/admin/post/18/edit, dopo il login. Questo grazie alla memorizzazione
in sessione dellURL richiesto. Se non c alcun URL in sessione (forse lutente ha richiesto direttamente la pagina di login), lutente rinviato alla pagina predefinita, che / (ovvero la homepage). Si pu modificare questo
comportamento in diversi modi.
Note: Come accennato, lutente viene rinviato alla pagina che ha precedentemente richiesto. A volte questo pu
causare problemi, per esempio se una richiesta AJAX eseguita in background appare come ultimo URL visitato,
rinviando quindi lutente in quellURL. Per informazioni su come controllare questo comportamento, vedere Come
cambiare il comportamento predefinito del puntamento del percorso.

Cambiare la pagina predefinita

Prima di tutto, la pagina predefinita (la pagina a cui lutente viene rinviato, se non ci sono pagine precedenti in
sessione) pu essere impostata. Per impostarla a /admin, usare la seguente configurazione:
YAML
# app/config/security.yml
security:
firewalls:
main:

3.1. Ricettario

379

Symfony2 documentation Documentation, Release 2

form_login:
# ...
default_target_path: /admin

XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
default_target_path="/admin"
/>
</firewall>
</config>

PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
// ...
default_target_path => /admin,
)),
),
));

Ora, se non ci sono URL in sessione, gli utenti saranno mandati su /admin.
Rinviare sempre alla pagina predefinita

Si pu fare in modo che gli utenti siano sempre rinviati alla pagina predefinita, senza considerare lURL richiesta
prima del login, impostando lopzione always_use_default_target_path a true:
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
always_use_default_target_path: true

XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
always_use_default_target_path="true"
/>
</firewall>
</config>

PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
// ...
always_use_default_target_path => true,

380

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

)),
),
));

Usare lURL del referer

Se nessun URL stato memorizzato in sessione, si potrebbe voler provare a usare HTTP_REFERER, che spesso
coincide. Lo si pu fare impostando use_referer a true (il valore predefinito false):
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
use_referer:

true

XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
use_referer="true"
/>
</firewall>
</config>

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

New in version 2.1: Dalla 2.1, se il referer uguale allopzione login_path, lutente sar rinviato a
default_target_path.
Controllare lURL di rinvio da dentro un form

Si pu anche forzare la pagina di rinvio dellutente nel form stesso, includendo un campo nascosto dal nome
_target_path. Per esempio, per rinviare allURL definito in una rotta account, fare come segue:
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 }}" />

3.1. Ricettario

381

Symfony2 documentation Documentation, Release 2

<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
<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">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="hidden" name="_target_path" value="account" />
<input type="submit" name="login" />
</form>

Lutente sar ora rinviato al valore del campo nascosto. Il valore pu essere un percorso relativo, un URL
assoluto o un nome di rotta. Si pu anche modificare il nome del campo nascosto, cambiando lopzione
target_path_parameter con il valore desiderato.
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
target_path_parameter: redirect_url

XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
target_path_parameter="redirect_url"
/>
</firewall>
</config>

PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
target_path_parameter => redirect_url,
)),
),
));

382

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Rinvio al fallimento del login

Oltre a rinviare lutente dopo un login eseguito con successo, si pu anche impostare lURL a cui lutente va
rinviato dopo un login fallito (p.e. perch stato inserito un nome utente o una password non validi). Per impostazione predefinita, lutente viene rinviato al medesimo form di login. Si pu impostare un URL diverso,
usando la configurazione seguente:
YAML
# app/config/security.yml
security:
firewalls:
main:
form_login:
# ...
failure_path: /login_failure

XML
<!-- app/config/security.xml -->
<config>
<firewall>
<form-login
failure_path="login_failure"
/>
</firewall>
</config>

PHP
// app/config/security.php
$container->loadFromExtension(security, array(
firewalls => array(
main => array(form_login => array(
// ...
failure_path => login_failure,
)),
),
));

3.1.49 Proteggere servizi e metodi di unapplicazione


Nel capitolo sulla sicurezza, si pu vedere come proteggere un controllore, richiedendo il servizio
security.context dal contenitore di servizi e verificando il ruolo dellutente attuale:
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
// ...
public function helloAction($name)
{
if (false === $this->get(security.context)->isGranted(ROLE_ADMIN)) {
throw new AccessDeniedException();
}
// ...
}

Si pu anche proteggere qualsiasi servizio in modo simile, iniettando in esso il servizio security.context.
Per unintroduzione generale alliniezione di dipendenze nei servizi, vedere il capitolo Contenitore di servizi del
libro. Per esempio, si supponga di avere una classe NewsletterManager, che invia email, e di voler restringere
il suo utilizzo ai soli utenti con un ruolo ROLE_NEWSLETTER_ADMIN. Prima di aggiungere la sicurezza, la classe
assomiglia a qualcosa del genere:

3.1. Ricettario

383

Symfony2 documentation Documentation, Release 2

namespace Acme\HelloBundle\Newsletter;
class NewsletterManager
{
public function sendNewsletter()
{
// qui va la logica specifica
}
// ...
}

Lo scopo verificare il ruolo dellutente al richiamo del metodo sendNewsletter(). Il primo passo in questa
direzione liniezione del servizio security.context nelloggetto. Non avendo molto senso non eseguire un
controllo di sicurezza, questo un candidato ideale per uniniezione nel costruttore, che garantisce che loggetto
della sicurezza sia disponibile in tutta la classe NewsletterManager:
namespace Acme\HelloBundle\Newsletter;
use Symfony\Component\Security\Core\SecurityContextInterface;
class NewsletterManager
{
protected $securityContext;
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
// ...
}

Quindi, nella configurazione dei servizi, si pu iniettare il servizio:


YAML
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
services:
newsletter_manager:
class:
%newsletter_manager.class%
arguments: [@security.context]

XML

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


<parameters>
<parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</
</parameters>
<services>
<service id="newsletter_manager" class="%newsletter_manager.class%">
<argument type="service" id="security.context"/>
</service>
</services>

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

384

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

use Symfony\Component\DependencyInjection\Reference;

$container->setParameter(newsletter_manager.class, Acme\HelloBundle\Newsletter\NewsletterM
$container->setDefinition(newsletter_manager, new Definition(
%newsletter_manager.class%,
array(new Reference(security.context))
));

Il servizio iniettato pu quindi essere usato per eseguire il controllo di sicurezza, quando il metodo
sendNewsletter() viene richiamato:
namespace Acme\HelloBundle\Newsletter;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\SecurityContextInterface;
// ...
class NewsletterManager
{
protected $securityContext;
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
public function sendNewsletter()
{
if (false === $this->securityContext->isGranted(ROLE_NEWSLETTER_ADMIN)) {
throw new AccessDeniedException();
}
//-}
// ...
}

Se lutente attuale non ha il ruolo ROLE_NEWSLETTER_ADMIN, gli sar richiesto di autenticarsi.


Mettere i sicurezza i metodi con le annotazioni
Si possono anche proteggere i metodi di un servizio tramite annotazioni, usando il bundle JMSSecurityExtraBundle. Questo bundle incluso nella Standard Edition di Symfony2.
Per abilitare le annotazioni, taggare il servizio da proteggere con il tag security.secure_service (si pu
anche abilitare automaticamente la funzionalit per tutti i servizi, vedere i dettagli pi avanti):
YAML
# src/Acme/HelloBundle/Resources/config/services.yml
# ...
services:
newsletter_manager:
# ...
tags:
- { name: security.secure_service }

XML

3.1. Ricettario

385

Symfony2 documentation Documentation, Release 2

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


<!-- ... -->
<services>
<service id="newsletter_manager" class="%newsletter_manager.class%">
<!-- ... -->
<tag name="security.secure_service" />
</service>
</services>

PHP
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$definition = new Definition(
%newsletter_manager.class%,
array(new Reference(security.context))
));
$definition->addTag(security.secure_service);
$container->setDefinition(newsletter_manager, $definition);

Si possono ottenere gli stessi risultati usando le annotazioni:


namespace Acme\HelloBundle\Newsletter;
use JMS\SecurityExtraBundle\Annotation\Secure;
// ...
class NewsletterManager
{
/**
* @Secure(roles="ROLE_NEWSLETTER_ADMIN")
*/
public function sendNewsletter()
{
//-}
// ...
}

Note: Le annotazioni funzionano perch viene creata una classe proxy per la propria classe, che esegue i controlli
di sicurezza. Questo vuol dire che, sebbene si possano usare le annotazioni su metodi pubblici e protetti, non si
possono usare su metodi privati o su metodi finali.
Il bundle JMSSecurityExtraBundle consente anche di proteggere i parametri e i valori resituiti dai metodi.
Per maggiori informazioni vedere la documentazione di JMSSecurityExtraBundle.

386

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Attivare le annotazioni per tutti i servizi


Quando si proteggono i metodi di un servizio (come mostrato precedentemente), si pu taggare ogni
servizio individualmente oppure attivare la funzionalit per tutti i servizi. Per farlo, impostare lopzione
secure_all_services a true:
YAML
# app/config/config.yml
jms_security_extra:
# ...
secure_all_services: true

XML

<!-- app/config/config.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
<jms_security_extra secure_controllers="true" secure_all_services="true" />
</srv:container>

PHP
// app/config/config.php
$container->loadFromExtension(jms_security_extra, array(
// ...
secure_all_services => true,
));

Lo svantaggio di questo sistema che, se attivato, il caricamento della pagina iniziale potrebbe essere molto
lento, a seconda di quanti servizi sono stati definiti.

3.1.50 Come caricare gli utenti dal database (il fornitore di entit)
Il livello della sicurezza uno degli strumenti migliori di Symfony. Gestisce due aspetti: il processo di autenticazione e quello di autorizzazione. Sebbene possa sembrare difficile capirne il funzionamento interno, il sistema di
sicurezza molto flessibile e consente di integrare la propria applicazione con qualsiasi backend di autenticazione,
come Active Directory, OAuth o un database.
Introduzione
Questo articolo mostra come autenticare gli utenti con una tabella di database, gestita da una
classe entit di Doctrine.
Il contenuto di questa ricetta suddiviso in tre parti.
La prima
parte riguarda la progettazione di una classe entit User e il renderla usabile nel livello della sicurezza di Symfony. La seconda parte descrive come autenticare facilmente un utente con loggetto
Symfony\Bridge\Doctrine\Security\User\EntityUserProvider distribuito con il framework, oltre che con un po di configurazione.
Infine, la guida dimostrer come creare una classe
Symfony\Bridge\Doctrine\Security\User\EntityUserProvider personalizzata, per recuperare utenti dal database con condizioni particolari.
Questa guida presume che ci sia un bundle Acme\UserBundle gi pronto nellapplicazione.
Il modello dei dati
Ai fini di questa ricetta, il bundle AcmeUserBundle contiene una classe entit User, con i seguenti campi: id,
username, salt, password, email e isActive. Il campo isActive indica se lutente attivo o meno.

3.1. Ricettario

387

Symfony2 documentation Documentation, Release 2

Per sintetizzare, i metodi setter e getter per ogni campo sono stati rimossi, in modo da focalizzarsi sui metodi
pi importanti, provenienti da Symfony\Component\Security\Core\User\UserInterface. New
in version 2.1.
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\UserBundle\Entity\User
*
* @ORM\Table(name="acme_users")
* @ORM\Entity(repositoryClass="Acme\UserBundle\Entity\UserRepository")
*/
class User implements UserInterface
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id()
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(name="username", type="string", length=25, unique=true)
*/
private $username;
/**
* @ORM\Column(name="salt", type="string", length=40)
*/
private $salt;
/**
* @ORM\Column(name="password", type="string", length=40)
*/
private $password;
/**
* @ORM\Column(name="email", type="string", length=60, unique=true)
*/
private $email;
/**
* @ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
public function __construct()
{
$this->isActive = true;
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36);
}
public function getRoles()
{
return array(ROLE_USER);
}
public function eraseCredentials()

388

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

{
}
public function getUsername()
{
return $this->username;
}
public function getSalt()
{
return $this->salt;
}
public function getPassword()
{
return $this->password;
}
}

Per poter usare unistanza della classe AcmeUserBundle:User nel livello della sicurezza di Symfony, la classe
entit deve implementare Symfony\Component\Security\Core\User\UserInterface. Questa interfaccia costringe la classe a implementare i seguenti cinque metodi: getRoles(), getPassword(),
getSalt(), getUsername(), eraseCredentials(). Per maggiori dettagli su tali metodi, vedere
Symfony\Component\Security\Core\User\UserInterface.
Di seguito mostrata unesportazione della tabella User in MySQL. Per dettagli sulla creazione delle righe degli
utenti e sulla codifica delle password, vedere Codificare la password dellutente.

mysql> select * from user;


+----+----------+------------------------------------------+-------------------------------------| id | username | salt
| password
+----+----------+------------------------------------------+-------------------------------------| 1 | hhamon
| 7308e59b97f6957fb42d66f894793079c366d7c2 | 09610f61637408828a35d7debee5b38a8350e
| 2 | jsmith
| ce617a6cca9126bf4036ca0c02e82deea081e564 | 8390105917f3a3d533815250ed7c64b4594d7
| 3 | maxime
| cd01749bb995dc658fa56ed45458d807b523e4cf | 9764731e5f7fb944de5fd8efad4949b995b72
| 4 | donald
| 6683c2bfd90c0426088402930cadd0f84901f2f4 | 5c3bcec385f59edcc04490d1db95fdb8673bf
+----+----------+------------------------------------------+-------------------------------------4 rows in set (0.00 sec)

Il database ora contiene quattro utenti, con differenti nomi, email e status. Nella prossima parte, vedremo come
autenticare uno di questi utenti, grazie al fornitore di entit di Doctrine e a un paio di righe di configurazione.
Autenticazione con utenti sul database
Lautenticazione di un utente tramite database, usando il livello della sicurezza di Symfony, un gioco da ragazzi.
Sta tutto nella configurazione SecurityBundle, memorizzata nel file app/config/security.yml.
Di seguito mostrato un esempio di configurazione, in cui lutente inserir il suo nome e la sua password, tramite
autenticazione HTTP. Queste informazioni saranno poi verificate sulla nostra entit User, nel database:
YAML
# app/config/security.yml
security:
encoders:
Acme\UserBundle\Entity\User:
algorithm: sha1
encode_as_base64: false
iterations: 1
providers:
administrators:
entity: { class: AcmeUserBundle:User, property: username }

3.1. Ricettario

389

Symfony2 documentation Documentation, Release 2

firewalls:
admin_area:
pattern:
^/admin
http_basic: ~
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }

La sezione encoders associa il codificatore sha1 alla classe entit. Ci vuol dire che Symfony si aspetta che
le password siano codificate nel database, tramite tale algoritmo. Per maggiori dettagli su come creare un nuovo
oggetto utente, vedere la sezione Codificare la password dellutente del capitolo sulla sicurezza.
La sezione providers definsice un fornitore di utenti administrators. Un fornitore di utenti una sorgente da cui gli utenti vengono caricati durante lautenticazione. In questo caso, la chiave entity vuol dire
che Symfony user il fornitore di entit di Doctrine per caricare gli oggetti User dal database, usando il campo
univoco username. In altre parole, dice a Symfony come recuperare gli utenti dal database, prima di verificare
la validit della password.
Questo codice e questa configurazione funzionano, ma non bastano per proteggere lapplicazione per gli utenti
attivi. Finora, possiamo ancora autenticarci con maxime. Nella prossima sezione, vedremo come inibire gli
utenti non attivi.
Inibire gli utenti inattivi
Il
modo
pi
facile
per
escludere
gli
utenti
inattivi

implementare
linterfaccia
Symfony\Component\Security\Core\User\AdvancedUserInterface, che si occupa di verificare lo stato degli utenti. Linterfaccia Symfony\Component\Security\Core\User\AdvancedUserInterface
estende Symfony\Component\Security\Core\User\UserInterface, quindi occorre solo modificare linterfaccia nella classe AcmeUserBundle:User, per poter beneficiare di comportamenti semplici e
avanzati di autenticazione.
Linterfaccia Symfony\Component\Security\Core\User\AdvancedUserInterface aggiunge altri quattro metodi, per validare lo stato degli utenti:
isAccountNonExpired() verifica se lutente scaduto,
isAccountNonLocked() verifica se lutente bloccato,
isCredentialsNonExpired() verifica se la password dellutente scaduta,
isEnabled() verifica se lutente abilitato
Per questo esempio, i primi tre metodi restituiranno true, mentre il metodo isEnabled() restituire il valore
booleano del campo isActive.
// src/Acme/UserBundle/Entity/User.php
namespace Acme\Bundle\UserBundle\Entity;
// ...
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
// ...
class User implements AdvancedUserInterface
{
// ...
public function isAccountNonExpired()
{
return true;
}
public function isAccountNonLocked()
{

390

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

return true;
}
public function isCredentialsNonExpired()
{
return true;
}
public function isEnabled()
{
return $this->isActive;
}
}

Se proviamo ora ad autenticare maxime, laccesso sar negato, perch questo utente non stato abilitato. La
prossima parte analizzer il modo in cui scrivere fornitori di utenti personalizzati, per autenticare un utente con il
suo nome oppure con la sua email.
Autenticazione con un fornitore entit personalizzato
Il passo successivo consisten nel consentire a un utente di autenticarsi con il suo nome o con il suo indirizzo email,
che sono entrambi unici nel database. Sfortunatamente, il fornitore di entit nativo in grado di gestire una sola
propriet per recuperare lutente dal database.
Per poterlo fare, creare un fornitore di entit personalizzato, che cerchi un utente il cui
nome o la cui email corrisponda al nome utente inserito.
La buona notizia che un
oggetto repository di Doctrine pu agire da fornitore di entit, se implementa linterfaccia
Symfony\Component\Security\Core\User\UserProviderInterface.
Questa
interfaccia
ha
tre
metodi
da
implementare:
loadUserByUsername($username),
refreshUser(UserInterface $user) e supportsClass($class). Per maggiori dettagli, si
veda Symfony\Component\Security\Core\User\UserProviderInterface.

Il codice successivo mostra limplementazione di Symfony\Component\Security\Core\User\UserProviderInterf


nella classe UserRepository:
// src/Acme/UserBundle/Entity/UserRepository.php
namespace Acme\UserBundle\Entity;
use
use
use
use
use
use

Symfony\Component\Security\Core\User\UserInterface;
Symfony\Component\Security\Core\User\UserProviderInterface;
Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
Symfony\Component\Security\Core\Exception\UnsupportedUserException;
Doctrine\ORM\EntityRepository;
Doctrine\ORM\NoResultException;

class UserRepository extends EntityRepository implements UserProviderInterface


{
public function loadUserByUsername($username)
{
$q = $this
->createQueryBuilder(u)
->where(u.username = :username OR u.email = :email)
->setParameter(username, $username)
->setParameter(email, $username)
->getQuery()
;
try {
// The Query::getSingleResult() method throws an exception
// if there is no record matching the criteria.
$user = $q->getSingleResult();

3.1. Ricettario

391

Symfony2 documentation Documentation, Release 2

} catch (NoResultException $e) {


throw new UsernameNotFoundException(sprintf(Impossibile trovare un oggetto AcmeUserBu
}
return $user;
}

public function refreshUser(UserInterface $user)


{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(sprintf(Istanze di "%s" non supportate., $class))
}
return $this->loadUserByUsername($user->getUsername());
}

public function supportsClass($class)


{
return $this->getEntityName() === $class || is_subclass_of($class, $this->getEntityName())
}
}

Per concludere limplementazione, occorre modificare la configurazione del livello della sicurezza, per
dire a Symfony di usare il nuovo fornitore di entit personalizzato, al posto del fornitore di entit generico di Doctrine. Lo si pu fare facilmente, rimuovendo il campo property nella sezione
security.providers.administrators.entity del file security.yml.
YAML
# app/config/security.yml
security:
# ...
providers:
administrators:
entity: { class: AcmeUserBundle:User }
# ...

In questo modo, il livello della sicurezza user unistanza di UserRepository e richiamer il suo metodo
loadUserByUsername() per recuperare un utente dal database, sia che abbia inserito il suo nome utente che
abbia inserito la sua email.
Gestire i ruoli nel database
Lultima parte della guida spiega come memorizzare e recuperare una lista di ruoli dal database. Come gi accennato, quando lutente viene caricato, il metodo getRoles() restituisce un array di ruoli di sicurezza, che
gli andrebbero assegnati. Si possono caricare tali dati da qualsiasi posto, una lista predefinita usata per ogni
utente (p.e. array(ROLE_USER)), un array di Doctrine chiamato roles, oppure tramite una relazione di
Doctrine, come vedremo in questa sezione.
Caution:
In una configurazione tipica, si dovrebbe sempre restituire almeno un ruolo nel
metodogetRoles(). Per convenzione, solitamente si restituisce un ruolo chiamato ROLE_USER. Se non
si restituisce alcun ruolo, lutente potrebbe apparire come non autenticato.
In questo esempio, la classe entit AcmeUserBundle:User definisce una relazione molti-a-molti con la classe
entit AcmeUserBundle:Group. Un utente pu essere in relazione con molti gruppi e un gruppo pu essere
composto da uno o pi utenti. Poich un gruppo anche un ruolo, il precedente metodo getRoles() ora
restituisce lelenco dei gruppi correlati:
// src/Acme/UserBundle/Entity/User.php

392

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

namespace Acme\Bundle\UserBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
// ...
class User implements AdvancedUserInterface
{
/**
* @ORM\ManyToMany(targetEntity="Group", inversedBy="users")
*
*/
private $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
}
// ...
public function getRoles()
{
return $this->groups->toArray();
}
}

La classe entit AcmeUserBundle:Group definisce tre campi di tabella (id, name e role). Il campo
univoco role contiene i nomi dei ruoli usati dal livello della sicurezza di Symfony per proteggere parti
dellapplicazione. La cosa pi importante da notare che la classe entit AcmeUserBundle:Group implementa Symfony\Component\Security\Core\Role\RoleInterface, che la obbliga ad avere un
metodo getRole():
namespace Acme\Bundle\UserBundle\Entity;
use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="acme_groups")
* @ORM\Entity()
*/
class Group implements RoleInterface
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id()
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/** @ORM\Column(name="name", type="string", length=30) */
private $name;
/** @ORM\Column(name="role", type="string", length=20, unique=true) */
private $role;
/** @ORM\ManyToMany(targetEntity="User", mappedBy="groups") */
private $users;
public function __construct()
{
$this->users = new ArrayCollection();

3.1. Ricettario

393

Symfony2 documentation Documentation, Release 2

}
// ... getter e setter per ogni propriet
/** @see RoleInterface */
public function getRole()
{
return $this->role;
}
}

Per migliorare le prestazioni ed evitare il caricamento pigro dei gruppi al momento del recupero dellutente
dal fornitore di utenti personalizzato, la soluzione migliore fare un join dei gruppi correlati nel metodo
UserRepository::loadUserByUsername(). In tal modo, sar recuperato lutente e i suoi gruppi/ruoli
associati, con una sola query:
// src/Acme/UserBundle/Entity/UserRepository.php
namespace Acme\Bundle\UserBundle\Entity;
// ...
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$q = $this
->createQueryBuilder(u)
->select(u, g)
->leftJoin(u.groups, g)
->where(u.username = :username OR u.email = :email)
->setParameter(username, $username)
->setParameter(email, $username)
->getQuery()
;
// ...
}
// ...
}

Il metodo QueryBuilder::leftJoin() recupera con un join i gruppi correlati dalla classe del modello
AcmeUserBundle:User, quando un utente viene recuperato con la sua email o con il suo nome.

3.1.51 Come creare un fornitore utenti personalizzato


Parte del processo standard di autenticazione di Symfony2 dipende dai fornitori utenti. Quando un utente invia
nome e password, il livello di autenticazione chiede al fornitore utenti configurato di restituire un oggetto utente
per un dato nome utente. Symfony quindi verifica che la password di tale utente sia corretta e genera un token di
sicurezza, in modo che lutente resti autenticato per la sessione corrente. Symfony dispone di due fornitori utenti
predefiniti, in_memory e entity. In questa ricetta, vedremo come poter creare il poprio fornitore utenti, che
potrebbe essere utile se gli utenti accedono tramite un database personalizzato, un file, oppure (come mostrato in
questo esempio) tramite un servizio web.
Creare una classe utente
Prima di tutto, indipendentemente dalla provenienza dei dati utente, occorre creare una classe User, che rappresenti tali dati. La classe User, comunque, pu essere fatta a piacere e contenere qualsiasi dato si desideri. Lunico
requisito che implementi Symfony\Component\Security\Core\User\UserInterface. I metodi
394

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

in tale interfaccia vanno quindi deifniti nella classe utente personalizzata: getRoles(), getPassword(),
getSalt(), getUsername(), eraseCredentials(), equals().
Vediamola in azione:
// src/Acme/WebserviceUserBundle/Security/User.php
namespace Acme\WebserviceUserBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
class WebserviceUser implements UserInterface
{
private $username;
private $password;
private $salt;
private $roles;
public function __construct($username, $password, $salt, array $roles)
{
$this->username = $username;
$this->password = $password;
$this->salt = $salt;
$this->roles = $roles;
}
public function getRoles()
{
return $this->roles;
}
public function getPassword()
{
return $this->password;
}
public function getSalt()
{
return $this->salt;
}
public function getUsername()
{
return $this->username;
}
public function eraseCredentials()
{
}
public function equals(UserInterface $user)
{
if (!$user instanceof WebserviceUser) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->getSalt() !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {

3.1. Ricettario

395

Symfony2 documentation Documentation, Release 2

return false;
}
return true;
}
}

Se si hanno maggiori informazioni sui propri utenti, come il nome di battesimo, si possono aggiungere campi per
memorizzare tali dati.
Per maggiori dettagli su ciascun metodo, vedere Symfony\Component\Security\Core\User\UserInterface.
Creare un fornitore utenti
Ora che abbiamo una classe User, creeremo un fornitore di utenti, che estrarr informazioni da un servizio web,
creer un oggetto WebserviceUser e lo popoler con i dati.
Il
fornitore
utenti

semplicemente
una
classe
PHP
che
deve
implementare
Symfony\Component\Security\Core\User\UserProviderInterface,
la
quale
richiede
la
definizione
di
tre
metodi:
loadUserByUsername($username),
refreshUser(UserInterface $user) and supportsClass($class). Per maggiori dettagli,
vedere Symfony\Component\Security\Core\User\UserProviderInterface.
Ecco un esempio di come potrebbe essere:
// src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php
namespace Acme\WebserviceUserBundle\Security\User;
use
use
use
use

Symfony\Component\Security\Core\User\UserProviderInterface;
Symfony\Component\Security\Core\User\UserInterface;
Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class WebserviceUserProvider implements UserProviderInterface


{
public function loadUserByUsername($username)
{
// fare qui una chiamata al servizio web
// $userData = ...
// supponiamo che restituisca un array, oppure false se non trova utenti
if ($userData) {
// $password = ...;
// ...

return new WebserviceUser($username, $password, $salt, $roles)


} else {
throw new UsernameNotFoundException(sprintf(Nome utente "%s" non trovato., $username
}
}

public function refreshUser(UserInterface $user)


{
if (!$user instanceof WebserviceUser) {
throw new UnsupportedUserException(sprintf(Istanza di "%s" non supportata., get_clas
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{

396

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

return $class === Acme\WebserviceUserBundle\Security\User\WebserviceUser;


}
}

Creare un servizio per il fornitore utenti


Ora renderemo il fornitore utenti disponibile come servizio.
YAML

# src/Acme/MailerBundle/Resources/config/services.yml
parameters:
webservice_user_provider.class: Acme\WebserviceUserBundle\Security\User\WebserviceUserPro
services:
webservice_user_provider:
class: %webservice_user_provider.class%

XML

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


<parameters>
<parameter key="webservice_user_provider.class">Acme\WebserviceUserBundle\Security\User\W
</parameters>

<services>
<service id="webservice_user_provider" class="%webservice_user_provider.class%"></service
</services>

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

$container->setParameter(webservice_user_provider.class, Acme\WebserviceUserBundle\Securit

$container->setDefinition(webservice_user_provider, new Definition(%webservice_user_provid

Tip: La vera implementazione del fornitore utenti avr probabilmente alcune dipendenze da opzioni di configurazione o altri servizi. Aggiungerli come parametri nella definizione del servizio.

Note: Assicurarsi che il file dei servizi sia importato. Vedere Importare la configurazione con imports per
maggiori dettagli.

Modificare security.yml
tutto in /app/config/security.ymlr. Aggiungere il fornitore di utenti alla lista di fornitori nella sezione security. Scegliere un nome per il fornitore di utenti (p.e. webservice) e menzionare lid del servizio appena
definito.
security:
providers:
webservice:
id: webservice_user_provider

Symfony deve anche sapere come codificare le password fornite dagli utenti, per esempio quando
compilano il form di login.
Lo si pu fare aggiungendo una riga alla sezione encoders, in
/app/config/security.yml.

3.1. Ricettario

397

Symfony2 documentation Documentation, Release 2

security:
encoders:
Acme\WebserviceUserBundle\Security\User\WebserviceUser: sha512

Il valore inserito deve corrispondere al modo in cui le password sono state codificate originariamente, alla
creazione degli uenti (in qualsiasi modo siano stati creati). Quando un utente inserisce la sua password, la password viene concatenata con il valore del sale e quindi codificata con questo algoritmo, prima di confrontarla con la
password restituita dal proprio metodo getPassword(). Inoltre, a seconda delle proprie opzioni, la password
pu essere codificata pi volte e poi codificata in base64.
Specifiche sulle codifiche delle password
Symfony usa un metodo specifico per concatenare il sale e codificare la password, prima di confrontarla
con la password memorizzata. Se getSalt() non restituisce nulla, la password inserita semplicemente
codificata con lalgoritmo specificato in security.yml. Se invece il sale fornito, il seguente valore
viene creato e poi codificato tramite lalgoritmo:
$password.{.$salt.};
Se gli utenti esterni hanno password con sali diversi, occorre un po di lavoro in pi per far s che Symfony possa codificare correttamente la password. Questo va oltre lo scopo di questa ricetta, possiamo accennare che includerebbe la creazione di una sotto-classe di MessageDigestPasswordEncoder e la
sovrascrittura del metodo mergePasswordAndSalt.
Inoltre, per impostazione predefinita, lhash codificato pi volte e poi codificato in base64. Per i dettagli,
si veda MessageDigestPasswordEncoder. Se lo si vuole evitare, configurarlo in security.yml:
security:
encoders:
Acme\WebserviceUserBundle\Security\User\WebserviceUser:
algorithm: sha512
encode_as_base64: false
iterations: 1

3.1.52 Come creare un fornitore di autenticazione personalizzato


Chi ha letto il capitolo sulla Sicurezza pu capire la distinzione che fa Symfony2 tra autenticazione e autorizzazione, nellimplementazione della sicurezza. Questo capitolo discute le classi di base coinvolte nel processo di
autenticazione e come implementare un fornitore di autenticazione personalizzato. Poich autenticazione e autorizzazione sono concetti separati, questa estensione sar agnostica rispetto al fornitore di utenti e funzioner con
il fornitore di utenti della propria applicazione, sia esso basato sulla memoria, su un database o su qualsiasi altro
supporto scelto.
WSSE
Il seguente capitolo mostra come creare un fornitore di autenticazione personalizzato per lautenticazione WSSE.
Questo protocollo di sicurezza per WSSE fornisce diversi benefici:
1. Criptazione di nome utente e password
2. Protezione dagli attacchi di replay
3. Nessuna configurazione del server web necessaria
WSSE molto utile per proteggere i servizi web, siano essi SOAP o REST.
C molta buona documentazione su WSSE, ma questo articolo non approfondir il protocollo di sicurezza, quando
il modo in cui un protocollo personalizzato possa essere aggiunto alla propria applicazione Symfony2. La base di
WSSE la verifica degli header di richiesta tramite credenziali criptate, con luso di un timestamp e di nonce, e la
verifica dellutente richiesto tramite un digest di password.

398

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Note: WSSE supporta anche la validazione di chiavi dellapplicazione, che utile per i servizi web, ma fuori
dallo scopo di questo capitolo.

Il token
Il ruolo del token nel contesto della sicurezza di Symfony2 importante. Un token rappresenta i dati di autenticazione dellutente presenti nella richiesta. Una volta autenticata la richiesta, il token mantiene i dati dellutente
e fornisce tali data attraverso il contesto della sicurezza. Prima di tutto, creeremo la nostra classe per il token.
Questo consentir il passaggio di tutte le informazioni rilevanti al nostro fornitore di autenticazione.
// src/Acme/DemoBundle/Security/Authentication/Token/WsseUserToken.php
namespace Acme\DemoBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class WsseUserToken extends AbstractToken
{
public $created;
public $digest;
public $nonce;
public function getCredentials()
{
return ;
}
}

Note: La classe WsseUserToken estende la classe Symfony\Component\Security\Core\Authentication\Token


del componente della sicurezza, la quale fornisce funzionalit di base per il token. Si pu implementare
Symfony\Component\Security\Core\Authentication\Token\TokenInterface su una
qualsiasi classe da usare come token.

Lascoltatore
Ora occorre un ascoltatore, che ascolti nel contesto della sicurezza. Lascoltatore responsabile delle
richieste al firewall e di richiamare il fornitore di autenticazione. Un ascoltatore deve essere unistanza
di Symfony\Component\Security\Http\Firewall\ListenerInterface. Un ascoltatore di sicurezza dovrebbe gestire levento Symfony\Component\HttpKernel\Event\GetResponseEvent e
impostare un token di autenticazione nel contesto della sicurezza, in caso positivo.
// src/Acme/DemoBundle/Security/Firewall/WsseListener.php
namespace Acme\DemoBundle\Security\Firewall;
use
use
use
use
use
use
use
use

Symfony\Component\HttpFoundation\Response;
Symfony\Component\HttpKernel\Event\GetResponseEvent;
Symfony\Component\Security\Http\Firewall\ListenerInterface;
Symfony\Component\Security\Core\Exception\AuthenticationException;
Symfony\Component\Security\Core\SecurityContextInterface;
Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
Acme\DemoBundle\Security\Authentication\Token\WsseUserToken;

class WsseListener implements ListenerInterface


{
protected $securityContext;
protected $authenticationManager;

public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerIn

3.1. Ricettario

399

Symfony2 documentation Documentation, Release 2

{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
}
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->headers->has(x-wsse)) {

$wsseRegex = /UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"


if (preg_match($wsseRegex, $request->headers->get(x-wsse), $matches)) {
$token = new WsseUserToken();
$token->setUser($matches[1]);
$token->digest
$token->nonce
$token->created

= $matches[2];
= $matches[3];
= $matches[4];

try {
$returnValue = $this->authenticationManager->authenticate($token);
if ($returnValue instanceof TokenInterface) {
return $this->securityContext->setToken($returnValue);
} else if ($returnValue instanceof Response) {
return $event->setResponse($returnValue);
}
} catch (AuthenticationException $e) {
// si potrebbe loggare qualcosa in questo punto
}
}
}
$response = new Response();
$response->setStatusCode(403);
$event->setResponse($response);
}
}

Questo ascoltatore verifica che la richiesta contenga lheader X-WSSE, confronta il valore restituito con
linformazione WSSE attesa, crea un token usando tale informazione e passa il token al gestore di autenticazione. Se non viene fornita uninformazione adeguata oppure se il gestore di autenticazione lancia una Symfony\Component\Security\Core\Exception\AuthenticationException, viene
restituita una risposta 403.

Note: Una classe non usata precedentemente, la classe Symfony\Component\Security\Http\Firewall\AbstractAu


una classe base molto utile, che fornisce le funzionalit solitamente necessarie per le estensioni della sicurezza.
Ci include il mantenimento del token in sessione, fornire gestori di successo/fallimento, login da URL, eccetera.
Poich WSSE non richiede di mantenere sessioni di autenticazione n form di login, non sar usata per questo
esempio.

Il fornitore di autenticazione
Il fornitore di autenticazione verificher il token WsseUserToken. Questo vuol dire che il fornitore verificher
che il valore dellheader Created sia valido entro cinque minuti, che il valore dellheader Nonce sia unico nei
cinque minuti e che il valore dellheader PasswordDigest corrisponda alla password dellutente.

400

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

// src/Acme/DemoBundle/Security/Authentication/Provider/WsseProvider.php
namespace Acme\DemoBundle\Security\Authentication\Provider;
use
use
use
use
use
use

Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
Symfony\Component\Security\Core\User\UserProviderInterface;
Symfony\Component\Security\Core\Exception\AuthenticationException;
Symfony\Component\Security\Core\Exception\NonceExpiredException;
Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
Acme\DemoBundle\Security\Authentication\Token\WsseUserToken;

class WsseProvider implements AuthenticationProviderInterface


{
private $userProvider;
private $cacheDir;
public function __construct(UserProviderInterface $userProvider, $cacheDir)
{
$this->userProvider = $userProvider;
$this->cacheDir
= $cacheDir;
}
public function authenticate(TokenInterface $token)
{
$user = $this->userProvider->loadUserByUsername($token->getUsername());

if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->


$authenticatedToken = new WsseUserToken($user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
}
throw new AuthenticationException(The WSSE authentication failed.);
}
protected function validateDigest($digest, $nonce, $created, $secret)
{
// Scade dopo 5 minuti
if (time() - strtotime($created) > 300) {
return false;
}

// Valida che nonce sia unico nei 5 minuti


if (file_exists($this->cacheDir./.$nonce) && file_get_contents($this->cacheDir./.$nonc
throw new NonceExpiredException(Previously used nonce detected);
}
file_put_contents($this->cacheDir./.$nonce, time());
// Valida la parola segreta
$expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));
return $digest === $expected;
}
public function supports(TokenInterface $token)
{
return $token instanceof WsseUserToken;
}
}

Note: Linterfaccia Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProv


richiede un metodo authenticate sul token dellutente e un metodo supports, che dice al gestore di aut-

3.1. Ricettario

401

Symfony2 documentation Documentation, Release 2

enticazione se usare o meno questo fornitore per il token dato. In caso di pi fornitori, il gestore di autenticazione
passer al fornitore successivo della lista.

Il factory

Abbiamo creato un token personalizzato, un ascoltatore personalizzato e un fornitore personalizzato.


Ora dobbiamo legarli insieme.
Come rendere disponibile il fornitore alla configurazione della sicurezza?
La risposta : usando un factory.
Un factory quando ci si aggancia al componente della sicurezza, dicendogli il nome del proprio provider e qualsiasi opzione
di configurazione disponibile per esso.
Prima di tutto, occorre creare una classe che implementi
Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterf
// src/Acme/DemoBundle/DependencyInjection/Security/Factory/WsseFactory.php
namespace Acme\DemoBundle\DependencyInjection\Security\Factory;
Symfony\Component\DependencyInjection\ContainerBuilder;
Symfony\Component\DependencyInjection\Reference;
Symfony\Component\DependencyInjection\DefinitionDecorator;
Symfony\Component\Config\Definition\Builder\NodeDefinition;
Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;

use
use
use
use
use

class WsseFactory implements SecurityFactoryInterface


{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntry
{
$providerId = security.authentication.provider.wsse..$id;
$container
->setDefinition($providerId, new DefinitionDecorator(wsse.security.authentication.pro
->replaceArgument(0, new Reference($userProvider))
;

$listenerId = security.authentication.listener.wsse..$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator(wsse.security.
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function getPosition()
{
return pre_auth;
}
public function getKey()
{
return wsse;
}
public function addConfiguration(NodeDefinition $node)
{}
}

Linterfaccia Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFact
richiede i seguenti metodi:
metodo create, che aggiunge lascoltatore e il fornitore di autenticazione provider al contenitore di dipendenze per il contesto della sicurezza appropriato;
metodo getPosition, che deve essere del tipo pre_auth, form, http o remember_me e definisce
la posizione in cui il fornitore viene chiamato;
metodo getKey, che definisce la chiave di configurazione usata per fare riferimento al fornitore;

402

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

metodo addConfiguration, usato per definire le opzioni di configurazione sotto la chiave


configuration della configurazione della sciurezza. Le opzioni di configurazione sono spiegate pi
avanti in questo capitolo.

Note: Una classe non usata in questo esempio, Symfony\Bundle\SecurityBundle\DependencyInjection\Securit


una classe base molto utile, che fornisce funzionalit solitamente necessaria per i factory della sicurezza. Pu
tornare utile quando si definisce un fornitore di autenticazione di tipo diverso.
Una volta creata la classe factory, la chiave wsse pu essere usata con firewall nella configurazione della sicurezza.
Note: Ci si potrebbe chiedere il motivo per cui sia necessaria una speciale classe factory per aggiungere ascoltatori
e fornitori al contenitore di dipendenze. una buona domanda. La ragione che si pu usare il proprio firewall pi
volte, per proteggere diverse parti della propria applicazione. Per questo, ogni volta che si usa il proprio firewall,
il contenitore di dipendenze crea un nuovo servizio. Il factory serve a creare questi nuovi servizi.

Configurazione
tempo di vedere in azione il nostro fornitore di autenticazione. Servono ancora alcune cose per farlo funzionare.
La prima cosa aggiungere i servizi di cui sopra al contenitore di servizi. La classe factory vista prima fa
riferimento a degli id di servizi che non esistono ancora: wsse.security.authentication.provider
e wsse.security.authentication.listener. Ora definiremo questi servizi.
YAML
# src/Acme/DemoBundle/Resources/config/services.yml
services:
wsse.security.authentication.provider:
class: Acme\DemoBundle\Security\Authentication\Provider\WsseProvider
arguments: [, %kernel.cache_dir%/security/nonces]
wsse.security.authentication.listener:
class: Acme\DemoBundle\Security\Firewall\WsseListener
arguments: [@security.context, @security.authentication.manager]

XML

<!-- src/Acme/DemoBundle/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/

<services>
<service id="wsse.security.authentication.provider"
class="Acme\DemoBundle\Security\Authentication\Provider\WsseProvider" public="false"
<argument /> <!-- User Provider -->
<argument>%kernel.cache_dir%/security/nonces</argument>
</service>
<service id="wsse.security.authentication.listener"
class="Acme\DemoBundle\Security\Firewall\WsseListener" public="false">
<argument type="service" id="security.context"/>
<argument type="service" id="security.authentication.manager" />
</service>
</services>
</container>

PHP

3.1. Ricettario

403

Symfony2 documentation Documentation, Release 2

// src/Acme/DemoBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$container->setDefinition(wsse.security.authentication.provider,
new Definition(
Acme\DemoBundle\Security\Authentication\Provider\WsseProvider,
array(, %kernel.cache_dir%/security/nonces)
));
$container->setDefinition(wsse.security.authentication.listener,
new Definition(
Acme\DemoBundle\Security\Firewall\WsseListener, array(
new Reference(security.context),
new Reference(security.authentication.manager))
));

Ora che i servizi sono stati definiti, diciamo al contesto della sicurezza del factory. I factory devono essere inclusi
in un singolo file di configurazione, mentre stiamo scrivendo. Quindi, iniziamo creando il file con il servizio
factory, con tag security.listener.factory:
YAML
# src/Acme/DemoBundle/Resources/config/security_factories.yml
services:
security.authentication.factory.wsse:
class: Acme\DemoBundle\DependencyInjection\Security\Factory\WsseFactory
tags:
- { name: security.listener.factory }

XML

<!-- src/Acme/DemoBundle/Resources/config/security_factories.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/

<services>
<service id="security.authentication.factory.wsse"
class="Acme\DemoBundle\DependencyInjection\Security\Factory\WsseFactory" public="fa
<tag name="security.listener.factory" />
</service>
</services>
</container>

New in version 2.1: Prima della 2.1, il factory successivo veniva aggiunto tramite security.yml. Come ultimo
passo, aggiungere il factory allestensione della sicurezza nella classe del bundle.
// src/Acme/DemoBundle/AcmeDemoBundle.php
namespace Acme\DemoBundle;
use Acme\DemoBundle\DependencyInjection\Security\Factory\WsseFactory;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class AcmeDemoBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$extension = $container->getExtension(security);
$extension->addSecurityListenerFactory(new WsseFactory());
}

404

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Abbiamo finito! Ora si possono definire le parti dellapplicazione sotto protezione WSSE.
security:
firewalls:
wsse_secured:
pattern:
wsse:

/api/.*
true

Con questo abbiamo concluso la scrittura di un fornitore di autenticazione personalizzato.


Un piccolo extra
E se si volesse rendere il fornitore di autenticazione WSSE un po pi eccitante? Le possibilit sono infinite.
Possiamo iniziare a renderlo ancora pi brillante.
Configurazione

Si possono aggiungere opzioni personalizzate sotto la voce wsse nella configurazione della sicurezza. Per esempio, il tempo consentito predefinito prima della scadenza dellheader di creazione di 5 minuti. Lo si pu rendere
configurabile, in modo che firewall diversi possano avere lunghezze di scadenza diverse.
Occorre innanzitutto modificare WsseFactory e definire la nuova opzione nel metodo addConfiguration.
class WsseFactory implements SecurityFactoryInterface
{
# ...
public function addConfiguration(NodeDefinition $node)
{
$node
->children()
->scalarNode(lifetime)->defaultValue(300)
->end()
;
}
}

Ora, nel metodo create del factory, il parametro $config conterr una chiave lifetime, impostata a 5 minuti
(300 secondi), a meno che non sia specificato diversamente nella configurazione. Per usarlo, occorre passarlo
come parametro al proprio fornitore di autenticazione.

class WsseFactory implements SecurityFactoryInterface


{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntry
{
$providerId = security.authentication.provider.wsse..$id;
$container
->setDefinition($providerId,
new DefinitionDecorator(wsse.security.authentication.provider))
->replaceArgument(0, new Reference($userProvider))
->replaceArgument(2, $config[lifetime])
;
// ...
}
// ...
}

Note:
Occorre aggiungere anche un terzo parametro alla configurazione del servizio
wsse.security.authentication.provider, che potrebbe essere vuoto, oppure contenente il
3.1. Ricettario

405

Symfony2 documentation Documentation, Release 2

tempo di scadenza nel factory. La classe WsseProvider dovr anche accettare un terzo parametro nel
costruttore, il tempo, che dovrebbe usare al posto dei 300 secondi precedentemente fissati. Questi due passi non
sono mostrati.
Il tempo di scadenza di ogni richiesta WSSE ora configurabile e pu essere impostato con qualsiasi valore
desiderato per ogni firewall.
security:
firewalls:
wsse_secured:
pattern:
wsse:

/api/.*
{ lifetime: 30 }

Qualsiasi altra configurazione rilevante pu essere definita nel factory e utilizzata o passata a altre classi nel
contenitore.

3.1.53 Come cambiare il comportamento predefinito del puntamento del percorso


Per impostazione predefinita, il componente della sicurezza mantiene le informazioni sullURI dellultima richiesta in una variabile di sessione, chiamata _security.target_path. Dopo che un accesso viene eseguito,
lutente viene rinviato a questo percorso, come aiuto per continuare dallultima pagina visitata.
In alcune occasioni, questo comportamento inatteso. Per esempio, quando lURI dellultima richiesta era un
POST HTTP su una rotta configurata per consentire solo il metodo POST, lutente rinviato a tale rotta otterrebbe
solo un errore 404.
Per aggirare questo comportamento, occorre semplicemente estendere la classe ExceptionListener e
sovrascrivere il metodo chiamato setTargetPath().
Come prima cosa, sovrascrivere il parametro security.exception_listener.class nel proprio file
di configurazione. Lo si pu fare dalla propria configurazione principale (in app/config) oppure in un file di
configurazione importato da un bundle:
YAML
# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
# ...
security.exception_listener.class: Acme\HelloBundle\Security\Firewall\ExceptionListener

XML

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


<parameters>
<!-- ... -->
<parameter key="security.exception_listener.class">Acme\HelloBundle\Security\Firewall\Exc
</parameters>

PHP

// src/Acme/HelloBundle/Resources/config/services.php
// ...
$container->setParameter(security.exception_listener.class, Acme\HelloBundle\Security\Fire

Quindi, crare il proprio ExceptionListener:


// src/Acme/HelloBundle/Security/Firewall/ExceptionListener.php
namespace Acme\HelloBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Firewall\ExceptionListener as BaseExceptionListener;

406

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

class ExceptionListener extends BaseExceptionListener


{
protected function setTargetPath(Request $request)
{
// Non salvare il percorso del puntamento per richieste XHR o diverse da GET
// Si pu aggiungere altra logica, alloccorrenza
if ($request->isXmlHttpRequest() || GET !== $request->getMethod()) {
return;
}
$request->getSession()->set(_security.target_path, $request->getUri());
}
}

Si pu aggiungere tutta la logica necessaria ai propri scopi!

3.1.54 Come usare Varnish per accelerare il proprio sito


Poich la cache di Symfony2 usa gli header standard della cache HTTP, la Il reverse proxy di Symfony2 pu essere
facilmente sostituita da qualsiasi altro reverse proxy. Varnish un acceleratore HTTP potente e open source, che
in grado di servire contenuti in cache in modo veloce e include il supporto per Edge Side Include.
Configurazione
Come visto in precedenza, Symfony2 abbastanza intelligente da capire se sta parlando a un reverse proxy che
capisca ESI o meno. Funziona immediatamente, se si usa il reverse proxy di Symfony2, mentre occorre una
configurazione speciale per poter funzionare con Varnish. Fortunatamente, Symfony2 si appoggia a uno standard
scritto da Akama (Architettura Edge), quindi i suggerimenti di configurazione in questo capitolo possono essere
utili anche non usando Symfony2.
Note: Varnish supporta solo lattributo src per i tag ESI (onerror e alt vengono ignorati).
Prima di tutto, configurare Varnish in modo che pubblicizzi il suo supporto a ESI, aggiungendo un header
Surrogate-Capability alle richieste girate allapplicazione sottostante:
sub vcl_recv {
set req.http.Surrogate-Capability = "abc=ESI/1.0";
}

Quindi, ottimizzare Varnish in modo che analizzi i contenuti della risposta solo quando ci sia almeno un tag ESI,
verificando lheader Surrogate-Control, che Symfony2 aggiunge automaticamente:
sub vcl_fetch {
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
// per Varnish >= 3.0
set beresp.do_esi = true;
// per Varnish < 3.0
// esi;
}
}

Caution: La compressione con ESI non era supportata in Varnish fino alle versione 3.0 (leggere GZIP e
Varnish). Se non si usa Varnish 3.0, inserire un server web davanti a Varnish per eseguire la compressione.

3.1. Ricettario

407

Symfony2 documentation Documentation, Release 2

Invalidare la cache
Non si dovrebbe aver mai bisogno di invalidare dati in cache, perch linvalidazione gi gestita nativamente nei
modelli di cache HTTP (vedere Invalidazione della cache).
Tuttavia, Varnish pu essere configurato per accettare un metodo HTTP speciale PURGE, che invalida la cache per
una data risorsa:
sub vcl_hit {
if (req.request == "PURGE") {
set obj.ttl = 0s;
error 200 "Purged";
}
}
sub vcl_miss {
if (req.request == "PURGE") {
error 404 "Not purged";
}
}

Caution: Bisogna proteggere il metodo HTTP PURGE in qualche modo, per evitare che qualcuno pulisca i
dati in cache in modo casuale.

3.1.55 Iniettare variabili in tutti i template (variabili globali)


A volte si vuole che una variabile sia accessibile in tutti i template usati. Lo si pu fare, modificando il file
app/config/config.yml:
# app/config/config.yml
twig:
# ...
globals:
ga_tracking: UA-xxxxx-x

Ora, la variabile ga_tracking disponibile in tutti i template Twig


<p>Il codice di tracciamento Google : {{ ga_tracking }} </p>

molto facile! Si pu anche usare il sistema I parametri del servizio, che consente di isolare o riutilizzare il
valore:
; app/config/parameters.yml
[parameters]
ga_tracking: UA-xxxxx-x
# app/config/config.yml
twig:
globals:
ga_tracking: %ga_tracking%

La stessa variabile disponibile esattamente come prima.


Variabili globali pi complesse
Se la variabile globale che si vuole impostare pi complicata, per esempio un oggetto, non si potr usare il
metodo precedente. Invece, occorrer creare unestensione Twig e restituire la variabile globale come una delle
voci del metodo getGlobals.

408

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

3.1.56 Come usare PHP al posto di Twig nei template


Anche se Twig il motore predefinito di template in Symfony2, si pu ancora usare PHP, se lo si preferisce. Entrambi i motori di template sono supportati in ugual modo in Symfony2. Symfony2 aggiunge alcune caratteristiche
interessanti sopra PHP, per rendere la scrittura dei template pi potente.
Rendere i template PHP
Se si vuole usare il motore di template PHP, occorre prima di tutto assicurarsi di abilitarlo nel file di configurazione
della propria applicazione:
YAML
# app/config/config.yml
framework:
# ...
templating:
{ engines: [twig, php] }

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

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

Ora si pu rendere un template PHP invece di uno Twig, semplicemente usando nel nome del template lestensione
.php al posto di .twig. Il controllore sottostante rende il template index.html.php:
// src/Acme/HelloBundle/Controller/HelloController.php
public function indexAction($name)
{
return $this->render(HelloBundle:Hello:index.html.php, array(name => $name));
}

Decorare i template
Spesso i template in un progetto condividono elementi comuni, come la testata e il pie di pagina. In Symfony2,
ci piace pensare a questo problema in modo diverso: un template pu essere decorato da un altro template.
Il template index.html.php decorato layout.html.php, grazie alla chiamata a extend():
<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
<?php $view->extend(AcmeHelloBundle::layout.html.php) ?>
Ciao <?php echo $name ?>!

3.1. Ricettario

409

Symfony2 documentation Documentation, Release 2

La notazione HelloBundle::layout.html.php suona familiare, non vero? la stessa notazione usata


per fare riferimento a un template. La parte :: vuol dire semplicemente che lelemento controllore vuoto,
quindi il file corrispondente memorizzato direttamente sotto views/.
Diamo ora unocchiata al file layout.html.php:
<!-- src/Acme/HelloBundle/Resources/views/layout.html.php -->
<?php $view->extend(::base.html.php) ?>
<h1>Applicazione Ciao</h1>
<?php $view[slots]->output(_content) ?>

Il layout stesso decorato da un altro template (::base.html.php). Symfony2 supporta livelli molteplici di
decorazione: un layout pu esso stesso essere decorato da un altro layout. Quando la parte bundle del nome del
template vuota, le viste sono cercate nella cartella app/Resources/views/. Questa cartella contiene le
viste globali del proprio progetto:
<!-- 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, Hello Application) ?></title>
</head>
<body>
<?php $view[slots]->output(_content) ?>
</body>
</html>

Per entrambi i layout, lespressione $view[slots]->output(_content) viene sostituita dal contenuto del template figlio, rispettivamente index.html.php e layout.html.php (approfondiremo gli slot
nella prossima sezione).
Come si pu vedere, Symfony2 fornisce metodi su un misterioso oggetto $view. In un template, la variabile
$view sempre disponibile e fa riferimento a uno speciale oggetto che fornisce un sacco di metodi, che mantengono snello il motore dei template.
Lavorare con gli slot
Uno slot un pezzetto di codice, definito in un template e riutilizzabile in qualsiasi layout che decora il template.
Nel template index.html.php, definiamo uno slot title:
<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
<?php $view->extend(AcmeHelloBundle::layout.html.php) ?>
<?php $view[slots]->set(title, Applicazione Ciao mondo) ?>
Ciao <?php echo $name ?>!

Il layout base ha gi il codice per mostrare il titolo nella testata:


<!-- app/Resources/views/layout.html.php -->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><?php $view[slots]->output(title, Applicazione Ciao) ?></title>
</head>

Il metodo output() inserisce il contenuto di uno slot e accetta un valore predefinito opzionale, se lo slot non
definito. E _content solo uno slot speciale che contiene la resa del template figlio.
Per slot pi grandi, si pu usare una sintassi estesa:

410

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

<?php $view[slots]->start(title) ?>


Un sacco di HTML
<?php $view[slots]->stop() ?>

Includere altri template


Il modo migliore di condividere un pezzo di codice di template quello di definire un template che possa essere
incluso in altri template.
Creare un template hello.html.php:
<!-- src/Acme/HelloBundle/Resources/views/Hello/hello.html.php -->
Ciao <?php echo $name ?>!

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


<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
<?php $view->extend(AcmeHelloBundle::layout.html.php) ?>
<?php echo $view->render(AcmeHello:Hello:hello.html.php, array(name => $name)) ?>

Il metodo render() valuta e restituisce il contenuto di un altro template (questo esattamente lo stesso metodo
usato nel controllore).
Inserire altri controllori
Cosa fare se si vuole inserire il risultato di un altro controllore in un template? Pu essere molto utile lavorando
con Ajax, oppure quando il template inserito ha bisogno di variabili non disponibili nel template principale.
Se si crea unazione fancy e la si vuole includere nel template index.html.php, basta usare il seguente
codice:

<!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->


<?php echo $view[actions]->render(HelloBundle:Hello:fancy, array(name => $name, color =>

Qui la stringa HelloBundle:Hello:fancy si riferisce allazione fancy del controllore Hello:


// src/Acme/HelloBundle/Controller/HelloController.php
class HelloController extends Controller
{
public function fancyAction($name, $color)
{
// crear un oggettom basato sulla variabile $color
$object = ...;

return $this->render(HelloBundle:Hello:fancy.html.php, array(name => $name, object =


}
// ...
}

Ma dove definito $view[actions]? Come anche $view[slots], chiamato helper di template e


sar approfondito nella prossima sezione.
Usare gli helper di template
Il sistema di template di Symfony2 pu essere facilmente esteso tramite gli helper. Gli helper sono oggetti PHP
che forniscono caratteristiche utili nel contesto di un template. actions e slots sono due degli helper gi
disponibili in Symfony2.

3.1. Ricettario

411

Symfony2 documentation Documentation, Release 2

Creare collegamenti tra le pagine

Parlando di applicazioni web, non pu mancare la creazione di collegamenti. Invece di inserire a mano gli URL
nei template, lhelper router sa come generare gli URL, in base alla configurazione delle rotte. In questo modo,
tutti gli URL possono essere facilmente cambiati, cambiando la configurazione:
<a href="<?php echo $view[router]->generate(ciao, array(name => Thomas)) ?>">
Saluti Thomas!
</a>

Il metodo generate() accetta come parametri il nome della rotta e un array di parametri. Il nome della rotta
la chiave principale sotto cui le rotte sono referenziate e i parametri sono i valori dei segnaposto definiti nello
schema della rotta:
# src/Acme/HelloBundle/Resources/config/routing.yml
ciao: # Nome della rotta
pattern: /hello/{name}
defaults: { _controller: AcmeHelloBundle:Hello:index }

Usare le risorse: immagini, JavaScript e fogli di stile

Cosa sarebbe Internet senza immagini, JavaScript e fogli di stile? Symfony2 fornisce il tag assets per gestirli
facilmente:

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


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

Lo scopo principale dellhelper assets quello di rendere lapplicazione pi portabile. Grazie a questo helper,
si pu spostare la cartella radice dellapplicazione in qualsiasi punto sotto la propria cartella radice del web, senza
dover cambiare nulla nel codice dei template.
Escape delloutput
Quando si usano i template PHP, occorre fare escape delle variabili mostrate allutente:
<?php echo $view->escape($var) ?>

Per impostazione predefinita, il metodo escape() assume che la variabili sia inviata in output in un contesto
HTML. Il secondo parametro consente di cambiare il contesto. Per esempio, per mandare in output qualcosa in
uno script JavaScript, usare il contesto js:
<?php echo $view->escape($var, js) ?>

3.1.57 Come usare Monologo per scrivere log


Monolog una libreria di log per PHP 5.3 usata da Symfony2. ispirata dalla libreria LogBook di Python.
Utilizzo
In Monolog, ogni logger definisce un canale di log. Ogni canale ha una pila di gestori per scrivere i log (i gestori
possono essere condivisi).
Tip: Quando si inietta il logger in un servizio, si pu usar un canale personalizzato per vedere facilmente quale
parte dellapplicazione ha loggato il messaggio.

412

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Il gestore di base StreamHandler, che scrive log in un flusso (per impostazione definita, in
app/logs/prod.log in ambiente di produzione e in app/logs/dev.log in quello di sviluppo).
Monolog dispone anche di un potente gestore per il log in ambiente di produzione:
FingersCrossedHandler. Esso consente di memorizzare i messaggi in un buffer e di loggarli solo
se un messaggio raggiunge il livello di azione (ERROR, nella configurazione fornita con la standard edition)
girando i messaggi a un altro gestore.
Per loggare un messaggio, basta prendere il servizio logger dal contenitore, nel proprio controllore:
$logger = $this->get(logger);
$logger->info(Abbiamo preso il logger);
$logger->err(C stato un errore);

Tip: Usare solo i metodi dellinterfaccia Symfony\Component\HttpKernel\Log\LoggerInterface


consente di cambiare limplementazione del logger senza cambiare il proprio codice.

Usare diversi gestori

Il logger usa una pila di gestori, che sono richiamati in successione. Ci consente di loggare facilmente i messaggi
in molti modi.
YAML
monolog:
handlers:
syslog:
type: stream
path: /var/log/symfony.log
level: error
main:
type: fingers_crossed
action_level: warning
handler: file
file:
type: stream
level: debug

XML

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/m
<monolog:config>
<monolog:handler
name="syslog"
type="stream"
path="/var/log/symfony.log"
level="error"
/>
<monolog:handler
name="main"
type="fingers_crossed"
action-level="warning"
handler="file"
/>
<monolog:handler
name="file"

3.1. Ricettario

413

Symfony2 documentation Documentation, Release 2

type="stream"
level="debug"
/>
</monolog:config>
</container>

La configurazione appena vista definisce una pila di gestori, che saranno richiamati nellordine in cui sono stati
definiti.
Tip: Il gestore chiamato file non sar incluso nella pila, perch usato come gestore annidato del gestore
fingers_crossed.

Note: Se si vuole cambiare la configurazione di MonologBundle con un altro file di configurazione, occorre
ridefinire lintera pila. Non si possono fondere, perch lordine conta e una fusione non consente di controllare
lordine.

Cambiare il formattatore

Il gestore usa un Formatter per formattare un record, prima di loggarlo. Tutti i gestori di Monolog usano,
per impostazione predefinita, unistanza di Monolog\Formatter\LineFormatter, ma la si pu sostituire
facilmente. Il proprio formattatore deve implementare Monolog\Formatter\FormatterInterface.
YAML
services:
my_formatter:
class: Monolog\Formatter\JsonFormatter
monolog:
handlers:
file:
type: stream
level: debug
formatter: my_formatter

XML

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/m
<services>
<service id="my_formatter" class="Monolog\Formatter\JsonFormatter" />
</services>
<monolog:config>
<monolog:handler
name="file"
type="stream"
level="debug"
formatter="my_formatter"
/>
</monolog:config>
</container>

414

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Aggiungere dati extra nei messaggi di log


Monolog consente di processare il record prima di loggarlo, per aggiungere alcuni dati extra. Un processore pu
essere applicato allintera pila dei gestori oppure solo a un gestore specifico.
Un processore semplicemente una funzione che riceve il record come primo parametro.
I processori sono configurati con il tag monolog.processor del DIC. Vedere il riferimento.
Aggiungere un token di sessione/richiesta

A volte difficile dire quali voci nel log appartengano a quale sessione e/o richiesta. Lesempio seguente aggiunge
un token univoco per ogni richiesta, usando un processore.
namespace Acme\MyBundle;
use Symfony\Component\HttpFoundation\Session;
class SessionRequestProcessor
{
private $session;
private $token;
public function __construct(Session $session)
{
$this->session = $session;
}
public function processRecord(array $record)
{
if (null === $this->token) {
try {
$this->token = substr($this->session->getId(), 0, 8);
} catch (\RuntimeException $e) {
$this->token = ????????;
}
$this->token .= - . substr(uniqid(), -8);
}
$record[extra][token] = $this->token;
return $record;
}
}

YAML
services:
monolog.formatter.session_request:
class: Monolog\Formatter\LineFormatter
arguments:
- "[%%datetime%%] [%%extra.token%%] %%channel%%.%%level_name%%: %%message%%\n"
monolog.processor.session_request:
class: Acme\MyBundle\SessionRequestProcessor
arguments: [ @session ]
tags:
- { name: monolog.processor, method: processRecord }
monolog:
handlers:
main:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log

3.1. Ricettario

415

Symfony2 documentation Documentation, Release 2

level: debug
formatter: monolog.formatter.session_request

Note: Se si usano molti gestori, si pu anche registrare il processore a livello di gestore, invece che globalmente.

3.1.58 Come configurare Monolog con errori per email


Monolog pu essere configurato per inviare unemail quando accade un errore in unapplicazione. La configurazione per farlo richiede alcuni gestori annidati, per evitare di ricevere troppe email. Questa configurazione
appare complicata a prima vista, ma ogni gestore abbastanza semplice, se visto singolarmente.
YAML
# app/config/config.yml
monolog:
handlers:
mail:
type:
fingers_crossed
action_level: critical
handler:
buffered
buffered:
type:
buffer
handler: swift
swift:
type:
swift_mailer
from_email: error@example.com
to_email:
error@example.com
subject:
An Error Occurred!
level:
debug

XML

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/m
<monolog:config>
<monolog:handler
name="mail"
type="fingers_crossed"
action-level="critical"
handler="buffered"
/>
<monolog:handler
name="buffered"
type="buffer"
handler="swift"
/>
<monolog:handler
name="swift"
from-email="error@example.com"
to-email="error@example.com"
subject="An Error Occurred!"
level="debug"
/>
</monolog:config>
</container>

416

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Il gestore mail un gestore fingers_crossed, che vuol dire che viene evocato solo quando si raggiunge il
livello di azione, in questo caso critical. Esso logga ogni cosa, inclusi i messaggi sotto il livello di azione. Il
livello critical viene raggiunto solo per codici di errore HTTP 5xx. Limpostazione handler vuol dire che
loutput quindi passato nel gestore buffered.
Tip: Se si vuole che siano inviati per email sia gli errori 400 che i 500, impostare action_level a error,
invece che a critical.
Il gestore buffered mantiene tutti i messaggi per una richiesta e quindi li passa al gestore annidato in un colpo.
Se non si usa questo gestore, ogni messaggio sar inviato separatamente. Viene quindi passato al gestore swift.
Questo gestore quello che tratta effettivamente linvio della email con gli errori. Le sue impostazioni sono
semplici: gli indirizzi di mittente e destinatario e loggetto.
Si possono combinare questi gestori con altri gestori, in modo che gli errori siano comunque loggati sul server,
oltre che inviati per email:
YAML
# app/config/config.yml
monolog:
handlers:
main:
type:
fingers_crossed
action_level: critical
handler:
grouped
grouped:
type:
group
members: [streamed, buffered]
streamed:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.log
level: debug
buffered:
type:
buffer
handler: swift
swift:
type:
swift_mailer
from_email: error@example.com
to_email:
error@example.com
subject:
An Error Occurred!
level:
debug

XML

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/m
<monolog:config>
<monolog:handler
name="main"
type="fingers_crossed"
action_level="critical"
handler="grouped"
/>
<monolog:handler
name="grouped"
type="group"
>
<member type="stream"/>
<member type="buffered"/>

3.1. Ricettario

417

Symfony2 documentation Documentation, Release 2

</monolog:handler>
<monolog:handler
name="stream"
path="%kernel.logs_dir%/%kernel.environment%.log"
level="debug"
/>
<monolog:handler
name="buffered"
type="buffer"
handler="swift"
/>
<monolog:handler
name="swift"
from-email="error@example.com"
to-email="error@example.com"
subject="An Error Occurred!"
level="debug"
/>
</monolog:config>
</container>

Qui stato usato il gestore group, per inviare i messaggi ai due membri del gruppo, il gestore buffered e il
gestore stream. I messaggi saranno ora sia scritti sul log che inviati per email.

3.1.59 Come ottimizzare lambiente di sviluppo per il debug


Lavorando a un progetto Symfony sulla propria macchina locale, si dovrebbe utilizzare lambiente dev (il front
controller app_dev.php). La configurazione di questo ambiente ottimizzata per due scopi principali:
Dare accurate informazioni al programmatore quando qualcosa non funziona (barra web di debug, chiare
pagine delle eccezzioni, misurazione delle prestazioni, ...);
Essere pi simile possibile allambiente di produzione per evitare spiacevoli sorprese nel momento del
rilascio del progetto.
Disabilitare il file di avvio e la cache delle classi
Per rendere lambiente di produzione il pi veloce possibile, Symfony crea un unico file PHP, allinterno della
cache, che raccoglie tutte le classi PHP di cui ha bisogno il progetto. Un comportamento che potrebbe per
confondere lIDE o il debugger. Questa ricetta mostrer come modificare il meccanismo di gestione della cache
per rendere pi agevole il debug del codice relativo alle classi di Symfony.
Il front controller app_dev.php contiene, nella sua versione predefinita, il seguente codice:
// ...
require_once __DIR__./../app/bootstrap.php.cache;
require_once __DIR__./../app/AppKernel.php;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(dev, true);
$kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();

Per facilitare il lavoro del debugger, possibile disabilitare la cache di tutte le classi PHP rimuovendo la chiamata
a loadClassCache() e sostituendo la dichirazione del require, nel seguente modo:
// ...
// require_once __DIR__./../app/bootstrap.php.cache;

418

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

require_once __DIR__./../vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.ph
require_once __DIR__./../app/autoload.php;
require_once __DIR__./../app/AppKernel.php;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(dev, true);
// $kernel->loadClassCache();
$kernel->handle(Request::createFromGlobals())->send();

Tip: Una volta disabilitata la cache delle classi PHP, non bisogna dimenticare di riabilitarla alla fine della sessione
di debug.
Alcuni IDE non gradiscono il fatto che certe classi siano salvate in posti differenti. Per evitare problemi, possibile
o configurare lIDE per ignorare i file PHP della cache oppure modificare lestensione che Symfony assegna a
questi file:
$kernel->loadClassCache(classes, .php.cache);

3.1.60 Come estendere una classe senza usare lereditariet


Per consentire a molte classi di aggiungere metodi a unaltra classe, si pu definire il metodo magico __call()
nella classe che si vuole estendere, in questo modo:
class Pippo
{
// ...
public function __call($method, $arguments)
{
// crea un evento chiamato pippo.metodo_non_trovato
$event = new HandleUndefinedMethodEvent($this, $method, $arguments);
$this->dispatcher->dispatch($this, pippo.metodo_non_trovato, $event);

// nessun ascoltatore ha potuto processare levento? Il metodo non esiste


if (!$event->isProcessed()) {
throw new \Exception(sprintf(Metodo non definito %s::%s., get_class($this), $method)
}
// restituisce allascoltatore il valore restituito
return $event->getReturnValue();
}
}

Qui viene usato una classe speciale HandleUndefinedMethodEvent, che va creata. una classe generica
che potrebbe essere riusata ogni volta che si ha bisogno di questo tipo di estensione di classe:
use Symfony\Component\EventDispatcher\Event;
class HandleUndefinedMethodEvent extends Event
{
protected $subject;
protected $method;
protected $arguments;
protected $returnValue;
protected $isProcessed = false;
public function __construct($subject, $method, $arguments)
{
$this->subject = $subject;

3.1. Ricettario

419

Symfony2 documentation Documentation, Release 2

$this->method = $method;
$this->arguments = $arguments;
}
public function getSubject()
{
return $this->subject;
}
public function getMethod()
{
return $this->method;
}
public function getArguments()
{
return $this->arguments;
}
/**
* Imposta il valore da restituire e ferma le notifiche agli altri ascoltatori
*/
public function setReturnValue($val)
{
$this->returnValue = $val;
$this->isProcessed = true;
$this->stopPropagation();
}
public function getReturnValue($val)
{
return $this->returnValue;
}
public function isProcessed()
{
return $this->isProcessed;
}
}

Quindi, creare una classe che ascolter levento pippo.metodo_non_trovato e aggiungere il metodo
pluto():
class Pluto
{
public function onPippoMethodIsNotFound(HandleUndefinedMethodEvent $event)
{
// vogliamo rispondere solo alle chiamate al metodo pluto
if (pluto != $event->getMethod()) {
// consente agli altri ascoltatori di prendersi cura di questo metodo sconosciuto
return;
}
// loggetto in questione (listanza di Pippo)
$pippo = $event->getSubject();
// i parametri del metodo pluto
$arguments = $event->getArguments();
// fare qualcosa
// ...
// impostare il valore restituito

420

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

$event->setReturnValue($someValue);
}
}

Infine, aggiungere il nuovo metodo pluto alla classe Pippo, registrando unistanza di Pluto con levento
pippo.metodo_non_trovato:
$pluto = new Pluto();
$dispatcher->addListener(pippo.metodo_non_trovato, $pluto);

3.1.61 Come personalizzare il comportamento di un metodo senza usare


lereditariet
Fare qualcosa prima o dopo la chiamata a un metodo
Se si vuole fare qualcosa subito prima o subito dopo che un metodo sia chiamato, si pu inviare un evento rispettivamente allinizio o alla fine del metodo:
class Pippo
{
// ...
public function send($foo, $bar)
{
// fa qualcosa prima del metodo
$event = new FilterBeforeSendEvent($foo, $bar);
$this->dispatcher->dispatch(foo.pre_send, $event);
// prende $foo e $bar dallevento, potrebbero essere stati modificati
$foo = $event->getFoo();
$bar = $event->getBar();
// la vera implementazione del metodo qui
// $ret = ...;
// fa qualcosa dopo il metodo
$event = new FilterSendReturnValue($ret);
$this->dispatcher->dispatch(foo.post_send, $event);
return $event->getReturnValue();
}
}

In questo esempio, vengono lanciati due eventi: foo.pre_send, prima che il metodo sia eseguito, e
foo.post_send, dopo che il metodo eseguito. Ciascuno usa una classe Event personalizzata per comunicare informazioni agli ascoltatori di questi due eventi. Queste classi evento andrebbero create dallo sviluppatore
e dovrebbero consentire, in questo esempio, alle variabili $foo, $bar e $ret di essere recuperate e impostate
dagli ascoltatori.
Per esempio, ipotizziamo che FilterSendReturnValue abbia un metodo setReturnValue. un ascoltatore potrebbe assomigliare a questo:
public function onFooPostSend(FilterSendReturnValue $event)
{
$ret = $event->getReturnValue();
// modifica il valore originario di $ret
$event->setReturnValue($ret);
}

3.1. Ricettario

421

Symfony2 documentation Documentation, Release 2

3.1.62 Registrare un nuovo formato di richiesta e un nuovo tipo mime


Ogni Richiesta ha a un formato (come html, json), che viene usato per determinare il tipo
di contenuto che dovr essere restituito nell Risposta. Il formato della richiesta, accessibile tramite
:method:Symfony\\Component\\HttpFoundation\\Request::getRequestFormat, viene infatti utilizzato per
definire il tipo MIME dellintestazione Content-Type delloggetto Risposta. Symfony contiene una
mappa dei formati pi comuni (come html, json) e del corrispettivo tipo MIME (come text/html,
application/json). comunque possibile aggiungere nuovi formati-MIME. In questo documento si vedr come aggiungere un nuovo formato jsonp e il corrispondente tipo MIME.
Creazione di un ascoltatore per il kernel.request
La chiave per definire un nuovo tipo MIME creare una classe che rimarr in ascolto dellevento
kernel.request emesso dal kernel di Symfony. Levento kernel.request emesso da Symfony nelle
primissime fasi della gestione della richiesta e permette di modificare l oggetto richiesta.
Si crea una classe simile alla seguente, sostituendo i percorsi in modo che puntino ad un bundle del proprio
progetto:
// src/Acme/DemoBundle/RequestListener.php
namespace Acme\DemoBundle;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$event->getRequest()->setFormat(jsonp, application/javascript);
}
}

Registrazione dellascoltatore
Come per ogni ascoltatore, necessario aggiungere anche questo nel file di configurazione e registrarlo come tale
aggiungendogli il tag kernel.event_listener:
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"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
<service id="acme.demobundle.listener.request" class="Acme\DemoBundle\RequestListener">
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
</service>
</container>

YAML

# app/config/config.yml
services:
acme.demobundle.listener.request:
class: Acme\DemoBundle\RequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

422

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

PHP

# app/config/config.php
$definition = new Definition(Acme\DemoBundle\RequestListener);
$definition->addTag(kernel.event_listener, array(event => kernel.request, method =>
$container->setDefinition(acme.demobundle.listener.request, $definition);

A questo punto, il servizio acme.demobundle.listener.request stato configurato e verr notificato


dellavvenuta emissione, da parte del kernel Symfony, dellevento kernel.request.
Tip: possibile registrare lascoltatore anche in una classe di estensione della configurazione (si veda Importare
la configurazione attraverso estensioni del contenitore per ulteriori informazioni).

3.1.63 Come creare un raccoglitore di dati personalizzato


Il Profiler di Symfony delega la raccolta di dati ai raccoglitori di dati. Symfony2 dispone di un paio di
raccoglitori, ma se ne possono creare di personalizzati.
Creare un raccoglitore di dati personalizzato
Creare
un
raccoglitore
di
dati
personalizzato

semplice,
basta
implementare
Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface:
interface DataCollectorInterface
{
/**
* Collects data for the given Request and Response.
*
$request
A Request instance
* @param Request
$response A Response instance
* @param Response
* @param \Exception $exception An Exception instance
*/
function collect(Request $request, Response $response, \Exception $exception = null);
/**
* Returns the name of the collector.
*
* @return string The collector name
*/
function getName();
}

Il metodo getName() deve restituire un nome univoco. Viene usato per accedere successivamente
allinformazione (vedere Come usare il profilatore nei test funzionali, per esempio).
Il metodo collect() responsabile della memorizzazione dei dati, a cui vuole dare accesso, in propriet locali.
Caution: Siccome il profilatore serializza istanze di raccoglitori di dati, non si dovrebbero memorizzare
oggetti che non possono essere serializzati (come gli oggetti PDO), altrimenti occorre fornire il proprio metodo
serialize().

La maggior parte delle volte, conviene estendere Symfony\Component\HttpKernel\DataCollector\DataCollector


e popolare la propriet $this->data (che si occupa di serializzare la propriet $this->data):
class MemoryDataCollector extends DataCollector
{
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$this->data = array(

3.1. Ricettario

423

Symfony2 documentation Documentation, Release 2

memory => memory_get_peak_usage(true),


);
}
public function getMemory()
{
return $this->data[memory];
}
public function getName()
{
return memory;
}
}

Abilitare i raccoglitori di dati personalizzati


Per abilitare un raccoglitore di dati, aggiungerlo come servizio in una delle proprie configurazioni e assegnarli il
tag data_collector:
YAML
services:
data_collector.your_collector_name:
class: Fully\Qualified\Collector\Class\Name
tags:
- { name: data_collector }

XML

<service id="data_collector.your_collector_name" class="Fully\Qualified\Collector\Class\Name"


<tag name="data_collector" />
</service>

PHP
$container
->register(data_collector.your_collector_name, Fully\Qualified\Collector\Class\Name)
->addTag(data_collector)
;

Aggiungere template al profilatore web


Quando si vogliono mostrare i dati raccolti dal proprio raccoglitore di dati nella barra di debug del web, oppure
nel profilatore web, creare un template Twig, seguendo questo scheletro:
{% extends WebProfilerBundle:Profiler:layout.html.twig %}
{% block toolbar %}
{# contenuto della barra di debug del web #}
{% endblock %}
{% block head %}
{# se il profiltatore web ha bisogno di file JS o CSS #}
{% endblock %}
{% block menu %}
{# contenuto del men #}
{% endblock %}
{% block panel %}

424

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

{# contenuto del pannello #}


{% endblock %}

I blocchi sono tutti facoltativi. Il blocco toolbar usato per la barra di debug del web, mentre menu e panel
sono usati per aggiungere un pannello al profilatore web.
Tutti i blocchi hanno accesso alloggetto collector.
Tip:
I
template
predefiniti
usano
immagini
codificate
in
base64
barra
(<img src="src="data:image/png;base64,...").
Si
pu
lare facilmente il valore base64 di unimmagine con questo piccolo script:
base64_encode(file_get_contents($_SERVER[argv][1]));.

per
la
calcoecho

Per abilitare il template, aggiungere un attributo template al tag data_collector nella propria configurazione. Per esempio, ipotizzando che il template sia in un AcmeDebugBundle:
YAML

services:
data_collector.your_collector_name:
class: Acme\DebugBundle\Collector\Class\Name
tags:
- { name: data_collector, template: "AcmeDebug:Collector:templatename", id: "your

XML

<service id="data_collector.your_collector_name" class="Acme\DebugBundle\Collector\Class\Name


<tag name="data_collector" template="AcmeDebug:Collector:templatename" id="your_collector
</service>

PHP

$container
->register(data_collector.your_collector_name, Acme\DebugBundle\Collector\Class\Name)
->addTag(data_collector, array(template => AcmeDebugBundle:Collector:templatename,
;

3.1.64 Come creare un servizio web SOAP in un controllore di Symfony2


Impostare un controllore per agire da server SOAP semplice, con un paio di strumenti. Occorre avere, ovviamente, lestensione PHP SOAP installata. Poich lestensione PHP SOAP non pu attualmente generare un
WSDL, se ne deve creare uno da zero, oppure usare un generatore di terze parti.
Note: Ci sono molte implementazioni di server SOAP disponibili per PHP. Zend SOAP e NuSOAP sono due
esempi. Anche se useremo lestensione PHP SOAP nei nostri esempi, lidea generale dovrebbe essere applicabile
ad altre implementazioni.
SOAP funziona espondendo i metodi di un oggetto PHP a unentit esterna (alla persona che usa il servizio
SOAP). Per iniziare, creare una classe HelloService, che rappresenta la funzionalit che sar esposta nel
servizio SOAP. In questo caso, il servizio SOAP consentir al client di richiamare un metodo chiamto hello, che
invia unemail:
namespace Acme\SoapBundle;
class HelloService
{
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{

3.1. Ricettario

425

Symfony2 documentation Documentation, Release 2

$this->mailer = $mailer;
}
public function hello($name)
{
$message = \Swift_Message::newInstance()
->setTo(me@example.com)
->setSubject(Servizio Hello)
->setBody($name . dice ciao!);
$this->mailer->send($message);

return Hello, . $name;


}
}

Quindi, si pu dire a Symfony di creare unistanza di questa classe. Poich la classe invia unemail, stata
concepita per accettare unistanza di Swift_Mailer. Usando il contenitore di servizi, possiamo configurare
Symfony per costruire un oggetto HelloService in modo appropriato:
YAML
# app/config/config.yml
services:
hello_service:
class: Acme\DemoBundle\Services\HelloService
arguments: [mailer]

XML
<!-- app/config/config.xml -->
<services>
<service id="hello_service" class="Acme\DemoBundle\Services\HelloService">
<argument>mailer</argument>
</service>
</services>

Di seguito un esempio di un controllore che in grando di gestire una richiesta SOAP. Se indexAction()
accessibile tramite la rotta /soap, il documento WSDL pu essere recuperato tramite /soap?wsdl.
namespace Acme\SoapBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloServiceController extends Controller
{
public function indexAction()
{
$server = new \SoapServer(/path/to/hello.wsdl);
$server->setObject($this->get(hello_service));
$response = new Response();
$response->headers->set(Content-Type, text/xml; charset=ISO-8859-1);
ob_start();
$server->handle();
$response->setContent(ob_get_clean());
return $response;
}
}

426

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Si notino le chiamate a ob_start() e ob_get_clean(). Qesti metodi controllano il buffer delloutput, che
consente di intrappolare loutput inviato da $server->handle(). Questo si rende necessario, in quanto
Symfony si aspetta che il controllore restituisca un oggetto Response, con loutput come contenuto. Si deve
anche ricordare di impostare lheader Content-Type a text/xml, che quello che il client si aspetta. Quindi,
si usa ob_start() per iniziare il buffer di STDOUT e ob_get_clean() per inviare loutput nel contenuto
della risposta e per pulire il buffer. Infine, tutto pronto per restituire loggetto Response.
Di seguito un esempio che richiama il servizio, usando un client NuSOAP. Questo esempio presume che
indexAction nel controllore visto sopra sia accessibile tramite la rotta /soap:
$client = new \soapclient(http://example.com/app.php/soap?wsdl, true);
$result = $client->call(hello, array(name => Scott));

Di seguito, un esempio di WSDL


<?xml version="1.0" encoding="ISO-8859-1"?>
<definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="urn:arnleadservicewsdl"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="urn:helloservicewsdl">
<types>
<xsd:schema targetNamespace="urn:hellowsdl">
<xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" />
<xsd:import namespace="http://schemas.xmlsoap.org/wsdl/" />
</xsd:schema>
</types>
<message name="helloRequest">
<part name="name" type="xsd:string" />
</message>
<message name="helloResponse">
<part name="return" type="xsd:string" />
</message>
<portType name="hellowsdlPortType">
<operation name="hello">
<documentation>Hello World</documentation>
<input message="tns:helloRequest"/>
<output message="tns:helloResponse"/>
</operation>
</portType>
<binding name="hellowsdlBinding" type="tns:hellowsdlPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="hello">
<soap:operation soapAction="urn:arnleadservicewsdl#hello" style="rpc"/>
<input>
<soap:body use="encoded" namespace="urn:hellowsdl"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
<output>
<soap:body use="encoded" namespace="urn:hellowsdl"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
</binding>
<service name="hellowsdl">
<port name="hellowsdlPort" binding="tns:hellowsdlBinding">
<soap:address location="http://example.com/app.php/soap" />
</port>
</service>

3.1. Ricettario

427

Symfony2 documentation Documentation, Release 2

</definitions>

3.1.65 Differenze tra Symfony2 e symfony1


Il framework Symfony2 rappresenta unimportante evoluzione rispetto alla sua versione precedente. Fortunatamente, con larchitettura MVC al suo interno, le abilit usate per padroneggiare un progetto symfony1 continuano
a essere molto importanti per lo sviluppo con Symfony2. Certo, non c pi app.yml, ma le rotte, i controllori e
i template ci sono ancora tutti.
In questo capitolo analizzeremo le differenze tra symfony1 e Symfony2. Come vedremo, diverse cose sono implementate in modo un po diverso. Si imparer ad apprezzare tali differenze, in quanto esse promuovono nella
propria applicazione Symfony2 un codice stabile, prevedibile, testabile e disaccoppiato.
Prendiamoci dunque un po di relax, per andare da allora ad adesso.
Struttura delle cartelle
Guardando a un progetto Symfony2, per esempio Symfony2 Standard, si noter una struttura di cartelle molto
diversa rispetto a symfony1. Le differenze, tuttavia, sono in qualche modo superficiali.
La cartella app/

In symfony1, un progetto ha una o pi applicazioni, ognuna delle quali risiede nella cartella apps/ (per esempio apps/frontend). La configurazione predefinita di Symfony2 di avere ununica applicazione, nella
cartella app/. Come in symfony1, la cartella app/ contiene una configurazione specifica per quellapplicazione.
Contiene inoltre cartelle di cache, log e template specifiche dellapplicazione, come anche una classe Kernel
(AppKernel), che loggetto di base che rappresenta lapplicazione.
Diversamente da symfony1, c pochissimo codice PHP nella cartella app/. Questa cartella non pensata per
ospitare moduli o file di librerie, come era in symfony1. Invece, semplicemente il posto in cui risiedono la
configurazione e altre risorse (template, file di traduzione).
La cartella src/

Semplicemente, il proprio codice va messo qui. In Symfony, tutto il codice relativo alle applicazioni risiede in
un bundle (pressappoco equivalente a un plugin di symfony1) e ogni bundle risiede, per impostazione predefinita,
nella cartella src. In questo modo, la cartella src un po come la cartella plugins di symfony1, ma molto
pi flessibile. Inoltre, mentre i propri bundle risiedono nella cartella src, i bundle di terze parti possono risedere
nella cartella vendor/bundles/.
Per avere un quadro pi completo della cartella src/, pensiamo a unapplicazione symfony1. Innanzitutto, parte
del proprio codice probabilmente risiede in una o pi applicazioni. Solitamente questo include dei moduli, ma
potrebbe anche includere altre classi PHP inserite nella propria applicazione. Si potrebbe anche aver creato un
file schema.yml nella cartella config del progetto e costruito diversi file di modello. Infine, per aiutarsi con
alcune funzionalit comuni, si usano diversi plugin di terze parte, che stanno nella cartella plugins/. In altre
parole, il codice che guida la propria applicazione risiede in molti posti diversi.
In Symfony2, la vite molto pi semplice, perch tutto il codice di Symfony2 deve risiedere in un bundle. Nel
nostro ipotetico progetto symfony1, tutto il codice potrebbe essere spostato in uno o pi plugin (che in effetti una
buona pratica). Ipotizzando che tutti i moduli, le classi PHP, lo schema, la configurazione delle rotte, eccetera siano
spostate in un plugin, la cartella plugins/ di symfony1 sarebbe molto simile alla cartella src/ di Symfony2.
Detto semplicemente, la cartella src/ il posto in cui risiedono il proprio codice, le risorse, i template e quasi
ogni altra cosa specifica del proprio progetto.

428

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

La cartella vendor/

La cartella vendor/ essenzialmente equivalente alla cartella lib/vendor/ in symfony1, che era la cartella
convenzionale per tutte le librerie di terze parti. Per impostazione predefinita, si troveranno le librerie di Symfony2
in questa cartella, insieme a diverse altre librerie indipendenti, come Doctrine2, Twig e Swiftmailer. I bundle di
Symfony2 di terze parti solitamente risiedono in vendor/bundles/.
La cartella web/

Non cambiato molto nella cartella web/. La differenza pi notevole lassenza delle cartelle css/, js/ e
images/. La cosa intenzionale. Come il proprio codice PHP, tutte le risorse dovrebbero risiedere allinterno
di un bundle. Con laiuto di un comando della console, la cartella Resources/public/ di ogni bundle viene
copiata o collegata alla cartella web/bundles/. Questo consente di mantenere le risorse organizzate nel proprio
bundle, ma ancora disponibili pubblicamente. Per assicurarsi che tutti i bundle siano disponibili, eseguire il
seguente comando:
php app/console assets:install web

Note: Questo comando di Symfony2 lequivalente del comando plugin:publish-assets di symfony1.

Auto-caricamento
Uno dei vantaggi dei framework moderni il non doversi preoccupare di richiedere i file. Utilizzando un autoloader, si pu fare riferimento a qualsiasi classe nel proprio progetto e fidarsi che essa sia disponibile. Lautocaricamento cambiato in Symfony2, per essere pi universale, pi veloce e indipendente dalla pulizia della
cache.
In symfony1, lauto-caricamento era effettuato cercando nellintero progetto la presenza di file di classe PHP e
mettendo in cache tale informazione in un gigantesco array. Questo array diceva a symfony1 esattamente quale
file conteneva ciascuna classe. Nellambiente di produzione, questo causava la necessit di dover pulire la cache
quando una classe veniva aggiunta o spostata.
In Symfony2, una nuova classe, UniversalClassLoader gestisce questo processo. Lidea dietro
allautoloader semplice: il nome della propria classe (incluso lo spazio dei nomi) deve corrispondere al percorso del file che contiene tale classe. Si prenda come esempio FrameworkExtraBundle, nella Standard
Edition di Symfony2:
namespace Sensio\Bundle\FrameworkExtraBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
// ...
class SensioFrameworkExtraBundle extends Bundle
{
// ...

Il file stesso risiede in vendor/bundle/Sensio/Bundle/FrameworkExtraBundle/SensioFrameworkExtraBundl


Come si pu vedere, la locazione del file segue lo spazio dei nomi della classe. Nello specifico, lo spazio
dei nomi Sensio\Bundle\FrameworkExtraBundle dice che la cartella in cui il file dovrebbe
risiedere (vendor/bundle/Sensio/Bundle/FrameworkExtraBundle). Per questo motivo, nel file
app/autoload.php, si dovr configurare Symfony2 per cercare lo spazio dei nomi Sensio nella cartella
vendor/bundle:
// app/autoload.php
// ...
$loader->registerNamespaces(array(
// ...

3.1. Ricettario

429

Symfony2 documentation Documentation, Release 2

Sensio

=> __DIR__./../vendor/bundles,

));

Se il file non risiede in questa esatta locazione,


si ricever un errore Class
"Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle" does not
exist.. In Symfony2, un errore class does not exist vuol dire che lo spazio dei nomi della classe e la
locazione fisica del file non corrispondono. Fondamentalmente, Symfony2 cerca in una specifica locazione quella
classe, ma quella locazione non esiste (oppure contiene una classe diversa). Per poter auto-caricare una classe,
non mai necessario pulire la cache in Symfony2.
Come gi accennato, per poter far funzionare lautoloader, esso deve sapere che lo spazio dei nomi Sensio
risiede nella cartella vendor/bundles e che, per esempio, lo spazio dei nomi Doctrine risiede nella cartella
vendor/doctrine/lib/. Questa mappatura interamente controllata dallo sviluppatore, tramite il file
app/autoload.php.
Se si d unocchiata a HelloController nella Standard Edition di Symfony2, si vedr che esso risiede nello
spazio dei nomi Acme\DemoBundle\Controller. Anche qui, lo spazio dei nomi Acme non definito
in app/autoload.php. Non occorre configurare esplicitamente la locazione dei bundle che risiedono nella
cartella src/. UniversalClassLoader configurato per usare come locazione di riserva la cartella src/,
usando il suo metodo registerNamespaceFallbacks:
// app/autoload.php
// ...
$loader->registerNamespaceFallbacks(array(
__DIR__./../src,
));

Uso della console


In symfony1, la console nella cartella radice del progetto ed chiamata symfony:
php symfony

In Symfony2, la console ora nella sotto-cartella app ed chiamata console:


php app/console

Applicazioni
In un progetto basato su symfony 1, frequente avere diverse applicazioni: per esempio, una per il frontend e una
per il backend.
In un progetto basato su Symfony2, occorre creare una sola applicazione (unapplicazione blog, unapplicazione
intranet, ...). La maggior parte delle volte, se si vuole creare una seconda applicazione, sarebbe meglio creare un
altro progetto e condividere alcuni bundle tra essi.
Se poi si ha bisogno di separare le caratteristiche di frontend e di backend di alcuni bundle, creare dei sottospazi per controller, delle sotto-cartelle per i template, configurazioni semantiche diverse, configurazioni di rotte
separate e cos via.
Ovviamente non c nulla di sbagliato ad avere pi di unapplicazione nel proprio progetto, questa scelta lasciata
allo sviluppatore. Una seconda applicazione vorrebbe dire una nuova cartella, per esempio app2/, con la stessa
struttura di base della cartella app/.
Bundle e plugin
In un progetto symfony1, un plugin pu contenere configurazioni, moduli, librerie PHP, risorse e qualsiasi altra
cosa relativa al proprio progetto. In Symfony2, lidea di plugin stata rimpiazzata con quella di bundle. Un
bundle ancora pi potente di un plugin, perch il nucleo stesso del framework Symfony2 costituito da una
430

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

serie di bundle. In Symfony2, i bundle sono cittadini di prima classe e sono cos flessibili che il nucleo stesso un
bundle.
In symfony1, un plugin deve essere abilitato nella classe ProjectConfiguration:
// config/ProjectConfiguration.class.php
public function setup()
{
$this->enableAllPluginsExcept(array(/* nomi dei plugin */));
}

In Symfony2, i bundle sono attivati nel kernel dellapplicazione:


// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
// ...
new Acme\DemoBundle\AcmeDemoBundle(),
);
return $bundles;
}

Rotte (routing.yml) e configurazione (config.yml)

In symfony1, i file di configurazione routing.yml e app.yml sono caricati automaticamente allinterno di un


plugin. In Symfony2, le rotte e le configurazioni dellapplicazioni allinterno di un bundle vanno incluse a mano.
Per esempio, per inmcludere le rotte di un bundle chiamato AcmeDemoBundle, si pu fare nel seguente modo:
# app/config/routing.yml
_hello:
resource: "@AcmeDemoBundle/Resources/config/routing.yml"

Questo caricher le rotte trovate nel file Resources/config/routing.yml di AcmeDemoBundle. Il


nome @AcmeDemoBundle una sintassi abbreviata, risolta internamente con il percorso completo di quel bundle.
Si pu usare la stessa strategia per portare una configurazione da un bundle:
# app/config/config.yml
imports:
- { resource: "@AcmeDemoBundle/Resources/config/config.yml" }

In Symfony2, la configurazione un po come app.yml in symfony1, ma pi sistematica. Con app.yml, si


poteva semplicemente creare le voci volute. Per impostazione predefinita, queste voci erano prive di significato ed
era lasciato allo sviluppatore il compito di usarle nella propria applicazione:
# un file app.yml da symfony1
all:
email:
from_address: foo.bar@example.com

In Symfony2, si possono ancora creare voci arbitrarie sotto la voce parameters della propria configurazione:
parameters:
email.from_address: foo.bar@example.com

Si pu ora accedervi da un controllore, per esempio:

3.1. Ricettario

431

Symfony2 documentation Documentation, Release 2

public function helloAction($name)


{
$fromAddress = $this->container->getParameter(email.from_address);
}

In realt, la configurazione di Symfony2 molto pi potente ed usata principalmente per configurare oggetti da
usare. Per maggiori informazioni, vedere il capitolo intitolato Contenitore di servizi.
Flusso di lavoro
Come creare e memorizzare un progetto Symfony2 in git
Come creare e memorizzare un progetto Symfony2 in Subversion
Controllori
Come personalizzare le pagine di errore
Definire i controllori come servizi
Rotte
Come forzare le rotte per utilizzare sempre HTTPS
Come permettere un carattere / in un parametro di rotta
Gestione di JavaScript e CSS
Come usare Assetic per la gestione delle risorse
Minimizzare i file JavaScript e i fogli di stile con YUI Compressor
Usare Assetic per lottimizzazione delle immagini con le funzioni di Twig
Applicare i filtri di Assetic a file con specifiche estensioni
Interazione col database (Doctrine)
Come gestire il caricamento di file con Doctrine
Estensioni di Doctrine: Timestampable: Sluggable, Translatable, ecc.
Registrare ascoltatori e sottoscrittori di eventi
Come usare il livello DBAL di Doctrine
Come generare entit da una base dati esistente
Come lavorare con gestori di entit multipli
Registrare funzioni DQL personalizzate
Form e validazione
Come personalizzare la resa dei form
Utilizzare i data transformer
Come generare dinamicamente form usando gli eventi form
Come unire una collezione di form
Come creare un tipo di campo personalizzato di un form
Come creare vincoli di validazione personalizzati
(doctrine) Come gestire il caricamento di file con Doctrine
Configurazione e contenitore di servizi
Come padroneggiare e creare nuovi ambienti
Configurare parametri esterni nel contenitore dei servizi
Usare il factory per creare servizi

432

Chapter 3. Ricettario

Symfony2 documentation Documentation, Release 2

Gestire le dipendenza comuni con i servizi padre


Come lavorare con gli scope
Come far s che i servizi usino le etichette
Usare PdoSessionStorage per salvare le sessioni nella base dati
Bundle
Struttura del bundle e best practice
Come usare lereditariet per sovrascrivere parti di un bundle
Come sovrascrivere parti di un bundle
Come esporre una configurazione semantica per un bundle
Email
Come spedire unemail
Come usare Gmail per linvio delle email
Lavorare con le email durante lo sviluppo
Lo spool della posta
Test
Come simulare unautenticazione HTTP in un test funzionale
Come testare linterazione con diversi client
Come usare il profilatore nei test funzionali
Come testare i repository Doctrine
Sicurezza
Come caricare gli utenti dal database (il fornitore di entit)
Come aggiungere la funzionalit ricordami al login
Come implementare i propri votanti per una lista nera di indirizzi IP
Access Control List (ACL)
Concetti avanzati su ACL
Come forzare HTTPS o HTTP per URL diversi
Come personalizzare il form di login
Proteggere servizi e metodi di unapplicazione
Come creare un fornitore utenti personalizzato
Come creare un fornitore di autenticazione personalizzato
Come cambiare il comportamento predefinito del puntamento del percorso
Cache
Come usare Varnish per accelerare il proprio sito
Template
Iniettare variabili in tutti i template (variabili globali)
Come usare PHP al posto di Twig nei template
Strumenti, log e interni
Come usare Monologo per scrivere log
Come configurare Monolog con errori per email

3.1. Ricettario

433

Symfony2 documentation Documentation, Release 2

Strumenti e interni
Come ottimizzare lambiente di sviluppo per il debug
Servizi web
Come creare un servizio web SOAP in un controllore di Symfony2
Estendere Symfony
Come estendere una classe senza usare lereditariet
Come personalizzare il comportamento di un metodo senza usare lereditariet
Registrare un nuovo formato di richiesta e un nuovo tipo mime
Come creare un raccoglitore di dati personalizzato
Symfony2 per utenti di symfony1
Differenze tra Symfony2 e symfony1
Leggere il ricettario.

434

Chapter 3. Ricettario

CHAPTER

FOUR

COMPONENTI
4.1 I componenti
4.1.1 Il componente ClassLoader
Il componente ClassLoader carica le classi di un progetto automaticamente, purch seguano alcune
convenzioni standard di PHP.
Ogni volta che si usa una classe non ancora definita, PHP utilizza il meccanismo di auto-caricamento per delegare
il caricamento di un file che definisca la classe. Symfony2 fornisce un autoloader universale, capace di caricare
classi da file che implementano una delle seguenti convenzioni:
Gli standard tecnici di interoperabilit per i nomi di classi e gli spazi dei nomi di PHP 5.3;
La convenzione dei nomi delle classi di PEAR.
Se le proprie classi e le librerie di terze parti usate per il proprio progetto seguono questi standard, lautoloader di
Symfony2 lunico autoloader di cui si ha bisogno.
Installazione
Si pu installare il componente in molti modi diversi:
Usare il repository ufficiale su Git (https://github.com/symfony/ClassLoader);
Installarlo via PEAR ( pear.symfony.com/ClassLoader);
Installarlo via Composer (symfony/class-loader su Packagist).
Uso
New in version 2.1: Il metodo useIncludePath stato aggiunto in Symfony 2.1. La registrazione di
Symfony\Component\ClassLoader\UniversalClassLoader molto semplice:
require_once /percorso/src/Symfony/Component/ClassLoader/UniversalClassLoader.php;
use Symfony\Component\ClassLoader\UniversalClassLoader;
$loader = new UniversalClassLoader();
// Si pu cercare in include_path come ultima risorsa.
$loader->useIncludePath(true);
$loader->register();

Per un minimo guadagno di prestazioni, i percorsi delle classi possono essere memorizzati usando APC, registrando Symfony\Component\ClassLoader\ApcUniversalClassLoader:

435

Symfony2 documentation Documentation, Release 2

require_once /percorso/src/Symfony/Component/ClassLoader/UniversalClassLoader.php;
require_once /percorso/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php;
use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
$loader = new ApcUniversalClassLoader(apc.prefix.);
$loader->register();

Lautoloader utile solo se si aggiungono delle librerie da auto-caricare.


Note:
Lautoloader registrato automaticamente in ogni applicazione Symfony2 (si veda
app/autoload.php).
Se
le
classi
da
auto-caricare
usano
spazi
dei
nomi,
usare
:method:Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerNamespace
:method:Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerNamespaces:

metodi
o

$loader->registerNamespace(Symfony, __DIR__./vendor/symfony/src);
$loader->registerNamespaces(array(
Symfony => __DIR__./../vendor/symfony/src,
Monolog => __DIR__./../vendor/monolog/src,
));
$loader->register();

Per
classi
che
seguono
la
convenzione
dei
nomi
di
PEAR,
usare
:method:Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerPrefix
:method:Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerPrefixes:

metodi
o

$loader->registerPrefix(Twig_, __DIR__./vendor/twig/lib);
$loader->registerPrefixes(array(
Swift_ => __DIR__./vendor/swiftmailer/lib/classes,
Twig_ => __DIR__./vendor/twig/lib,
));
$loader->register();

Note: Alcune librerie richiedono anche che il loro percorso radice sia registrato nellinclude_path di PHP
(set_include_path()).
Le classi di un sotto-spazio dei nomi o di una sotto-gerarchia di PEAR possono essere cercate in un elenco di
posizioni, per facilitare i venditori di un sotto-insieme di classi per grossi progetti:
$loader->registerNamespaces(array(
Doctrine\\Common
=>
Doctrine\\DBAL\\Migrations =>
Doctrine\\DBAL
=>
Doctrine
=>
));

__DIR__./vendor/doctrine-common/lib,
__DIR__./vendor/doctrine-migrations/lib,
__DIR__./vendor/doctrine-dbal/lib,
__DIR__./vendor/doctrine/lib,

$loader->register();

In questo esempio, se si prova a usare una classe nello spazio dei nomi Doctrine\Common o uno dei suoi
figli, lautoloader cercher prima le classi sotto la cartella doctrine-common, quindi, se non le trova, cercher
nella cartella Doctrine (lultima configurata), infine si arrender. In questo caso, lordine di registrazione
significativo.

436

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

4.1.2 Il componente Console


Il componente Console semplifica la creazione di eleganti e testabili comandi da terminale.
Symfony2 viene distribuito con un componente Console che permette di creare comandi da terminale. I comandi
da terminale possono essere utilizzati per qualsiasi lavoro ripetivo come i lavori di cron, le importazioni o lavori
batch.
Installazione
Il componente pu essere installato in diversi modi:
Utilizzando il repository Git ufficiale (https://github.com/symfony/Console);
Installandolo via PEAR ( pear.symfony.com/Console);
Installandolo via Composer (symfony/console in Packagist).
Creazione di comandi di base
Per avere automaticamente a disposizione, sotto Symfony2, un comando a terminale, si crea una cartella Command
allinterno del proprio bundle dentro la quale si inserir un file, con il suffisso Command.php, per ogni comando
che si voglia realizzare. Ad esempio, per estendere lAcmeDemoBundle (disponibile in Symfony Standard
Edition) con un programma che porga il saluto dal terminale, si dovr creare il file SalutaCommand.php
contenente il seguente codice:
// src/Acme/DemoBundle/Command/GreetCommand.php
namespace Acme\DemoBundle\Command;
use
use
use
use
use

Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
Symfony\Component\Console\Input\InputArgument;
Symfony\Component\Console\Input\InputInterface;
Symfony\Component\Console\Input\InputOption;
Symfony\Component\Console\Output\OutputInterface;

class SalutaCommand extends ContainerAwareCommand


{
protected function configure()
{
$this
->setName(demo:saluta)
->setDescription(Saluta qualcuno)
->addArgument(nome, InputArgument::OPTIONAL, Chi vuoi salutare?)
->addOption(urla, null, InputOption::VALUE_NONE, Se impostato, il saluto verr urla
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$nome = $input->getArgument(nome);
if ($nome) {
$testo = Ciao .$nome;
} else {
$testo = Ciao;
}
if ($input->getOption(urla)) {
$testo = strtoupper($testo);
}
$output->writeln($testo);

4.1. I componenti

437

Symfony2 documentation Documentation, Release 2

}
}

You also need to create the file to run at the command line which creates an Application and adds commands
to it:
possibile provare il programma nel modo seguente
app/console demo:saluta Fabien

Il comando scriver, nel terminale, quello che segue:


Ciao Fabien

anche possibile usare lopzione --urla per stampare il saluto in lettere maiuscole:
app/console demo:saluta Fabien --urla

Il cui risultato sar:


CIAO FABIEN

Colorare loutput

possibile inserire il testo da stampare, allinterno di speciali tag per colorare loutput. Ad esempio:
// testo verde
$output->writeln(<info>pippo</info>);
// testo giallo
$output->writeln(<comment>pippo</comment>);
// testo nero su sfondo ciano
$output->writeln(<question>pippo</question>);
// testo nero su sfondo rosso
$output->writeln(<error>pippo</error>);

Utilizzo degli argomenti nei comandi


La parte pi interessante dei comandi data dalla possibilit di mettere a disposizione parametri e argomenti.
Gli argomenti sono delle stringhe, separate da spazi, che seguono il nome stesso del comando. Devono essere
inseriti in un ordine preciso e possono essere opzionali o obbligatori. Ad esempio, per aggiungere un argomento
opzionale cognome al precedente comando e rendere largomento nome obbligatorio, si dovr scrivere:
$this
// ...
->addArgument(nome, InputArgument::REQUIRED, Chi vuoi salutare?)
->addArgument(cognome, InputArgument::OPTIONAL, Il tuo cognome?)
// ...

A questo punto si pu accedere allargomento cognome dal proprio codice:


if ($cognome = $input->getArgument(cognome)) {
$testo .= .$cognome;
}

Il comando potr essere utilizzato in uno qualsiasi dei seguenti modi:


app/console demo:saluta Fabien
app/console demo:saluta Fabien Potencier

438

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

Utilizzo delle opzioni nei comandi


Diversamente dagli argomenti, le opzioni non sono ordinate (cio possono essere specificate in qualsiasi ordine)
e sono identificate dal doppio trattino (come in urla; anche possibile dichiarare una scorciatoia a singola lettera
preceduta da un solo trattino come in -u). Le opzioni sono sempre opzionali e possono accettare valori (come in
dir=src) o essere semplici indicatori booleani senza alcuna assegnazione (come in urla).
Tip: anche possibile fare in modo che unopzione possa opzionalmente accettare un valore (ad esempio si
potrebbe avere --urla o --urla=forte). Le opzioni possono anche essere configurate per accettare array di
valori.
Ad esempio, per specificare il numero di volte in cui il messaggio di saluto sar stampato, si pu aggiungere la
seguente opzione:

$this
// ...
->addOption(ripetizioni, null, InputOption::VALUE_REQUIRED, Quante volte dovr essere stamp

Ora possibile usare lopzione per stampare pi volte il messaggio:


for ($i = 0; $i < $input->getOption(ripetizioni); $i++) {
$output->writeln($testo);
}

In questo modo, quando si esegue il comando, sar possibile specificare, opzionalmente, limpostazione
--ripetizioni:
app/console demo:saluta Fabien
app/console demo:saluta Fabien --ripetizioni=5

Nel primo esempio, il saluto verr stampata una sola volta, visto che ripetizioni vuoto e il suo valore
predefinito 1 (lultimo argomento di addOption). Nel secondo esempio, il saluto verr stampato 5 volte.
Ricordiamo che le opzioni non devono essere specificate in un ordina predefinito. Perci, entrambi i seguenti
esempi funzioneranno correttamente:
app/console demo:saluta Fabien --ripetizioni=5 --urla
app/console demo:saluta Fabien --urla --ripetizioni=5

Ci sono 4 possibili varianti per le opzioni:


Opzione
InputOption::VALUE_IS_ARRAY
InputOption::VALUE_NONE
InputOption::VALUE_REQUIRED
InputOption::VALUE_OPTIONAL

Valore
Questa opzione accetta valori multipli
Non accettare alcun valore per questa opzione (come in --urla)
Il valore obbligatorio (come in ripetizioni=5)
Il valore opzionale

possibile combinare VALUE_IS_ARRAY con VALUE_REQUIRED o con VALUE_OPTIONAL nel seguente


modo:

$this
// ...
->addOption(ripetizioni, null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, Q

Richiedere informazioni allutente


Nel creare comandi possibile richiedere ulteriori informazioni dagli utenti rivolgendo loro domande. Ad esempio, si potrbbe richiedere la conferma prima di effettuare realmente una determinata azione. In questo caso si
dovr aggiungere il seguente codice al comando:

4.1. I componenti

439

Symfony2 documentation Documentation, Release 2

$dialogo = $this->getHelperSet()->get(dialog);
if (!$dialogo->askConfirmation($output, <question>Vuoi proseguire con questa azione?</question>,
return;
}

In questo modo, allutente verr chiesto se vuole proseguire con questa azione e, a meno che la risposta non sia
y, lazione non verr eseguita. Il terzo argomento di askConfirmation il valore predefinito da restituire nel
caso in cui lutente non fornisca alcun input.
possibile rivolgere domande che prevedano risposte pi complesse di un semplice si/no. Ad esempio, se volessimo conoscere il nome di qualcosa, potremmo fare nel seguente modo:
$dialogo = $this->getHelperSet()->get(dialog);
$nome = $dialogo->ask($output, Insersci il nome del widget, pippo);

Testare i comandi
Symfony2 mette a disposizione diversi strumenti a supporto del test dei comandi. Il pi utile di questi la
classe Symfony\Component\Console\Tester\CommandTester. Questa utilizza particolari classi per
la gestione dellinput/output che semplificano lo svolgimento di test senza una reale interazione da terminale:
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Acme\DemoBundle\Command\SalutaCommand;
class ListCommandTest extends \PHPUnit_Framework_TestCase
{
public function testExecute()
{
$application = new Application($kernel);
$application->add(new SalutaCommand());
$comando = $application->find(demo:saluta);
$testDelComando = new CommandTester($comando);
$testDelComando->execute(array(command => $comando->getFullName()));
$this->assertRegExp(/.../, $testDelComando->getDisplay());
// ...
}
}

Il metodo :method:Symfony\\Component\\Console\\Tester\\CommandTester::getDisplay restituisce ci che


sarebbe stato mostrato durante una normale chiamata dal terminale.
Si pu testare linvio di argomenti e opzioni al comando, passandoli come array al metodo
:method:Symfony\\Component\\Console\\Tester\\CommandTester::getDisplay:
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Acme\DemoBundle\Command\GreetCommand;
class ListCommandTest extends \PHPUnit_Framework_TestCase
{
//-public function testNameIsOutput()
{
$application = new Application();
$application->add(new GreetCommand());

440

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

$command = $application->find(demo:saluta);
$commandTester = new CommandTester($command);
$commandTester->execute(
array(command => $command->getName(), name => Fabien)
);
$this->assertRegExp(/Fabien/, $commandTester->getDisplay());
}
}

Tip:

possibile
testare
unintera
applicazione
Symfony\Component\Console\Tester\ApplicationTester.

da

terminale

utilizzando

Richiamare un comando esistente


Se un comando dipende da un altro, da eseguire prima, invece di chiedere allutente di ricordare lordine di
esecuzione, lo si pu richiamare direttamente. Questo utile anche quando si vuole creare un meta comando,
che esegue solo una serie di altri comandi (per esempio, tutti i comandi necessari quando il codice del progetto
cambiato sui server di produzione: pulire la cache, genereare i proxy di Doctrine, esportare le risorse di Assetic,
...).
Richiamare un comando da un altro molto semplice:
protected function execute(InputInterface $input, OutputInterface $output)
{
$comando = $this->getApplication()->find(demo:saluta);
$argomenti = array(
command => demo:saluta,
nome
=> Fabien,
--urla => true,
);
$input = new ArrayInput($argomenti);
$codiceDiRitorno = $comando->run($input, $output);
// ...
}

Innanzitutto si dovr trovare (:method:Symfony\\Component\\Console\\Command\\Command::find) il comando da eseguire usandone il nome come parametro.
Quindi si dovr creare un nuovo Symfony\Component\Console\Input\ArrayInput che contenga gli
argomenti e le opzioni da passare al comando.
Infine, la chiamata al metodo run() mander effettivamente in esecuzione il comando e restituir il codice di
ritorno del comando (0 se tutto andato a buon fine, un qualsiasi altro intero negli altri altri casi).
Note: Nella maggior parte dei casi, non una buona idea quella di eseguire un comando al di fuori del terminale.
Innanzitutto perch loutput del comando ottimizzato per il terminale. Ma, anche pi importante, un comando
come un controllore: dovrebbe usare un modello per fare qualsiasi cosa e restituire informazioni allutente.
Perci, invece di eseguire un comando dal Web, sarebbe meglio provare a rifattorizzare il codice e spostare la
logica allinterno di una nuova classe.

4.1.3 Il componente CssSelector


Il componente CssSelector converte selettori CSS in espressioni XPath.

4.1. I componenti

441

Symfony2 documentation Documentation, Release 2

Installazione
Si pu installare il componente in molti modi diversi:
Usare il repository ufficiale su Git (https://github.com/symfony/CssSelector);
Installarlo via PEAR ( pear.symfony.com/CssSelector);
Installarlo via Composer (symfony/css-selector su Packagist).
Uso
Perch usare selettori CSS?

Quando si analizzano documenti HTML o XML, XPath certamente il metodo pi potente.


Le espressioni XPath sono incredibilmente flessibili, quindi c quasi sempre unespressione XPath che trover
lelemento richiesto. Sfortunatamente, possono essere anche molto complicate e la curva di apprendimento
ripida. Anche operazioni comuni (come trovare un elemento con una data classe) possono richiedere espressioni
lunghe e poco maneggevoli.
Molti sviluppatori, in particolare gli sviluppatori web, si trovano pi a loro agio nel cercare elementi tramite
selettori CSS. Oltre a funzionare con i fogli di stile, i selettori CSS sono usati da Javascript con la funzione
querySelectorAll e da famose librerie Javascript, come jQuery, Prototype e MooTools.
I selettori CSS sono meno potenti di XPath, ma molto pi facili da scrivere, leggere e capire. Essendo meno potenti, quasi tutti i selettori CSS possono essere convertiti in equivalenti XPath. Queste espressioni XPath possono
quindi essere usate con altre funzioni e classi che usano XPath per trovare elementi in un documento.
Il componente CssSelector

Lunico fine del componente la conversione di selettori CSS nei loro equivalenti XPath:
use Symfony\Component\CssSelector\CssSelector;
print CssSelector::toXPath(div.item > h4 > a);

Questo fornisce il seguente output:


descendant-or-self::div[contains(concat( ,normalize-space(@class), ), item )]/h4/a

Si pu usare questa espressione, per esempio, con :phpclass:DOMXPath o :phpclass:SimpleXMLElement,


per trovare elementi in un documento.
Tip: Il metodo :method:Crawler::filter()<Symfony\\Component\\DomCrawler\\Crawler::filter> usa il
componente CssSelector per trovare elementi in base a un selettore CSS. Si veda Il componente DomCrawler
per ulteriori dettagli.

Limiti del componente CssSelector

Non tutti i selettori CSS possono essere convertiti in equivalenti XPath.


Ci sono molti selettori CSS che hanno senso solo nel contesto di un browser.
selettori dei collegamenti: :link, :visited, :target
selettori basati su azioni dellutente: :hover, :focus, :active
selettori dello stato dellinterfaccia: :enabled, :disabled, :indeterminate (tuttavia, :checked
e :unchecked sono disponibili)

442

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

Gli pseudo-elementi (:before, :after, :first-line, :first-letter) non sono supportati, perch
selezionano porzioni di testo, piuttosto che elementi.
Diverse pseudo-classi non sono ancora supportate:
:lang(language)
root
*:first-of-type,
*:last-of-type,
*:nth-of-type,
*:only-of-type. (funzionano con il nome di un elemento (p.e.
non con *.

*:nth-last-of-type,
li:first-of-type) ma

4.1.4 Il componente DomCrawler


Il componente DomCrawler semplifica la navigazione nel DOM dei documenti HTML e XML.
Installazione
possibile installare il componente in diversi modi:
Utilizzando il repository ufficiale su Git (https://github.com/symfony/DomCrawler);
Installandolo via PEAR ( pear.symfony.com/DomCrawler);
Installandolo via Composer (symfony/dom-crawler su Packagist).
Utilizzo
La classe Symfony\Component\DomCrawler\Crawler mette a disposizione metodi per effettuare query
e manipolare i documenti HTML e XML.
Unistanza di Crawler rappresenta un insieme (:phpclass:SplObjectStorage)
class:DOMElement, che sono, in pratica, nodi facilmente visitabili:

di

oggetti

:php-

use Symfony\Component\DomCrawler\Crawler;
$html = <<<HTML
<html>
<body>
<p class="messaggio">Ciao Mondo!</p>
<p>Ciao Crawler!</p>
</body>
</html>
HTML;
$crawler = new Crawler($html);
foreach ($crawler as $elementoDom) {
print $elementoDom->nodeName;
}

Le classi specializzate Symfony\Component\DomCrawler\Link e Symfony\Component\DomCrawler\Form


sono utili per interagire con collegamenti html e i form durante la visita dellalbero HTML.
Filtrare i nodi

possibile usare facilmente le espressioni di XPath:


$crawler = $crawler->filterXPath(descendant-or-self::body/p);

4.1. I componenti

443

Symfony2 documentation Documentation, Release 2

Tip: internamente viene usato DOMXPath::query per eseguire le query XPath.


La ricerca anche pi semplice se si installato il componente CssSelector. In questo modo possibile usare
lo stile jQuery per lattraversamento:
$crawler = $crawler->filter(body > p);

possibile usare funzioni anonime per eseguire filtri complessi:


$crawler = $crawler->filter(body > p)->reduce(function ($node, $i) {
// filtra anche i nodi
return ($i % 2) == 0;
});

Per rimuovere i nodi, la funzione anonima dovr restituire false.


Note:
Tutti
i
metodi
dei
filtri
restituiscono
una
Symfony\Component\DomCrawler\Crawler contenente gli elementi filtrati.

nuova

istanza

di

istanza

di

Attraversamento dei nodi

Accedere ai nodi tramite la loro posizione nella lista:


$crawler->filter(body > p)->eq(0);

Ottenere il primo o lultimo nodo della selezione:


$crawler->filter(body > p)->first();
$crawler->filter(body > p)->last();

Ottenere i nodi allo stesso livello della selezione attuale:


$crawler->filter(body > p)->siblings();

Ottenere i nodi, allo stesso livello, precedenti o successivi alla selezione attuale:
$crawler->filter(body > p)->nextAll();
$crawler->filter(body > p)->previousAll();

Ottenere tutti i nodi figlio o padre:


$crawler->filter(body)->children();
$crawler->filter(body > p)->parents();

Note:
Tutti i metodi di attraversamento
Symfony\Component\DomCrawler\Crawler.

restituiscono

un

nuova

Accedere ai nodi tramite il loro valore

Accedere al valore del primo nodo della selezione attuale:


$message = $crawler->filterXPath(//body/p)->text();

Accedere al valore dellattributo del primo nodo della selezione attuale:


$class = $crawler->filterXPath(//body/p)->attr(class);

Estrarre lattributo e/o il valore di un nodo da una lista di nodi:


444

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

$attributes = $crawler->filterXpath(//body/p)->extract(array(_text, class));

Note: Lattributo speciale _text rappresenta il valore di un nodo.


Chiamare una funzione anonima su ogni nodo della lista:
$nodeValues = $crawler->filter(p)->each(function ($nodo, $i) {
return $nodo->nodeValue;
});

La funzione anonima riceve la posizione e il nodo come argomenti. Il risultato un array contenente i valori
restituiti dalle chiamate alla funzione anonima.
Aggiungere contenuti

Il crawler supporta diversi modi per aggiungere contenuti:


$crawler = new Crawler(<html><body /></html>);
$crawler->addHtmlContent(<html><body /></html>);
$crawler->addXmlContent(<root><node /></root>);
$crawler->addContent(<html><body /></html>);
$crawler->addContent(<root><node /></root>, text/xml);
$crawler->add(<html><body /></html>);
$crawler->add(<root><node /></root>);

Essendo limplementazione del Crawler basata sullestensione di DOM, anche possibile interagire con le classi
native :phpclass:DOMDocument, :phpclass:DOMNodeList e :phpclass:DOMNode:
$documento = new \DOMDocument();
$documento->loadXml(<root><node /><node /></root>);
$listaNodi = $documento->getElementsByTagName(node);
$nodo = $documento->getElementsByTagName(node)->item(0);
$crawler->addDocument($documento);
$crawler->addNodeList($listaNodi);
$crawler->addNodes(array($nodo));
$crawler->addNode($nodo);
$crawler->add($documento);

Supporto per i collegamenti e per i form

Per i collegamenti e i form, contenuti nellalbero DOM, riservato un trattamento speciale.


Collegamenti Per trovare un collegamento tramite il suo nome (o unimmagine cliccabile tramite il suo attributo alt) si usa il metodo selectLink in un crawler esistente. La chiamata restituisce unistanza di
Crawler contenente il/i solo/i collegamento/i selezionato/i. La chiamata link() restituisce loggetto speciale
Symfony\Component\DomCrawler\Link:
$linksCrawler = $crawler->selectLink(Vai altrove...);
$link = $linksCrawler->link();
// oppure, in una sola riga
$link = $crawler->selectLink(Vai altrove...)->link();

4.1. I componenti

445

Symfony2 documentation Documentation, Release 2

Loggetto Symfony\Component\DomCrawler\Link ha diversi utili metodi per avere ulteriori informazioni


relative al collegamento selezionato:
// restituisce il valore di href
$href = $link->getRawUri();
// restituisce la URI che pu essere utilizzata per effettuare nuove richieste
$uri = $link->getUri();

Il metodo getUri() specialmente utile perch pulisce il valore di href e lo trasforma nel modo in cui
dovrebbe realmente essere processato. Ad esempio, un collegamento del tipo href="#foo" restituir la URI
completa della pagina corrente con il suffisso #foo. Il valore restituito da getUri() sempre una URI completa
sulla quale possibile eseguire lavori.
I Form Un trattamento speciale riservato anche ai form. disponibile, in Crawler, un metodo
selectButton() che restituisce unaltro Crawler relativo al pulsante (input[type=submit],
input[type=image], o button) con il testo dato. Questo metodo specialmente utile perch pu essere usato per restituire un oggetto Symfony\Component\DomCrawler\Form che rappresenta il form allinterno
del quale il pulsante definito:
$form = $crawler->selectButton(Valida)->form();
// o "riempie" i campi del form con dati
$form = $crawler->selectButton(Valida)->form(array(
nome => Ryan,
));

Loggetto Symfony\Component\DomCrawler\Form ha molti utilissimi metodi che permettono di lavorare


con i form:
$uri = $form->getUri();
$metodo = $form->getMethod();
Il metodo :method:Symfony\\Component\\DomCrawler\\Form::getUri fa pi che restituire il mero attributo action del form. Se il metodo del form GET, allora, imitando il comportamento del borwser, restituir
lattributo dellazione seguito da una stringa di tutti i valori del form.
possibile impostare e leggere virtualmente i valori nel form:
// imposta, internamente, i valori del form
$form->setValues(array(
registrazione[nomeutente] => fandisymfony,
registrazione[termini]
=> 1,
));
// restituisce un array di valori in un array "semplice", come in precedenza
$values = $form->getValues();
// restituisce i valori come li vedrebbe PHP con "registrazione" come array
$values = $form->getPhpValues();

Per lavorare con i campi multi-dimensionali:


<form>
<input name="multi[]" />
<input name="multi[]" />
<input name="multi[dimensionale]" />
</form>

necessario specificare il nome pienamente qualificato del campo:


// Imposta un singolo campo
$form->setValue(multi[0], valore);

446

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

// Imposta molteplici campi in una sola volta


$form->setValue(multi, array(
1
=> valore,
dimensionale => un altro valore
));

Se questo fantastico, il resto anche meglio! Loggetto Form permette di interagire con il form come se si
usasse il borwser, selezionando i valori dei radio, spuntando i checkbox e caricando file:
$form[registrazione[nomeutente]]->setValue(fandisymfony);
// cambia segno di spunta ad un checkbox
$form[registrazione[termini]]->tick();
$form[registrazione[termini]]->untick();
// seleziona unopzione
$form[registrazione[data_nascita][anno]]->select(1984);
// seleziona diverse opzioni da una lista di opzioni o da una serie di checkbox
$form[registrazione[interessi]]->select(array(symfony, biscotti));
// pu anche imitare lupload di un file
$form[registrazione[foto]]->upload(/percorso/al/file/lucas.jpg);

A cosa serve tutto questo? Se si stanno eseguendo i test interni, possibile recuperare informazioni da tutti i form
esattamente come se fossero stati inviati utilizzando i valori PHP:
$valori = $form->getPhpValues();
$files = $form->getPhpFiles();

Se si utilizza un client HTTP esterno, possibile usare il form per recuperare tutte le informazioni necessarie per
create una richiesta POST dal form:
$uri = $form->getUri();
$metodo = $form->getMethod();
$valori = $form->getValues();
$files = $form->getFiles();
// a questo punto si usa un qualche client HTTP e si inviano le informazioni

Un ottimo esempio di sistema integrato che utilizza tutte queste funzioni Goutte. Goutte usa a pieno gli oggetti
Symfony Crawler e, con essi, pu inviare i form direttamente:
use Goutte\Client;
// crea una richiesta ad un sito esterno
$client = new Client();
$crawler = $client->request(GET, https://github.com/login);
// seleziona il form e riempie alcuni valori
$form = $crawler->selectButton(Log in)->form();
$form[login] = fandisymfony;
$form[password] = unapassword;
// invia il form
$crawler = $client->submit($form);

4.1.5 Il componente Finder


Il componente Finder cerca file e cartelle tramite uninterfaccia intuitiva e fluida.

4.1. I componenti

447

Symfony2 documentation Documentation, Release 2

Installazione
possibile installare il componente in diversi modi:
Utilizzando il repository ufficiale su Git (https://github.com/symfony/Finder);
Installandolo via PEAR ( pear.symfony.com/Finder);
Installandolo tramite Composer (symfony/finder su Packagist).
Utilizzo
La classe Symfony\Component\Finder\Finder trova i file e/o le cartelle:
use Symfony\Component\Finder\Finder;
$finder = new Finder();
$finder->files()->in(__DIR__);
foreach ($finder as $file) {
// Stampa il percorso assoluto
print $file->getRealpath()."\n";
// Stampa il percorso relativo del file, omettendo il nome del file stesso
print $file->getRelativePath()."\n";
// Stampa il percorso relativo del file
print $file->getRelativePathname()."\n";
}

$file unistanza di Symfony\Component\Finder\SplFileInfo la quale estende :phpclass:SplFileInfo che mette a disposizione i metodi per poter lavorare con i percorsi relativi.
Il precedente codice stampa, ricorsivamente, i nomi di tutti i file della cartella corrente. La classe Finder implementa il concetto di interfaccia fluida, perci tutti i metodi restituiscono unistanza di Finder.
Tip: Un Finder unistanza di un Iterator PHP. Perci, invece di dover iterare attraverso Finder con un ciclo
foreach, possibile convertirlo in un array, tramite il metodo :phpfunction:iterator_to_array, o ottenere il
numero di oggetto in esso contenuti con :phpfunction:iterator_count.

Criteri
Posizione

La posizione lunico parametro obbligatorio. Indica al finder la cartella da utilizzare come base per la ricerca:
$finder->in(__DIR__);

Per
cercare
in
diverse
posizioni,

possibile
:method:Symfony\\Component\\Finder\\Finder::in:

concatenare

diverse

chiamate

$finder->files()->in(__DIR__)->in(/altraparte);

possibile escludere cartelle dalla ricerca tramite il metodo :method:Symfony\\Component\\Finder\\Finder::exclude:


$finder->in(__DIR__)->exclude(ruby);

Visto che Finder utilizza gli iteratori di PHP, possibile passargli qualsiasi URL che sia supportata dal protocollo:
$finder->in(ftp://example.com/pub/);

Funziona anche con flussi definiti dallutente:

448

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

use Symfony\Component\Finder\Finder;
$s3 = new \Zend_Service_Amazon_S3($chiave, $segreto);
$s3->registerStreamWrapper("s3");
$finder = new Finder();
$finder->name(photos*)->size(< 100K)->date(since 1 hour ago);
foreach ($finder->in(s3://bucket-name) as $file) {
// fare qualcosa
print $file->getFilename()."\n";
}

Note: Per approfondire largomento su come creare flussi personalizzati, si legga la documentazione degli stream.

File o cartelle

Il
comportamento
predefinito
di
Finder

quello
di
restituire
file
e
cartelle,
ma
grazie
ai
metodi
:method:Symfony\\Component\\Finder\\Finder::files
e
:method:Symfony\\Component\\Finder\\Finder::directories, possibile raffinare i risultati:
$finder->files();
$finder->directories();

Per seguire i collegamenti, possibile utilizzare il metodo followLinks():


$finder->files()->followLinks();

Normalmente literatore ignorer i file dei VCS pi diffusi. possibile modificare questo comportamento, grazie
al metodo ignoreVCS():
$finder->ignoreVCS(false);

Ordinamento

possibile ordinare i risultati per nome o per tipo (prima le cartelle e poi i file):
$finder->sortByName();
$finder->sortByType();

Note: Si noti che i metodi sort*, per poter funzionare, richiedono tutti gli elementi ricercati. In caso di iteratori
molto grandi, lordinamento potrebbe risultare lento.
anche possibile definire algoritmi di ordinamento personalizzati, grazie al metodo sort():
$sort = function (\SplFileInfo $a, \SplFileInfo $b)
{
return strcmp($a->getRealpath(), $b->getRealpath());
};
$finder->sort($sort);

4.1. I componenti

449

Symfony2 documentation Documentation, Release 2

Nomi dei file

possibile
eseguire
filtri
sui
nomi
:method:Symfony\\Component\\Finder\\Finder::name:

dei

file,

utilizzando

il

metodo

$finder->files()->name(*.php);

Il metodo name() accetta, come parametri, glob, stringhe o espressioni regolari:


$finder->files()->name(/\.php$/);

Il metodo notNames() viene invece usato per escludere i file che corrispondono allo schema:
$finder->files()->notName(*.rb);

Dimensione dei file

Per filtrare i file in base alla dimensione, si usa il metodo :method:Symfony\\Component\\Finder\\Finder::size:


$finder->files()->size(< 1.5K);

Si possono filtrare i file di dimensione compresa tra due valori, concatenando le chiamate:
$finder->files()->size(>= 1K)->size(<= 2K);

possibile utilizzare uno qualsiasi dei seguenti operatori di confronto: >, >=, <, <=, ==.
La dimensione pu essere indicata usando lindicazione in kilobyte (k, ki), megabyte (m, mi) o in gigabyte (g,
gi). Gli indicatori che terminano con i utilizzano lappropriata versione 2**n, in accordo allo standard IEC
Data dei file

possibile filtrare i file in base alla data


:method:Symfony\\Component\\Finder\\Finder::date:

dellultima

modifica,

con

il

metodo

$finder->date(since yesterday);

possibile utilizzare uno qualsiasi dei seguenti operatori di confronto: >, >=, <, <=, ==. anche possibile
usare i sostantivi since o after come degli alias di >, e until o before come alias di <.
Il valore usato pu essere una data qualsiasi tra quelle supportate dalla funzione strtotime.
Profondit della ricerca

Normalmente, Finder attraversa ricorsivamente tutte le cartelle. Per restringere la profondit dellattraversamento,
si usa il metodo :method:Symfony\\Component\\Finder\\Finder::depth:
$finder->depth(== 0);
$finder->depth(< 3);

Filtri personalizzati

possibile definire filtri personalizzati, grazie al metodo :method:Symfony\\Component\\Finder\\Finder::filter:


$filtro_personalizzato = function (\SplFileInfo $file)
{
if (strlen($file) > 10) {
return false;

450

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

}
};
$finder->files()->filter($filtro_personalizzato);

Il metodo filter() prende una Closure come argomento. Per ogni file che corrisponde ai criteri, la Closure
viene chiamata passandogli il file come unistanza di Symfony\Component\Finder\SplFileInfo. Il file
sar escluso dal risultato della ricerca nel caso in cui la Closure restituisca false.

4.1.6 Il componente HttpFoundation


Il componente HttpFoundation definisce un livello orientato agli oggetti per le specifiche HTTP.
In PHP, la richiesta rappresentata da alcune variabili globali ($_GET, $_POST, $_FILE, $_COOKIE,
$_SESSION...) e la risposta generata da alcune funzioni (echo, header, setcookie, ...).
Il componente HttpFoundation di Symfony2 sostituisce queste variabili globali e queste funzioni di PHP con un
livello orientato agli oggetti.
Installazione
Si pu installare il componente in molti modi diversi:
Usare il repository ufficiale su Git (https://github.com/symfony/HttpFoundation);
Installarlo via PEAR ( pear.symfony.com/HttpFoundation);
Installarlo via Composer (symfony/http-foundation su Packagist).
Richiesta
Il modo pi comune per creare una richiesta basarla sulle variabili attuali di PHP, con
:method:Symfony\\Component\\HttpFoundation\\Request::createFromGlobals:
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();

che quasi equivalente al pi verboso, ma anche pi flessibile, :method:Symfony\\Component\\HttpFoundation\\Request::__con


$request = new Request($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);

Accedere ai dati della richiesta

Un oggetto richiesta contiene informazioni sulla richiesta del client. Si pu accedere a queste informazioni tramite
varie propriet pubbliche:
request: equivalente di $_POST;
query: equivalente di $_GET ($request->query->get(name));
cookies: equivalente di $_COOKIE;
attributes: non ha equivalenti, usato dallapplicazione per memorizzare alrti dati (vedere sotto)
files: equivalente di $_FILE;
server: equivalente di $_SERVER;
headers:
quasi
equivalente
di
un
($request->headers->get(Content-Type)).

4.1. I componenti

sottinsieme

di

$_SERVER

451

Symfony2 documentation Documentation, Release 2

Ogni propriet unistanza di Symfony\Component\HttpFoundation\ParameterBag (o di una sua


sotto-classe), che una classe contenitore:
request: Symfony\Component\HttpFoundation\ParameterBag;
query: Symfony\Component\HttpFoundation\ParameterBag;
cookies: Symfony\Component\HttpFoundation\ParameterBag;
attributes: Symfony\Component\HttpFoundation\ParameterBag;
files: Symfony\Component\HttpFoundation\FileBag;
server: Symfony\Component\HttpFoundation\ServerBag;
headers: Symfony\Component\HttpFoundation\HeaderBag.
Tutte le istanze di Symfony\Component\HttpFoundation\ParameterBag hanno metodi per recuperare e aggiornare i propri dati:
:method:Symfony\\Component\\HttpFoundation\\ParameterBag::all: Restituisce i parametri;
:method:Symfony\\Component\\HttpFoundation\\ParameterBag::keys:
parametri;

Restituisce le chiavi dei

:method:Symfony\\Component\\HttpFoundation\\ParameterBag::replace: Sostituisce i parametri attuali con dei nuovi;


:method:Symfony\\Component\\HttpFoundation\\ParameterBag::add: Aggiunge parametri;
:method:Symfony\\Component\\HttpFoundation\\ParameterBag::get: Restituisce un parametro per
nome;
:method:Symfony\\Component\\HttpFoundation\\ParameterBag::set:
nome;

Imposta un parametro per

:method:Symfony\\Component\\HttpFoundation\\ParameterBag::has:
parametro definito;

Restituisce true se il

:method:Symfony\\Component\\HttpFoundation\\ParameterBag::remove: Rimuove un parametro.


La classe Symfony\Component\HttpFoundation\ParameterBag ha anche alcuni metodi per filtrare i
valori in entrata:
:method:Symfony\\Component\\HttpFoundation\\Request::getAlpha: Restituisce i caratteri alfabetici
nel valore del parametro;
:method:Symfony\\Component\\HttpFoundation\\Request::getAlnum: Restituisce i caratteri alfabetici e i numeri nel valore del parametro;
:method:Symfony\\Component\\HttpFoundation\\Request::getDigits: Restituisce i numeri nel valore
del parametro;
:method:Symfony\\Component\\HttpFoundation\\Request::getInt: Restituisce il valore del parametro
convertito in intero;
:method:Symfony\\Component\\HttpFoundation\\Request::filter: Filtra il parametro, usando la funzione PHP filter_var().
Tutti i getter accettano tre parametri: il primo il nome del parametro e il secondo il valore predefinito, da
restituire se il parametro non esiste:
// la query string ?foo=bar
$request->query->get(foo);
// restituisce bar
$request->query->get(bar);
// restituisce null

452

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

$request->query->get(bar, bar);
// restituisce bar

Quando PHP importa la query della richiesta, gestisce i parametri della richiesta, come foo[bar]=bar, in modo
speciale, creando un array. In questo modo, si pu richiedere il parametro foo e ottenere un array con un elemento
bar. A volte, per, si potrebbe volere il valore del nome originale del parametro: foo[bar]. Ci possibile con tutti i getter di ParameterBag, come :method:Symfony\\Component\\HttpFoundation\\Request::get,
tramite il terzo parametro:
// la query string ?foo[bar]=bar
$request->query->get(foo);
// restituisce array(bar => bar)
$request->query->get(foo[bar]);
// restituisce null
$request->query->get(foo[bar], null, true);
// restituisce bar

Infine, ma non meno importante, si possono anche memorizzare dati aggiuntivi nella
richiesta,
grazie alla propriet pubblica attributes,
che anche unistanza di
Symfony\Component\HttpFoundation\ParameterBag. La si usa soprattutto per allegare informazioni che appartengono alla richiesta e a cui si deve accedere in diversi punti della propria applicazione. Per
informazioni su come viene usata nel framework Symfony2, vedere saperne di pi.
Identificare una richiesta

Nella propria applicazione, serve un modo per identificare una richiesta.


La maggior parte delle
volte, lo si fa tramite il path info della richiesta, a cui si pu accedere tramite il metodo
:method:Symfony\\Component\\HttpFoundation\\Request::getPathInfo:
// per una richiesta a http://example.com/blog/index.php/post/hello-world
// path info "/post/hello-world"
$request->getPathInfo();

Simulare una richiesta

Invece di creare una richiesta basata sulle variabili di PHP, si pu anche simulare una richiesta:
$request = Request::create(/hello-world, GET, array(name => Fabien));

Il metodo :method:Symfony\\Component\\HttpFoundation\\Request::create crea una richiesta in base a path


info, un metodo e alcuni parametri (i parametri della query o quelli della richiesta, a seconda del metodo HTTP) e,
ovviamente, si possono forzare anche tutte le altre variabili (Symfony crea dei valori predefiniti adeguati per ogni
variabile globale di PHP).
In base a tale richiesta,
si possono forzare le variabili globali
:method:Symfony\\Component\\HttpFoundation\\Request::overrideGlobals:

di

PHP

tramite

$request->overrideGlobals();

Tip: Si pu anche duplicare una query esistente, tramite :method:Symfony\\Component\\HttpFoundation\\Request::duplicate,


o cambiare molti parametri con una singola chiamata a :method:Symfony\\Component\\HttpFoundation\\Request::initialize.

4.1. I componenti

453

Symfony2 documentation Documentation, Release 2

Accedere alla sessione

Se si ha una sessione allegata alla richiesta, vi si pu accedere tramite il metodo


:method:Symfony\\Component\\HttpFoundation\\Request::getSession.
Il
metodo
:method:Symfony\\Component\\HttpFoundation\\Request::hasPreviousSession dice se la richiesta
contiene una sessione, che sia stata fatta partire in una delle richieste precedenti.
Accedere ad altri dati

La classe Request ha molti altri metodi, che si possono usare per accedere alle informazioni della richiesta. Si dia
uno sguardo alle API per maggiori informazioni.
Risposta
Un oggetto Symfony\Component\HttpFoundation\Response contiene tutte le informazioni che devono essere rimandate al client, per una data richiesta. Il costruttore accetta fino a tre parametri: il contenuto della
risposta, il codice di stato e un array di header HTTP:
use Symfony\Component\HttpFoundation\Response;
$response = new Response(Contenuto, 200, array(content-type => text/html));

Queste informazioni possono anche essere manipolate dopo la creazione di Response:


$response->setContent(Ciao mondo);
// lattributo pubblico headers un ResponseHeaderBag
$response->headers->set(Content-Type, text/plain);
$response->setStatusCode(404);

Quando si imposta il Content-Type di Response, si pu impostare il charset, ma meglio impostarlo tramite


il metodo :method:Symfony\\Component\\HttpFoundation\\Response::setCharset:
$response->setCharset(ISO-8859-1);

Si noti che Symfony presume che le risposte siano codificate in UTF-8.


Inviare la risposta

Prima di inviare la risposta, ci si pu assicurare che rispetti le specifiche HTTP, richiamando il metodo
:method:Symfony\\Component\\HttpFoundation\\Response::prepare:
$response->prepare($request);

Inviare la risposta al client quindi semplice, basta richiamare :method:Symfony\\Component\\HttpFoundation\\Response::send


$response->send();

Impostare cookie

Si possono manipolare i cookie della risposta attraverso lattributo pubblico headers:


use Symfony\Component\HttpFoundation\Cookie;
$response->headers->setCookie(new Cookie(pippo, pluto));

454

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

Il metodo :method:Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::setCookie
unistanza di Symfony\Component\HttpFoundation\Cookie come parametro.

accetta

Si pu pulire un cookie tramite il metodo :method:Symfony\\Component\\HttpFoundation\\Response::clearCookie.


Gestire la cache HTTP

La classe Symfony\Component\HttpFoundation\Response ha un corposo insieme di metodi per manipolare gli header HTTP relativi alla cache:
:method:Symfony\\Component\\HttpFoundation\\Response::setPublic;
:method:Symfony\\Component\\HttpFoundation\\Response::setPrivate;
:method:Symfony\\Component\\HttpFoundation\\Response::expire;
:method:Symfony\\Component\\HttpFoundation\\Response::setExpires;
:method:Symfony\\Component\\HttpFoundation\\Response::setMaxAge;
:method:Symfony\\Component\\HttpFoundation\\Response::setSharedMaxAge;
:method:Symfony\\Component\\HttpFoundation\\Response::setTtl;
:method:Symfony\\Component\\HttpFoundation\\Response::setClientTtl;
:method:Symfony\\Component\\HttpFoundation\\Response::setLastModified;
:method:Symfony\\Component\\HttpFoundation\\Response::setEtag;
:method:Symfony\\Component\\HttpFoundation\\Response::setVary;
Il metodo :method:Symfony\\Component\\HttpFoundation\\Response::setCache pu essere usato per impostare le informazioni di cache pi comuni, con ununica chiamata:
$response->setCache(array(
etag
=> abcdef,
last_modified => new \DateTime(),
max_age
=> 600,
s_maxage
=> 600,
private
=> false,
public
=> true,
));

Per
verificare
che
i
validatori
della
risposta
(ETag,
Last-Modified)
corrispondano a un valore condizionale specificato nella richiesta del client,
usare il metodo
:method:Symfony\\Component\\HttpFoundation\\Response::isNotModified:
if ($response->isNotModified($request)) {
$response->send();
}

Se la risposta non stata modificata, imposta il codice di stato a 304 e rimuove il contenuto effettivo della risposta.
Rinviare lutente

Per rinviare il client a un altro URL, si pu usare la classe Symfony\Component\HttpFoundation\RedirectResponse:


use Symfony\Component\HttpFoundation\RedirectResponse;
$response = new RedirectResponse(http://example.com/);

4.1. I componenti

455

Symfony2 documentation Documentation, Release 2

Flusso di risposta

New in version 2.1: Il supporto per i flussi di risposte stato aggiunto in Symfony 2.1. La classe
Symfony\Component\HttpFoundation\StreamedResponse consente di inviare flussi di risposte al
client. Il contenuto della risposta viene rappresentato da un callable PHP, invece che da una stringa:
use Symfony\Component\HttpFoundation\StreamedResponse;
$response = new StreamedResponse();
$response->setCallback(function () {
echo Ciao mondo;
flush();
sleep(2);
echo Ciao mondo;
flush();
});
$response->send();

Scaricare file

New in version 2.1: Il metodo makeDisposition stato aggiunto in Symfony 2.1. Quando si carica un
file, occorre aggiungere un header Content-Disposition alla risposta. Sebbene la creazione di questo
header per scaricamenti di base sia facile, luso di nomi di file non ASCII pi complesso. Il metodo
:method::Symfony\\Component\\HttpFoundation\\Response:makeDisposition astrae lingrato compito dietro una semplice API:
use Symfony\\Component\\HttpFoundation\\ResponseHeaderBag;
$d = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, foo.pdf);
$response->headers->set(Content-Disposition, $d);

Sessione
TBD Questa parte non ancora stata scritta, perch probabilmente sar presto rifattorizzata in Symfony 2.1.

4.1.7 Il componente Locale


Il componente Locale fornisce codice per gestire casi in cui manchi lestensione intl. Inoltre,
estende limplementazione di una classe nativa :phpclass:Locale con diversi metodi.
Viene fornito un rimpiazzo per le seguenti funzioni e classi:
:phpfunction:intl_is_failure()
:phpfunction:intl_get_error_code()
:phpfunction:intl_get_error_message()
:phpclass:Collator
:phpclass:IntlDateFormatter
:phpclass:Locale
:phpclass:NumberFormatter
Note: Limplementazione di stub supporta solo il locale en.

456

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

Installazione
Si pu installare il componente in molti modi diversi:
Usare il repository ufficiale su Git (https://github.com/symfony/Locale);
Installarlo via PEAR ( pear.symfony.com/Locale);
Installarlo via Composer (symfony/locale su Packagist).
Uso
Tra i vantaggi presenti, inclusa la richiesta di funzioni stub e laggiunta di classi stub allautoloader.
Quando si usa il componente ClassLoader, il codice seguente basta a fornire lestensione intl mancante:
if (!function_exists(intl_get_error_code)) {
require __DIR__./percorso/src/Symfony/Component/Locale/Resources/stubs/functions.php;

$loader->registerPrefixFallbacks(array(__DIR__./percorso/src/Symfony/Component/Locale/Resourc
}

La classe Symfony\Component\Locale\Locale arricchisce la classe nativa :phpclass:Locale con caratteristiche aggiuntive:


use Symfony\Component\Locale\Locale;
// Nomi dei paesi per un locale, o tutti i codici dei paesi
$countries = Locale::getDisplayCountries(pl);
$countryCodes = Locale::getCountries();
// Nomi delle lingue per un locale, o tutti i codici delle lingue
$languages = Locale::getDisplayLanguages(fr);
$languageCodes = Locale::getLanguages();
// Nomi dei locale per un dato codice, o tutti i codici dei locale
$locales = Locale::getDisplayLocales(en);
$localeCodes = Locale::getLocales();
// Versioni ICU
$icuVersion = Locale::getIcuVersion();
$icuDataVersion = Locale::getIcuDataVersion();

4.1.8 Il componente Process


Il componente Process esegue i comandi nei sotto-processi.
Installazione
Si pu installare il componente in molti modi diversi:
Usare il repository ufficiale su Git (https://github.com/symfony/Process);
Installarlo via PEAR ( pear.symfony.com/Process);
Installarlo via Composer (symfony/process su Packagist).
Uso
La classe Symfony\Component\Process\Process consente di eseguire un comando in un sotto-processo:

4.1. I componenti

457

Symfony2 documentation Documentation, Release 2

use Symfony\Component\Process\Process;
$process = new Process(ls -lsa);
$process->setTimeout(3600);
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException($process->getErrorOutput());
}
print $process->getOutput();

Il metodo :method:Symfony\\Component\\Process\\Process::run si prende cura delle sottili differenze tra le


varie piattaforme, durante lesecuzione del comando.
Quando si esegue un comando che gira a lungo (come la sincronizzazione di file con un server remoto), si pu dare un feedback allutente finale in tempo reale, passando una funzione anonima al metodo
:method:Symfony\\Component\\Process\\Process::run:
use Symfony\Component\Process\Process;
$process = new Process(ls -lsa);
$process->run(function ($type, $buffer) {
if (err === $type) {
echo ERR > .$buffer;
} else {
echo OUT > .$buffer;
}
});

Se si vuole eseguire del codice PHP in isolamento, usare invece PhpProcess:


use Symfony\Component\Process\PhpProcess;
$process = new PhpProcess(<<<EOF
<?php echo Ciao mondo; ?>
EOF);
$process->run();

New in version 2.1: La classe ProcessBuilder stata aggiunta nella 2.1.


Per far funzionare meglio il proprio codice su tutte le piattaforme, potrebbe essere preferibile usare la classe
Symfony\Component\Process\ProcessBuilder:
use Symfony\Component\Process\ProcessBuilder;
$builder = new ProcessBuilder(array(ls, -lsa));
$builder->getProcess()->run();

4.1.9 Il componente Routing


Il componente Routing confronta una richiesta HTTP con un insieme di variabili di configurazione.
Installazione
possibile installare il componente in diversi modi:
Utilizzando il repository ufficiale su Git (https://github.com/symfony/Routing);
Installandolo via PEAR ( pear.symfony.com/Routing);
Installandolo via Composer (symfony/routing su Packagist).

458

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

Utilizzo
Per poter usare un sistema di rotte di base, sono necessari tre elementi:
Una Symfony\Component\Routing\RouteCollection, che contiene la definizione delle rotte
(unistanza della classe Symfony\Component\Routing\Route)
Un Symfony\Component\Routing\RequestContext, che contiene informazioni relative alla
richiesta
Un Symfony\Component\Routing\Matcher\UrlMatcher, che associa la richiesta ad una singola rotta
Il seguente esempio assume che lautoloader sia gi stato configurato in modo tale da caricare il componente
Routing:
use
use
use
use

Symfony\Component\Routing\Matcher\UrlMatcher;
Symfony\Component\Routing\RequestContext;
Symfony\Component\Routing\RouteCollection;
Symfony\Component\Routing\Route;

$rotte = new RouteCollection();


$rotte->add(nome_rotta, new Route(/pippo, array(controller => MioControllore)));
$contesto = new RequestContext($_SERVER[REQUEST_URI]);
$abbinatore = new UrlMatcher($rotte, $contesto);
$parametri = $abbinatore->match( /pippo );
// array(controller => MioControllore, _route => nome_rotta)

Note: Particolare attenzione va data alutilizzo di $_SERVER[REQUEST_URI], perch potrebbe contenere


qualsiasi parametro della richiesta inserito nelURL creando problemi con labbinamento alla rotta. Un semplice
modo per risolvere il problema usare il componente HTTPFoundation come spiegato in below.

possibile
aggiungere
un
numero
qualsiasi
Symfony\Component\Routing\RouteCollection.

di

rotte

ad

una

classe

Il
metodo
:method:RouteCollection::add()<Symfony\\Component\\Routing\\RouteCollection::add>
accetta due parametri.
Il primo il nome della rotta, il secondo un oggetto
Symfony\Component\Routing\Route, il cui costruttore si aspetta di ricevere un percorso URL e
un array di variabili personalizzate. Larray di variabili personalizzate pu essere qualsiasi cosa che abbia senso
per lapplicazione e viene restituito quando la rotta viene abbinata.
Se
non
viene
trovato
alcun
abbinamento
con
la
rotta
verr
Symfony\Component\Routing\Exception\ResourceNotFoundException.

lanciata

una

Oltre alarray di variabili personalizzate, viene aggiunta la chiave _route che conterr il nome della rotta abbinata.
Definire le rotte

La definizione completa di una rotta pu contenere fino a quattro parti:


1. Lo schema dellURL della rotta. questo il valore con il quale si confronta lURL passato a RequestContext. Pu contenere diversi segnaposto (per esempio {segnaposto}) che possono abbinarsi a parti dinamiche
dellURL.
2. Un array di valori base. Contiene un array di valori arbitrari che verranno restituiti quando la richiesta viene
abbinata alla rotta.
3. Un array di requisiti. Definisce i requisiti per i valori dei segnaposto in forma di espressione regolare.

4.1. I componenti

459

Symfony2 documentation Documentation, Release 2

4. Un array di opzioni. Questo array contiene configurazioni interne per le rotte e, solitamente, sono la parte di
cui meno ci si interessa.
Si prenda la seguente rotta, che combina diversi dei concetti esposti:
$route = new Route(
/archivio/{mese}, // pattern per la rotta
array(controller => mostraArchivio), // valori predefiniti
array(mese => [0-9]{4}-[0-9]{2}), // requisiti
array() // opzioni
);
// ...
$parametri = $abbinatore->match(/archivio/2012-01);
// array(controller => mostraArchivio, mese => 2012-01, _route => ...)
$parametri = $abbinatore->match(/archivio/pippo);
// lancia una ResourceNotFoundException

In questo caso la rotta viene trovata con /archivio/2012/01, perch il segnaposto {mese} associabile
alla espressione regolare definita. Invece, per /archivio/pippo, non verr trovata nessuna corrispondenza
perch pippo non rispetta i requisiti del segnaposto.
Oltre ai requisiti definiti con le espressioni regolari, possibile definire due requisiti speciali:
_method richiede che il metodo HTTP utilizzato sia quello definito (HEAD, GET, POST, ...)
_scheme richiede che lo schema HTTP utilizzato sia quello definito (http, https)
La rotta seguente, per esempio, accetter le sole richieste a /pippo che siano eseguite con metodo POST e con
connessione sicura:
$rotta = new Route(/pippo, array(_method => post, _scheme => https ));

Tip: Per creare una corrispondenza che trovi tutte le url che inizino con un determinato percorso e terminino con
un suffisso arbitrario, possibile usare la seguente definizione:
$rotta = new Route(/inizio/{suffisso}, array(suffisso => ), array(suffisso => .*));

Usare i prefissi

possibile aggiungere sia rotte che nuove istanze di Symfony\Component\Routing\RouteCollection


ad unaltra collezione. In questo modo si possono creare alberi di rotte. Inoltre possibile definire dei prefissi,
requisiti predefiniti e opzioni predefinite per tutte le rotte di un sotto albero:
$radiceCollezione = new RouteCollection();
$subCollezione = new RouteCollection();
$subCollezione->add( /*...*/ );
$subCollezione->add( /*...*/ );
$radiceCollezione->addCollection($subCollezione, /prefisso, array(_scheme => https));

Configurare i parametri della richiesta

Symfony\Component\Routing\RequestContext fornisce informazioni relative alla richiesta attuale.


Con questa classe, tramite il suo costruttore, possibile definire tutti i parametri di una richiesta HTTP:

460

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

public function __construct($baseUrl = , $method = GET, $host = localhost, $scheme = http,

possibile
passare
i
valori
della
variabile
$_SERVER
per
popolare
Symfony\Component\Routing\RequestContext. Ma se si utilizza il componente HttpFoundation, possibile usarne la classe Symfony\Component\HttpFoundation\Request per riempire la
Symfony\Component\Routing\RequestContext con una scorciatoia:
use Symfony\Component\HttpFoundation\Request;
$context = new RequestContext();
$context->fromRequest(Request::createFromGlobals());

Generare un URL

Mentre la classe Symfony\Component\Routing\Matcher\UrlMatcher cerca di trovare una rotta che


sia adeguata ad una determinata richiesta, anche possibile creare degli URL a partire da una determinata rotta:
use Symfony\Component\Routing\Generator\UrlGenerator;
$rotte = new RouteCollection();
$rotte->add(mostra_articolo, new Route(/mostra/{slug}));
$contesto = new RequestContext($_SERVER[REQUEST_URI]);
$generatore = new UrlGenerator($rotte, $contesto);
$url = $generatore->generate(mostra_articolo, array(
slug => articolo-sul-mio-blog
));
// /mostra/articolo-sul-mio-blog

Note: Se fosse stato definito il requisito dello _scheme, verrebbe generata un URL assoluto nel caso in cui lo
schema corrente Symfony\Component\Routing\RequestContext non fosse coerente con i requisiti.

Caricare le rotte da un file

Si visto come sia semplice aggiungere rotte ad una collezione direttamente tramite PHP. Ma anche possibile
caricare le rotte da diversi tipi di file differenti.
Il componente del Routing contiene diverse classi di caricamento, ognuna delle quali fornisce labilit di
caricare collezioni di definizioni di rotte da file esterni di diverso formato. Ogni caricatore si aspetta
di ricevere unistanza di Symfony\Component\Config\FileLocator come argomento del costruttore. Si pu usare il Symfony\Component\Config\FileLocator per definire una array di percorsi
nei quali il caricatore andr a cercare i file richiesti. Se il file viene trova, il caricatore restituisce una
Symfony\Component\Routing\RouteCollection.
Si utilizza il caricatore YamlFileLoader, allora la definizione delle rotte sar simile alla seguente:
# rotte.yml
rotta1:
pattern: /pippo
defaults: { controller: MioControllore::pippoAction }
rotta2:
pattern: /pippo/pluto
defaults: { controller: MioControllore::pippoPlutoAction }

Per caricare questo file, possibile usare il seguente codice. Si presume che il file rotte.yml sia nella stessa
cartella in cui si trova i codice:
4.1. I componenti

461

Symfony2 documentation Documentation, Release 2

use Symfony\Component\Config\FileLocator;
use Symfony\Component\Routing\Loader\YamlFileLoader;
// controlla alinterno della cartella *corrente*
$cercatore = new FileLocator(array(__DIR__));
$caricatore = new YamlFileLoader($cercatore);
$collezione = $caricatore->load(rotte.yml);

Oltre a Symfony\Component\Routing\Loader\YamlFileLoader ci sono altri due caricatori che funzionano nello stesso modo:
Symfony\Component\Routing\Loader\XmlFileLoader
Symfony\Component\Routing\Loader\PhpFileLoader
Se si usa Symfony\Component\Routing\Loader\PhpFileLoader sar necessario fornire il nome del
file php che restituir una Symfony\Component\Routing\RouteCollection:
// FornitoreDiRotte.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collezione = new RouteCollection();
$collezione->add(nome_rotta, new Route(/pippo, array(controller => ControlloreEsempio)));
// ...
return $collezione;

Rotte e Closure Esiste anche un Symfony\Component\Routing\Loader\ClosureLoader, il quale


chiama una closure e ne utilizza il risultato come una Symfony\Component\Routing\RouteCollection:
use Symfony\Component\Routing\Loader\ClosureLoader;
$closure = function() {
return new RouteCollection();
};
$caricatore = new ClosureLoader();
$collezione = $caricatore->load($closure);

Rotte e annotazioni Ultime, ma non meno importanti sono Symfony\Component\Routing\Loader\AnnotationDire


e Symfony\Component\Routing\Loader\AnnotationFileLoader usate per caricare le rotte a
partire dalle annotazioni delle classi. Questo articolo non tratter i dettagli di queste classi.
Il router tutto-in-uno

La classe Symfony\Component\Routing\Router un pacchetto tutto-in-uno che permette i usare rapidamente il componente Routing. Il costruttore si aspetta di ricevere listanza di un caricatore, un percorso per la
definizione della rotta principale e alcuni altri parametri:

public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestC

Tramite lopzione cache_dir possibile abilitare la cache delle rotte (cio se si fornisce un percorso) o disabilitarla (se viene configurata a null). La cache realizzata automaticamente nello sfondo, qualora la si volesse
utilizzare. Un semplice esempio di come sia fatta la classe Symfony\Component\Routing\Router riportato di seguito:
$cercatore = new FileLocator(array(__DIR__));
$contestoRichiesta = new RequestContext($_SERVER[REQUEST_URI]);

462

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

$router = new Router(


new YamlFileLoader($cercatore),
"rotte.yml",
array(cache_dir => __DIR__./cache),
$contestoRichiesta,
);
$router->match(/pippo/pluto);

Note: Se si utilizza la cache, il componente Routing compiler nuove classi che saranno salvate in cache_dir.
Questo significa che lo script deve avere i permessi di scrittura nella cartella indicata.

4.1.10 Il componente YAML


Il componente YAML carica ed esporta file YAML.
Che cos?
Il componente YAML di Symfony2 analizza stringhe YAML da convertire in array PHP. anche in grado di
convertire array PHP in stringhe YAML.
YAML, YAML Aint Markup Language, uno standard amichevole di serializzazione di dati per tutti i linguaggi
di programmazione. YAML un ottimo formato per i file di configurazione. I file YAML sono espressivi quanto
i file XML e leggibili quanto i file INI.
Il componente YAML di Symfony2 implementa la versione 1.2. della specifica.
Installazione
Si pu installare il componente in molti modi diversi:
Usare il repository ufficiale su Git (https://github.com/symfony/Yaml);
Installarlo via PEAR ( pear.symfony.com/Yaml);
Installarlo via Composer (symfony/yaml su Packagist).
Perch?
Veloce

Uno degli scopi di YAML trovare il giusto rapporto tra velocit e caratteristiche. Supporta proprio la caratteristica
necessaria per gestire i file di configurazione.
Analizzatore reale

Dispone di un analizzatore reale, capace di analizzare un grande sotto-insieme della specifica YAML, per tutte
le necessit di configurazione. Significa anche che lanalizzatore molto robusto, facile da capire e facile da
estendere.
Messaggi di errore chiari

Ogni volta che si ha un problema di sintassi con un proprio file YAML, la libreria mostra un utile messaggio
di errore, con il nome del file e il numero di riga in cui il problema si verificato. Questo facilita parecchio le
operazioni di debug.

4.1. I componenti

463

Symfony2 documentation Documentation, Release 2

Supporto per lesportazione

anche in grado di esportare array PHP in YAML con supporto agli oggetti e configurazione in linea per output
eleganti.
Tipi supportati

Supporta la maggior parte dei tipi di YAML, come date, interi, ottali, booleani e molto altro...
Pieno supporto alla fusione di chiavi

Pieno supporto per riferimenti, alias e piena fusione di chiavi. Non occorre ripetersi usando riferimenti a bit
comuni di configurazione.
Usare il componente YAML di Symfony2
Il componente YAML di Symfony2 molto semplice e consiste di due classi principali: una analizza le
stringhe YAML (Symfony\Component\Yaml\Parser) e laltra esporta un array PHP in una stringa YAML
(Symfony\Component\Yaml\Dumper).
Sopra queste classi, la classe Symfony\Component\Yaml\Yaml funge da involucro leggero, il che semplifica
gli usi pi comuni.
Leggere file YAML

Il metodo :method:Symfony\\Component\\Yaml\\Parser::parse analizza una stringa YAML e la converte in


un array PHP:
use Symfony\Component\Yaml\Parser;
$yaml = new Parser();
$value = $yaml->parse(file_get_contents(/percorso/del/file.yml));

Se
si
verifica
un
errore
durante
lanalizi,
lanalizzatore
lancia
uneccezione
Symfony\Component\Yaml\Exception\ParseException, che indica il tipo di errore e la riga
della stringa YAML originale in cui lerrore si verificato:
use Symfony\Component\Yaml\Exception\ParseException;
try {
$value = $yaml->parse(file_get_contents(/percorso/del/file.yml));
} catch (ParseException $e) {
printf("Impossibile analizzare la stringa YAML: %s", $e->getMessage());
}

Tip: Poich lanalizzatore rientrante, si pu usare lo stesso oggetto analizzatore per caricare diverse stringhe
YAML.
Quando si carica un file YAML, a volte
:method:Symfony\\Component\\Yaml\\Yaml::parse:

meglio

usare

il

metodo

involucro

use Symfony\Component\Yaml\Yaml;
$loader = Yaml::parse(/percorso/del/file.yml);

464

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

Il metodo statico :method:Symfony\\Component\\Yaml\\Yaml::parse prende una stringa YAML o un file contenente YAML. Internamente, richiama il metodo :method:Symfony\\Component\\Yaml\\Parser::parse, ma
con alcuni bonus aggiuntivi:
Esegue il file YAML come se fosse un file PHP, quindi si possono inserire comandi PHP nei file YAML;
Quando un file non pu essere analizzato, aggiunge automaticamente il nome del file al messaggio di errore,
semplificando il debug, quando lapplicazione sta caricando numerosi file YAML.
Scrivere file YAML

Il metodo :method:Symfony\\Component\\Yaml\\Dumper::dump esporta un array PHP nella corrispondente


rappresentazione YAML:
use Symfony\Component\Yaml\Dumper;
$array = array(foo => bar, bar => array(foo => bar, bar => baz));
$dumper = new Dumper();
$yaml = $dumper->dump($array);
file_put_contents(/percorso/del/file.yml, $yaml);

Note: Ovviamente, lesportatore YAML non in grado di esportare risorse. Inoltre, anche se lesportatore in
grado di esportare oggetti PHP, la caratteristica considerata come non supportata.
Se
si
verifica
un
errore
durante
lesportazione,
Symfony\Component\Yaml\Exception\DumpException.

lesportatore

lancia

uneccezione

Se si ha bisogno di esportare un solo array, si pu usare come scorciatoia il metodo statico


:method:Symfony\\Component\\Yaml\\Yaml::dump:
use Symfony\Component\Yaml\Yaml;
$yaml = Yaml::dump($array, $inline);

Il formato YAML supporta due tipi di rappresentazioni di array, quello espanso e quello in linea. Per impostazione
predefinita, lesportatore usa la rappresentazione in linea:
{ foo: bar, bar: { foo: bar, bar: baz } }

Il secondo parametro del metodo :method:Symfony\\Component\\Yaml\\Dumper::dump personalizza il livello in cui loutput cambia dalla rappresentazione espansa a quella in linea:
echo $dumper->dump($array, 1);
foo: bar
bar: { foo: bar, bar: baz }
echo $dumper->dump($array, 2);
foo: bar
bar:
foo: bar
bar: baz

Il formato YAML
Secondo il sito ufficiale di YAML, YAML uno standard amichevole di serializzazione dei dati per tutti i linguaggi di programmazione.
4.1. I componenti

465

Symfony2 documentation Documentation, Release 2

Anche se il formato YAML pu descrivere strutture di dati annidate in modo complesso, questo capitolo descrive
solo linsieme minimo di caratteristiche per usare YAML come formato per i file di configurazione.
YAML un semplice linguaggio che descrive dati. Come PHP, ha una sintassi per tipi semplici, come stringhe,
booleani, numeri a virgola mobile o interi. Ma, diversamente da PHP, distingue tra array (sequenze) e hash
(mappature).
Scalari

La sintassi per gli scalari simile a quella di PHP.


Stringhe
Una stringain YAML
Una string in YAML tra apici singoli

Tip: In una stringa tra apici singoli, un apice singolo va raddoppiato:


Un apice singolo in una stringa tra apici singoli

"Una string in YAML tra apici doppi\n"

Gli apici sono utili quando una stringa inizia o finisce con uno o pi spazi significativi.
Tip: Lo stile a doppi apici fornisce un modo per esprimere stringhe arbitrarie, ma usando sequenze di escape con
\ escape. molto utile quando occorre inserire \n o un carattere unicode in una stringa.
Quando una stringa contiene degli a capo, si pu usare lo stile letterale, indicato dalla barra verticale (|), per
indicare che la stringa si estende su diverse righe. Nei letterali, gli a capo sono preservati:
|
\/ /| |\/| |
/ / | | | |__

In alternativa, le stringhe possono essere scritte con lo stile avvolto, denotato da >, in cui gli a capo sono sostituiti
da uno spazio:
>
Questa una frase molto lunga
che si espande per diverse righe in YAML
ma che sar resa come una stringa
senza rimandi a capo.

Note: Si notino i due spazi prima di ogni riga nellesempio qui sopra. Non appariranno nella stringa PHP
risultante.

Numeri
# un intero
12
# un ottale
014
# un esadecimale
0xC

466

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

# un numero a virgola mobile


13.4
# un esponenziale
1.2e+34
# infinito
.inf

Null Null in YAML pu essere espresso con null o con ~.


Booleani I booleani in YAML sono espressi con true e false.
Date YAML usa lo standard ISO-8601 per esprimere le date:
2001-12-14t21:59:43.10-05:00
# data semplice
2002-12-14

Insiemi

Un file YAML usato raramente per descrivere semplici scalari. La maggior parte delle volte, descrive un insieme. Un insieme pu essere una sequenza o una mappatura di elementi. Sia le sequenze che le mappature sono
convertite in array PHP.
Le sequenze usano un trattino, seguito da uno spazio (- ):
- PHP
- Perl
- Python

Il file YAML qui sopra equivale al seguente codice PHP:


array(PHP, Perl, Python);

Le mappature usano un due punti, seguito da uno spazio (: ) per marcare ogni coppia chiave/valore:
PHP: 5.2
MySQL: 5.1
Apache: 2.2.20

che equivale a questo codice PHP:


array(PHP => 5.2, MySQL => 5.1, Apache => 2.2.20);

Note: In una mappatura, una chiave pu essere un qualsiasi scalare valido.


Il numero di spazi tra i due punti e il valore non significativo:
PHP:
5.2
MySQL: 5.1
Apache: 2.2.20

YAML usa unindentazione con uno o pi spazi per descrivere insiemi annidati:

4.1. I componenti

467

Symfony2 documentation Documentation, Release 2

"symfony 1.0":
PHP:
5.0
Propel: 1.2
"symfony 1.2":
PHP:
5.2
Propel: 1.3

Lo YAML qui sopra equivale al seguente codice PHP:


array(
symfony 1.0
PHP
=>
Propel =>
),
symfony 1.2
PHP
=>
Propel =>
),
);

=> array(
5.0,
1.2,
=> array(
5.2,
1.3,

C una cosa importante da ricordare quando si usano le indentazioni in un file YAML: le indentazioni devono
essere fatte con uno o pi spazi, ma mai con tabulazioni.
Si possono annidare sequenze e mappature a volont:
Capitolo 1:
- Introduzione
- Tipi di eventi
Capitolo 2:
- Introduzione
- Helper

YAML pu anche usare stili fluenti per gli insiemi, usando indicatori espliciti invece che le intendantazioni, per
denotare il livello.
Una sequenza pu essere scritta come lista separata da virgole in parentesi quadre ([]):
[PHP, Perl, Python]

Una mappatura pu essere scritta come lista separata da virgole di chiavi/valori tra parentesi graffe ({}):
{ PHP: 5.2, MySQL: 5.1, Apache: 2.2.20 }

Si possono mescolare gli stili, per ottenere una migliore leggibilit:


Chapter 1: [Introduzione, Tipi di eventi]
Chapter 2: [Introduzione, Helper]
"symfony 1.0": { PHP: 5.0, Propel: 1.2 }
"symfony 1.2": { PHP: 5.2, Propel: 1.3 }

Commenti

Si possono aggiungere commenti in YAML, usando come prefisso un cancelletto (#):


# Commento su una riga
"symfony 1.0": { PHP: 5.0, Propel: 1.2 } # Commento a fine riga
"symfony 1.2": { PHP: 5.2, Propel: 1.3 }

Note: I commenti sono semplicemente ignorati dallanalizzatore YAML e non necessitano di indentazione in
base al livello di annidamento di un insieme.

468

Chapter 4. Componenti

Symfony2 documentation Documentation, Release 2

Leggere la documentazione dei componenti.

4.1. I componenti

469

Symfony2 documentation Documentation, Release 2

470

Chapter 4. Componenti

CHAPTER

FIVE

DOCUMENTI DI RIFERIMENTO
Trovare rapidamente le risposte, con i documenti di riferimento:

5.1 Documenti di riferimento


5.1.1 Configurazione di FrameworkBundle (framework)
Questo riferimento ancora provvisorio. Dovrebbe essere accurato, ma non sono pienamente coperate tutte le
opzioni.
FrameworkBundle contiene la maggior parte delle funzionalit di base del framework e pu essere configurato
sotto la chiave framework nella configurazione della propria applicazione. Include impostazioni relative a
sessioni, traduzione, form, validazione, rotte e altro.
Configurazione
charset
secret
ide
test
form
enabled
csrf_protection
enabled
field_name
session_
lifetime
templating_
assets_base_urls
assets_version
assets_version_format

471

Symfony2 documentation Documentation, Release 2

charset

tipo: stringa predefinito: UTF-8


Il set di caratteri usato nel framework.
kernel.charset.

Diventa il parametro del contenitore di servizi di nome

secret

tipo: stringa obbligatorio


Una stringa che dovrebbe essere univoca nella propria applicaizone. In pratica, usta per generare il token antiCSRF, ma potrebbe essere usata in ogni altro contesto in cui utili avere una stringa univoca. Diventa il parametro
del contenitore di servizi di nome kernel.secret.
ide

tipo: stringa predefinito: null


Se si usa un IDE, come TextMate o Mac Vim, allora Symfony pu cambiare tutti i percorsi del file nei messaggi
di eccezione in collegamenti, che apriranno i file nel proprio IDE.
Se si usa TextMate o Mac Vim, si possono usare semplicemente uno dei seguenti valori:
textmate
macvim
Si pu anche specificare una stringa con un collegamento personalizzato. Se lo si fa, tutti i simboli percentuale
(%) devono essere raddoppiati, per escape. Per esempio, la stringa completa per TextMate sarebbe come questa:
framework:
ide: "txmt://open?url=file://%%f&line=%%l"

Ovviamente, poich ogni sviluppatore usa un IDE diverso, meglio impostarlo a livello di sistema. Lo si pu fare
impostando il valore xdebug.file_link_format di php.ini alla stringa del collegamento. Se questo valore
di configurazione impostato, non occorre specificare lopzione ide.
test

tipo: booleano
Se questo parametro di configurazione presente e non false, saranno caricati i servizi correlati ai test della
propria applicazione (p.e. test.client). Questa impostazione dovrebbe essere presete nel proprio ambiente
test (solitamente tramite app/config/config_test.yml). Per maggiori informazioni, vedere Test.
form

csrf_protection
sessioni

lifetime

tipo: integer predefinito: 86400

Determina il tempo di scadeza della sessione, in secondi.

472

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

template

assets_base_urls

predefinito: { http:

[], https:

[] }

Questa opzione consente di definire URL di base da usare per i riferimenti alle risorse nelle pagine http e https.
Si pu fornire un valore stringa al posto di un array a elementi singoli. Se si forniscono pi URL base, Symfony2
ne sceglier una dallelenco ogni volta che genera il percorso di una risorsa.
Per praticit, assets_base_urls pu essere impostata direttamente con una stringa o array di stringhe, che
saranno automaticamente organizzate in liste di URL base per le richieste http e https. Se un URL inizia con
https:// o protocol-relative (cio inizia con // ), sar aggiunto a entrambe le liste. Gli URL che iniziano con
http:// saranno aggiunti solo alla lista http. New in version 2.1.
assets_version

tipo: stringa

Questa opzione usata per spaccare le risorse in cache, aggiungendo globalmente un parametro di query a tutti
i percorsi delle risorse (p.e. /images/logo.png?v2). Si applica solo alle risorse rese tramite la funzione
asset di Twig (o al suo equivalente PHP), come pure alle risorse rese con Assetic.
Per esempio, si supponga di avere il seguente:
Twig
<img src="{{ asset(images/logo.png) }}" alt="Symfony!" />

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

Per impostazione predefinita, render un percorso alla propria immagine, come /images/logo.png. Ora,
attivare lopzione assets_version:
YAML
# app/config/config.yml
framework:
# ...
templating: { engines: [twig], assets_version: v2 }

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

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

Ora, la stessa risora sar resa come /images/logo.png?v2. Se si usa questa caratteristica, si deve incrementare a mano il valore di assets_version, prima di ogni deploy, in modo che il parametro della query
cambi.
Si pu anche contollare il funzionamento della stringa della query, tramite lopzione assets_version_format.

5.1. Documenti di riferimento

473

Symfony2 documentation Documentation, Release 2

assets_version_format

tipo: stringa predefinito: %%s?%%s

Specifica uno schema per sprintf(), usato con lopzione assets_version per costruire il percorso della risorsa. Per
impostazione predefinita, lo schema aggiunge la versione della risorsa alla stringa della query. Per esempio, se
assets_version_format impostato a %%s?version=%%s e assets_version impostato a 5, il
percorso della risorsa sar /images/logo.png?version=5.
Note: Tutti i simboli percentuale (%) nel formato devono essere raddoppiati per escape. Senza escape, i valori
sarebbero inavvertitamente interpretati come I parametri del servizio.

Tip: Alcuni CDN non sopportano la spaccatura della cache tramie stringa della query, quindi si rende necessario
linserimento della versione nel vero percorso della risorsa. Fortunatamente, assets_version_format non
limitato alla produzionoe di stringhe di query con versioni.
Lo schema riceve il percorso originale della risorsa e la versione come primo e secondo parametro,
rispettivamente. Poich il percorso della risorsa un parametro, non possiamo modificarlo al volo (p.e.
/images/logo-v5.png). Tuttavia, possiamo aggiungere un prefisso al percorso della risorsa, usando uno
schema version-%%2$s/%%1$s, che risulta nel percorso version-5/images/logo.png.
Si possono quindi usare le riscritture degli URL, per togliere il prefissod con la versione prima di servire la risorsa.
In alternativa, si possono copiare le risorse nel percorso appropriato con la versione, come parte del processo di
deploy, e non usare la riscrittura degli URL. Lultima opzione utile se si vuole che le vecchie versioni delle
risorse restino disponibili nei loro URL originari.

Configurazione predefinita completa


YAML
framework:
# general configuration
charset:
~
secret:
~ # Required
ide:
~
test:
~
default_locale:
en
trust_proxy_headers: false
# form configuration
form:
enabled:
csrf_protection:
enabled:
field_name:
# esi configuration
esi:
enabled:

true
true
_token

true

# profiler configuration
profiler:
only_exceptions:
false
only_master_requests: false
dsn:
sqlite:%kernel.cache_dir%/profiler.db
username:
password:
lifetime:
86400
matcher:
ip:
~

474

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

path:
service:

~
~

# router configuration
router:
resource:
type:
http_port:
https_port:

~ # Required
~
80
443

# session configuration
session:
auto_start:
storage_id:
name:
lifetime:
path:
domain:
secure:
httponly:

~
session.storage.native
~
86400
~
~
~
~

# templating configuration
templating:
assets_version:
~
assets_version_format: "%%s?%%s"
assets_base_urls:
http:
[]
ssl:
[]
cache:
~
engines:
# Required
form:
resources:
[FrameworkBundle:Form]
# Example:
- twig
loaders:
packages:

[]

# Prototype
name:
version:
version_format:
base_urls:
http:
ssl:

~
~
[]
[]

# translator configuration
translator:
enabled:
true
fallback:
en
# validation configuration
validation:
enabled:
true
cache:
~
enable_annotations:
false
# annotation configuration
annotations:
cache:
file
file_cache_dir:
%kernel.cache_dir%/annotations
debug:
true

5.1. Documenti di riferimento

475

Symfony2 documentation Documentation, Release 2

5.1.2 Riferimento configurazione AsseticBundle


Configurazione predefinita completa
YAML
assetic:
debug:
use_controller:
read_from:
write_to:
java:
node:
sass:
bundles:
#
-

true
true
%kernel.root_dir%/../web
%assetic.read_from%
/usr/bin/java
/usr/bin/node
/usr/bin/sass

Defaults (all currently registered bundles):


FrameworkBundle
SecurityBundle
TwigBundle
MonologBundle
SwiftmailerBundle
DoctrineBundle
AsseticBundle
...

assets:
# Prototype
name:
inputs:
filters:
options:

[]
[]

# Prototype
name:

[]

filters:
# Prototype
name:
twig:
functions:

[]

# Prototype
name:

[]

5.1.3 Riferimento configurazione


YAML
doctrine:
dbal:
default_connection:
connections:
default:
dbname:
host:
port:
user:
password:
driver:

476

default

database
localhost
1234
user
secret
pdo_mysql

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

driver_class:
MyNamespace\MyDriverImpl
options:
foo: bar
path:
%kernel.data_dir%/data.sqlite
memory:
true
unix_socket:
/tmp/mysql.sock
wrapper_class:
MyDoctrineDbalConnectionWrapper
charset:
UTF8
logging:
%kernel.debug%
platform_service:
MyOwnDatabasePlatformService
mapping_types:
enum: string
conn1:
# ...
types:
custom: Acme\HelloBundle\MyCustomType
orm:
auto_generate_proxy_classes:
false
proxy_namespace:
Proxies
proxy_dir:
%kernel.cache_dir%/doctrine/orm/Proxies
default_entity_manager:
default # The first defined is used if not set
entity_managers:
default:
# The name of a DBAL connection (the one marked as default is used if not set
connection:
conn1
mappings: # Required
AcmeHelloBundle: ~
class_metadata_factory_name:
Doctrine\ORM\Mapping\ClassMetadataFactory
# All cache drivers have to be array, apc, xcache or memcache
metadata_cache_driver:
array
query_cache_driver:
array
result_cache_driver:
type:
memcache
host:
localhost
port:
11211
instance_class: Memcache
class:
Doctrine\Common\Cache\MemcacheCache
dql:
string_functions:
test_string: Acme\HelloBundle\DQL\StringFunction
numeric_functions:
test_numeric: Acme\HelloBundle\DQL\NumericFunction
datetime_functions:
test_datetime: Acme\HelloBundle\DQL\DatetimeFunction
hydrators:
custom: Acme\HelloBundle\Hydrators\CustomHydrator
em2:
# ...

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 default-connection="default">
<doctrine:connection
name="default"
dbname="database"
host="localhost"

5.1. Documenti di riferimento

477

Symfony2 documentation Documentation, Release 2

port="1234"
user="user"
password="secret"
driver="pdo_mysql"
driver-class="MyNamespace\MyDriverImpl"
path="%kernel.data_dir%/data.sqlite"
memory="true"
unix-socket="/tmp/mysql.sock"
wrapper-class="MyDoctrineDbalConnectionWrapper"
charset="UTF8"
logging="%kernel.debug%"
platform-service="MyOwnDatabasePlatformService"
>
<doctrine:option key="foo">bar</doctrine:option>
<doctrine:mapping-type name="enum">string</doctrine:mapping-type>
</doctrine:connection>
<doctrine:connection name="conn1" />
<doctrine:type name="custom">Acme\HelloBundle\MyCustomType</doctrine:type>
</doctrine:dbal>

<doctrine:orm default-entity-manager="default" auto-generate-proxy-classes="false" pr


<doctrine:entity-manager name="default" query-cache-driver="array" result-cache-d
<doctrine:metadata-cache-driver type="memcache" host="localhost" port="11211"
<doctrine:mapping name="AcmeHelloBundle" />
<doctrine:dql>
<doctrine:string-function name="test_string>Acme\HelloBundle\DQL\StringFu
<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:entity-manager name="em2" connection="conn2" metadata-cache-driver="apc
<doctrine:mapping
name="DoctrineExtensions"
type="xml"
dir="%kernel.root_dir%/../src/vendor/DoctrineExtensions/lib/DoctrineExten
prefix="DoctrineExtensions\Entity"
alias="DExt"
/>
</doctrine:entity-manager>
</doctrine:orm>
</doctrine:config>
</container>

Panoramica della configurazione


Il seguente esempio di configurazione mostra tutte le configurazioni predefinite, che lORM risolve:
doctrine:
orm:
auto_mapping: true
# la distribuzione standard sovrascrive a true in debug, false altrimenti
auto_generate_proxy_classes: false
proxy_namespace: Proxies
proxy_dir: %kernel.cache_dir%/doctrine/orm/Proxies
default_entity_manager: default
metadata_cache_driver: array
query_cache_driver: array
result_cache_driver: array

Ci sono molte altre opzioni di configurazione che si possono usare per sovrascrivere determinate classi, ma sono
solo per casi molto avanzati.

478

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Driver per la cache

Per i driver della cache, si pu specificare array, apc, memcache o xcache.


Lesempio seguente mostra una panoramica delle configurazioni di cache:
doctrine:
orm:
auto_mapping: true
metadata_cache_driver: apc
query_cache_driver: xcache
result_cache_driver:
type: memcache
host: localhost
port: 11211
instance_class: Memcache

Configurazioni della mappatura

La definizione esplicita di tutte le entit mappate lunica configurazione necessaria per lORM e ci sono diverse
opzioni di configurazione controllabili. La mappatura dispone delle seguenti opzioni di configurazione:
type Uno tra annotation, xml, yml, php o staticphp. Specifica quale di tipo di meta-dati usa la
mappatura.
dir Percorso per la mappatura o per i file entit (a seconda del driver). Se questo percorso relativo, si
assume sia relativo alla radice dei bundle. Funziona solo se il nome della propria mappatura il nome di
un bundle. Se si vuole usare questa opzione per specificare percorsi assoluti, si dovrebbe aggiungere al
percorso un prefisso con i parametri del kernel nel DIC (per esempio %kernel.root_dir%).
prefix Un prefisso comune di spazio dei nomi che tutte le entit di questa mappatura condividono.
Questo prefisso non deve essere in conflitto con i prefissi di altre mappature definite, altrimenti alcune
entit non saranno trovate da Doctrine. Questa opzione ha come valore predefinito lo spazio dei nomi
del bundle + Entity, per esempio per un bundle chiamato AcmeHelloBundle il prefisso sarebbe
Acme\HelloBundle\Entity.
alias Doctrine offre un modo per avere alias di spazi dei nomi con nomi pi corti e semplici, da usare
nelle query DQL o per laccesso al Repository. Quando si usa un bundle, lalias predefinito il nome del
bundle.
is_bundle Questa opzione un valore derivato da dir ed ha true come valore predefinito, se la cartella
fornita da una verifica con file_exists() che restituisca false. false se la verifica restituisce
true. In questo caso, un percorso assoluto stato specificato e i file dei meta-dati sono probabilmente in
una cartella fuori da un bundle.
Configurazione Doctrine DBAL

Note: DoctrineBundle supporta tutti i parametri che i driver predefiniti di Doctrine accettano, convertiti alla
nomenclatura XML o YML di Symfony. Vedere la documentazione DBAL di Doctrine per maggiori informazioni.
Oltre alle opzioni di Doctrine, ci sono alcune opzioni relative a Symfony che si possono configurare. Il blocco
seguente mostra tutte le voci di configurazione:
YAML
doctrine:
dbal:
dbname:
host:
port:

5.1. Documenti di riferimento

database
localhost
1234

479

Symfony2 documentation Documentation, Release 2

user:
user
password:
secret
driver:
pdo_mysql
driver_class:
MyNamespace\MyDriverImpl
options:
foo: bar
path:
%kernel.data_dir%/data.sqlite
memory:
true
unix_socket:
/tmp/mysql.sock
wrapper_class:
MyDoctrineDbalConnectionWrapper
charset:
UTF8
logging:
%kernel.debug%
platform_service:
MyOwnDatabasePlatformService
mapping_types:
enum: string
types:
custom: Acme\HelloBundle\MyCustomType

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
name="default"
dbname="database"
host="localhost"
port="1234"
user="user"
password="secret"
driver="pdo_mysql"
driver-class="MyNamespace\MyDriverImpl"
path="%kernel.data_dir%/data.sqlite"
memory="true"
unix-socket="/tmp/mysql.sock"
wrapper-class="MyDoctrineDbalConnectionWrapper"
charset="UTF8"
logging="%kernel.debug%"
platform-service="MyOwnDatabasePlatformService"
>
<doctrine:option key="foo">bar</doctrine:option>
<doctrine:mapping-type name="enum">string</doctrine:mapping-type>
<doctrine:type name="custom">Acme\HelloBundle\MyCustomType</doctrine:type>
</doctrine:dbal>
</doctrine:config>

Se si vogliono configurare connessioni multiple in YAML, si possono mettere sotto la voce connections e dar
loro un nome univoco:
doctrine:
dbal:
default_connection:
connections:
default:
dbname:
user:
password:
host:
customer:
dbname:
user:
password:

480

default

Symfony2
root
null
localhost
customer
root
null

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

host:

localhost

Il servizio database_connection fa sempre riferimento alla configurazione predefinita, che la prima


definita o lunica configurata tramite il parametro default_connection.
Ogni connessione anche accessibile tramite il servizio doctrine.dbal.[nome]_connection, in cui
[nome] il nome della connessione.

5.1.4 Riferimento configurazione sicurezza


Il sistema di sicurezza una delle parti pi potenti di Symfony2 e pu essere controllato in gran parte tramite la
sua configurazione.
Configurazione predefinita completa
La seguente la configurazione predefinita completa del sistema di sicurezza. Ogni parte sar spiegata nella
prossima sezione.
YAML
# app/config/security.yml
security:
access_denied_url: /foo/error403
always_authenticate_before_granting: false
# se richiamare eraseCredentials sul token
erase_credentials: true
# la strategia pu essere: none, migrate, invalidate
session_fixation_strategy: migrate
access_decision_manager:
strategy: affirmative
allow_if_all_abstain: false
allow_if_equal_granted_denied: true
acl:
connection: default # qualsiasi nome configurato nella sezione doctrine.dbal
tables:
class: acl_classes
entry: acl_entries
object_identity: acl_object_identities
object_identity_ancestors: acl_object_identity_ancestors
security_identity: acl_security_identities
cache:
id: service_id
prefix: sf2_acl_
voter:
allow_if_object_identity_unavailable: true
encoders:
somename:
class: Acme\DemoBundle\Entity\User
Acme\DemoBundle\Entity\User: sha512
Acme\DemoBundle\Entity\User: plaintext
Acme\DemoBundle\Entity\User:
algorithm: sha512
encode_as_base64: true
iterations: 5000
Acme\DemoBundle\Entity\User:

5.1. Documenti di riferimento

481

Symfony2 documentation Documentation, Release 2

id: my.custom.encoder.service.id
providers:
memory_provider_name:
memory:
users:
foo: { password: foo, roles: ROLE_USER }
bar: { password: bar, roles: [ROLE_USER, ROLE_ADMIN] }
entity_provider_name:
entity: { class: SecurityBundle:User, property: username }

factories:
MyFactory: %kernel.root_dir%/../src/Acme/DemoBundle/Resources/config/security_factori
firewalls:
somename:
pattern: .*
request_matcher: some.service.id
access_denied_url: /foo/error403
access_denied_handler: some.service.id
entry_point: some.service.id
provider: nome_di_un_provider_di_cui_sopra
context: name
stateless: false
x509:
provider: nome_di_un_provider_di_cui_sopra
http_basic:
provider: nome_di_un_provider_di_cui_sopra
http_digest:
provider: nome_di_un_provider_di_cui_sopra
form_login:
check_path: /login_check
login_path: /login
use_forward: false
always_use_default_target_path: false
default_target_path: /
target_path_parameter: _target_path
use_referer: false
failure_path: /foo
failure_forward: false
failure_handler: some.service.id
success_handler: some.service.id
username_parameter: _username
password_parameter: _password
csrf_parameter: _csrf_token
intention: authenticate
csrf_provider: my.csrf_provider.id
post_only: true
remember_me: false
remember_me:
token_provider: name
key: someS3cretKey
name: NameOfTheCookie
lifetime: 3600 # in seconds
path: /foo
domain: somedomain.foo
secure: true
httponly: true
always_remember_me: false
remember_me_parameter: _remember_me
logout:
path:
/logout
target: /

482

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

invalidate_session: false
delete_cookies:
a: { path: null, domain: null }
b: { path: null, domain: null }
handlers: [some.service.id, another.service.id]
success_handler: some.service.id
anonymous: ~
access_control:
path: ^/foo
host: mydomain.foo
ip: 192.0.0.0/8
roles: [ROLE_A, ROLE_B]
requires_channel: https
role_hierarchy:
ROLE_SUPERADMIN: ROLE_ADMIN
ROLE_SUPERADMIN: ROLE_ADMIN, ROLE_USER
ROLE_SUPERADMIN: [ROLE_ADMIN, ROLE_USER]
anything: { id: ROLE_SUPERADMIN, value: ROLE_USER, ROLE_ADMIN }
anything: { id: ROLE_SUPERADMIN, value: [ROLE_USER, ROLE_ADMIN] }

Configurazione del form di login


Quando si usa lascoltatore di autenticazione form_login dietro un firewall, ci sono diverse opzioni comuni
per configurare lesoerienza del form di login:
Il form e il processo di login

login_path (tipo: stringa, predefinito: /login) lURL a cui lutente sar rinviato (a meno che
use_forward non sia true) quando prova ad accedere a una risorsa protetta, ma non autenticato.
Questo URL deve essere accessibile da un utente normale e non autenticato, altrimenti si creerebbe un loop
infinito. Per dettagli, vedere Evitare problemi comuni.
check_path (tipo: stringa, predefinito: /login_check) lURL a cui il form di login viene
inviato. Il firewall intercetter ogni richiesta (solo quelle POST, per impostazione predefinita) a questo
URL e processer le credenziali di login inviate.
Assicurarsi che questo URL sia coperto dal proprio firewall principale (cio non creare un firewall separato
solo per lURL check_path).
use_forward (tipo: booleano, predefinito: false) Se si vuole che lutente sia rimandato al form di
login invece di essere rinviato, impostare questa opzione a true.
username_parameter (tipo: stringa, predefinito: _username) Questo il nome del campo che si
dovrebbe dare al campo username del proprio form di login. Quando si invia il form a check_path, il
sistema di sicurezza cercher un parametro POST con questo nome.
password_parameter (tipo: stringa, predefinito: _password) Questo il nome del campo che si
dovrebbe dare al campo password del proprio form di login. Quando si invia il form a check_path, il
sistema di sicurezza cercher un parametro POST con questo nome.
post_only (tipo: booleano, predefinito: true) Per impostazione predefinita, occorre inviare il proprio
form di login allURL check_path usando una richiesta POST. Impostando questa opzione a true, si
pu inviare una richiesta GET allURL check_path.

5.1. Documenti di riferimento

483

Symfony2 documentation Documentation, Release 2

Rinvio dopo il login

always_use_default_target_path (tipo: booleano, predefinito: false)


default_target_path (tipo: stringa, predefinito: /)
target_path_parameter (tipo: stringa, predefinito: _target_path)
use_referer (tipo: booleano, predefinito: false)

5.1.5 Configurazione di SwiftmailerBundle (swiftmailer)


Questo riferimento ancora provvisorio. Dovrebbe essere accurato, ma non sono pienamente coperte tutte le
opzioni. Per un elenco completo delle opzioni predefinite di configurazione, vedere Configurazione
La chiave swiftmailer configura lintegrazione di Symfony con Swiftmailer, che si occupa di creare e spedire
messaggi email.
Configurazione
transport
username
password
host
port
encryption
auth_mode
spool
type
path
sender_address
antiflood
threshold
sleep
delivery_address
disable_delivery
logging
transport

tipo: stringa predefinito: smtp


Il metodo di trasporto usato per inviare le email. Valori validi:
smtp
gmail (vedere /cookbook/gmail)
mail
sendmail

484

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

null (lo stesso che impostare disable_delivery a true)


username

tipo: stringa
Il nome utente quando si usa smtp come trasporto.
password

tipo: stringa
La password quando si usa smtp come trasporto.
host

tipo: stringa predefinito: localhost


Lhost a cui connettersi quando si usa smtp come trasporto.
port

tipo: stringa predefinito: 25 o 465 (a seconda di encryption)


La porta quando si usa smtp come trasporto. Predefinito 465 se encryption ssl, 25 in caso contrario.
encryption

tipo: stringa
La modalit di criptazione quando si usa smtp come trasporto. Valori validi sono tls, ssl o null (che indica
nessuna criptazione).
auth_mode

tipo: stringa
La modalit di autenticazione quando si usa smtp come trasporto. Valori validi sono plain, login, cram-md5
o null.
spool

Per dettagli sullo spool delle email, vedere Lo spool della posta.
type

tipo: stringa predefinito: file

Il metodo usato per memorizzare i messagi nello spool. Attualmente supportato solo file. Tuttavia, si dovrebbe
poter creare uno spool personalizzato, creando un servizio di nome swiftmailer.spool.mio_spool e
impostando questo valore a mio_spool.
path

tipo: stringa predefinito: %kernel.cache_dir%/swiftmailer/spool

Quando si usa file come spool, questo il percorso in cui i messaggi in spool sono memorizzati.

5.1. Documenti di riferimento

485

Symfony2 documentation Documentation, Release 2

sender_address

tipo: stringa
Se impostato, tutti i messaggi saranno inviati con questo indirizzo come return path, che
lindirizzo a cui i messaggi in bounce vengono spediti.
Viene gestito internamente dalla classe
Swift_Plugins_ImpersonatePlugin di Swiftmailer.
antiflood

threshold

tipo: stringa predefinito: 99

Usato con Swift_Plugins_AntiFloodPlugin. il numero di email da inviare prima di far ripartire il


trasporto.
sleep

tipo: stringa predefinito: 0

Usato con Swift_Plugins_AntiFloodPlugin. il numero di secondi da attendere prima della ripartenza


del trasporto.
delivery_address

tipo: stringa
Se impostato, tutti i messaggi email saranno inviati a questo indirizzo, invece che ai loro reali destinatari. Molto
utile durante lo sviluppo, Per esempio, impostandolo nel file config_dev.yml, ci si pu assicurare che tutte le
email inviate durante la fase di sviluppo siano inviate a un singolo account.
Usa Swift_Plugins_RedirectingPlugin.
X-Swift-To, X-Swift-Cc e X-Swift-Bcc.

I destinatari originali sono disponibili negli header

disable_delivery

tipo: booleano predefinito: false


Se true, transport sar automaticamente impostato a null e quindi nessuna email sar veramente inviata.
logging

tipo: booleano predefinito: %kernel.debug%


Se true, il raccoglitore di dati di Symfony sar attivato per Swiftmailer e le informazioni saranno disponibili nel
profiler.
Configurazione predefinita completa
YAML
swiftmailer:
transport:
username:
password:
host:
port:
encryption:
auth_mode:
spool:

486

smtp
~
~
localhost
false
~
~

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

type:
path:
sender_address:
antiflood:
threshold:
sleep:
delivery_address:
disable_delivery:
logging:

file
%kernel.cache_dir%/swiftmailer/spool
~
99
0
~
~
%kernel.debug%

5.1.6 Riferimento configurazione TwigBundle


YAML
twig:
form:
resources:
# Default:
- div_layout.html.twig
# Esempio:
- MioBundle::form.html.twig
globals:
# Esempi:
foo:
pi:
# Prototype
key:
id:
type:
value:
autoescape:
base_template_class:
cache:
charset:
debug:
strict_variables:
auto_reload:
exception_controller:

"@bar"
3.14

~
~
~

~
~ # Esempio: Twig_Template
%kernel.cache_dir%/twig
%kernel.charset%
%kernel.debug%
~
~
Symfony\Bundle\TwigBundle\Controller\ExceptionController::showActi

XML

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:twig="http://symfony.com/schema/dic/twig"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
http://symfony.com/schema/dic/twig http://symfony.com/schema/dic/doct

<twig:config auto-reload="%kernel.debug%" autoescape="true" base-template-class="Twig_Tem


<twig:form>
<twig:resource>MioBundle::form.html.twig</twig:resource>
</twig:form>
<twig:global key="foo" id="bar" type="service" />
<twig:global key="pi">3.14</twig:global>
</twig:config>
</container>

PHP

5.1. Documenti di riferimento

487

Symfony2 documentation Documentation, Release 2

$container->loadFromExtension(twig, array(
form => array(
resources => array(
MioBundle::form.html.twig,
)
),
globals => array(
foo => @bar,
pi => 3.14,
),
auto_reload
=> %kernel.debug%,
autoescape
=> true,
base_template_class => Twig_Template,
cache
=> %kernel.cache_dir%/twig,
charset
=> %kernel.charset%,
debug
=> %kernel.debug%,
strict_variables
=> false,
));

Configurazione
exception_controller

tipo: stringa predefinito: Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController::showAc


Questo il controllore che viene attivato dopo il lancio di uneccezione nella propria applicaizone. Il controllore predefinito (Symfony\Bundle\TwigBundle\Controller\ExceptionController) quello responsabile di rendere template specifici sotto differenti condizioni di errore (vedere Come personalizzare le pagine
di errore). La modifica di questa opzione avanzata. Se occorre personalizzare una pagina di errore, si dovrebbe
usare il collegamento precedente. Se occorre eseguire qualche azioni su uneccezione, si dovrebbe aggiungere un
ascoltatore allevento kernel.exception (vedere Abilitare ascoltatori personalizzati).

5.1.7 Riferimento configurazione


YAML
monolog:
handlers:
# Esempi:
syslog:
type:
path:
level:
bubble:
formatter:
main:
type:
action_level:
buffer_size:
handler:
custom:
type:
id:
# Prototype
name:
type:
id:
priority:

488

stream
/var/log/symfony.log
ERROR
false
my_formatter
fingers_crossed
WARNING
30
custom
service
my_handler

~ # Required
~
0

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

level:
DEBUG
bubble:
true
path:
%kernel.logs_dir%/%kernel.environment%.log
ident:
false
facility:
user
max_files:
0
action_level:
WARNING
stop_buffering:
true
buffer_size:
0
handler:
~
members:
[]
from_email:
~
to_email:
~
subject:
~
email_prototype:
id:
~ # Required (when the email_prototype is used)
method: ~
formatter:
~

XML

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:monolog="http://symfony.com/schema/dic/monolog"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
http://symfony.com/schema/dic/monolog http://symfony.com/schema/dic/m
<monolog:config>
<monolog:handler
name="syslog"
type="stream"
path="/var/log/symfony.log"
level="error"
bubble="false"
formatter="my_formatter"
/>
<monolog:handler
name="main"
type="fingers_crossed"
action-level="warning"
handler="custom"
/>
<monolog:handler
name="custom"
type="service"
id="my_handler"
/>
</monolog:config>
</container>

Note: Quando il profiler abilitato, viene aggiunto un gestore per memorizzare i messaggi di log nel profiler. Il
profiler usa il nome debug, quindi il nome riservato e non pu essere usato nella configurazione.

5.1.8 Configurazione di WebProfilerBundle


Configurazione completa predefinita
YAML

5.1. Documenti di riferimento

489

Symfony2 documentation Documentation, Release 2

web_profiler:
# mostra informazioni secondarie per rendere la barra pi corta
verbose:
true

# mostra la barra di web debug in fondo alle pagine con un riassunto delle info del profi
toolbar:
false
# d la possibilit di guardare i dati raccolti prima di seguire il rinvio
intercept_redirects: false

5.1.9 Riferimenti sui tipi nei form


Tipo di campo birthday
Un campo date specializzato nel gestire i compleanni.
Pu essere reso come singola casella di testo, tre caselle di testo (mese, giorno e anno) oppure tre select.
Questo tipo essenzialmente lo stesso di date, ma con valori predefiniti pi appropriati per lopzione years.
Lopzione years ha come valori predefiniti fino a 120 anni nel passato.
Tipo di dato sottostante
Reso come
Opzioni
Opzioni ereditate

Tipo genitore
Classe

pu essere DateTime, string, timestamp o


array (vedere opzione input)
pu essere tre select o 1 o 3 caselle di testo, in base
allopzione widget
years

widget
input
months
days
format
pattern
data_timezone
user_timezone

date
Symfony\Component\Form\Extension\Core\Type\Birthd

Opzioni del campo

years

tipo: array predefinito: 120 anni nel passato

Lista di anni disponibili nel campo anno. Questa opzione rilevante solo quando lopzione widget impostata
a choice.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo date:


widget

tipo: stringa predefinito: choice

Il modo di base in cui questo campo andrebbe reso. Pu essere uno tra:
choice: rende tre campi select. Lordine dei select definito nellopzione pattern.

490

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

text: rende tre campi testuali (mese, giorno, anno).


single_text: rende un singolo campo testuale. I dati inseriti dallutente sono validati in base allopzione
format.
input

tipo: stringa predefinito: datetime

Il formato in ingresso dei dati, cio il formato in cui la data memorizzata nelloggetto sottostante. Valori validi
sono:
stringa (p.e. 2011-06-05)
datetime (un oggetto DateTime)
array (p.e. array(year => 2011, month => 06, day => 05))
timestamp (p.e. 1307232000)
Anche il valore proveniente dal form sar normalizzato in tale formato.
months

tipo: array predefinito: da 1 a 12

Lista di ore disponibili per il tipo di campo month. Questa opzione rilevante solo se lopzione widget
impostata a choice.
days

tipo: array predefinito: da 1 a 31

Lista di giorni disponibili per il tipo di campo day. Questa opzione rilevante solo quando lopzione widget
impostata a choice:
days => range(1, 31)

format

tipo: intero o stringa predefinito: IntlDateFormatter::MEDIUM

Opzione passata alla classe IntlDateFormatter, usata per trasformare il dato inserito dallutente nel formato
appropriato. Quest molto importante quando lopzione widget single_text, e definisce il modo in cui il
dato viene trasformato. Per impostazione predefinita, il formato determinato in base al locale dellutente; si pu
sovrascriverlo passando il formato come stringa.
Per maggiori informazioni sui formati validi, si veda sintassi del formato Date/Time. Per esempio, per rendere un
singolo campo testuale che si aspetta che lutente inserisca yyyy-MM-dd, usare le seguenti opzioni:
$builder->add(date_created, date, array(
widget => single_text,
format => yyyy-MM-dd,
));

pattern

tipo: stringa

Questa opzione rilevante solo se widget impostato a choice. Lo schema predefinito basato sullopzione
format e prova a combaciare con i caratteri M, d e y nello schema del formato. Se non viene trovata corrispondenza,
il valore predefinito la stringa {{ year }}-{{ month }}-{{ day }}. I possibili segnaposto di questa
opzione sono:
{{ year }}: Sostituito con year
{{ month }}: Sostituito con month
{{ day }}: Sostituito con day
data_timezone

tipo: stringa predefinito: fuso orario di sistema

Fuso orario in cui la data inserita memorizzata. Deve essere uno dei fusi orari supportati da PHP
5.1. Documenti di riferimento

491

Symfony2 documentation Documentation, Release 2

user_timezone

tipo: stringa predefinito: fuso orario di sistema

Fuso orario usato per mostrare la data allutente (e quindi anche la data inviata dallutente stesso). Deve essere
uno dei fusi orari supportati da PHP.
Tipo di campo checkbox
Crea un singolo input di tipo checkbox. Andrebbe sempre usato per un campo con un valore booleano: se il box
spuntato, il campo sar impostato a vero, altrimenti il campo sar impostato a falso.
Reso come
Opzioni
Opzioni ereditate

Tipo genitore
Classee

campo input text


value

required
label
read_only
error_bubbling

field
Symfony\Component\Form\Extension\Core\Type\Checkb

Esempio di utilizzo
$builder->add(public, checkbox, array(
label
=> Mostrare questa voce?,
required => false,
));

Opzioni del campo

value

tipo: mixed predefinito: 1

Il valore usato effettivamente per il checkbox. Non ha effetti sul valore impostato nel proprio oggetto.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

492

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo choice
Un campo multi-funzione, usato per consentire allutente di scegliere una o pi opzioni. Pu essere reso come tag
select, bottone radio o checkbox.
Per usare questo campo, bisogna specificare lopzione choice_list oppure lopzione choices.
Reso come
Opzioni

pu essere vari tag (vedere sotto)

Opzioni ereditate

Tipo genitore
Classe

choices
choice_list
multiple
expanded
preferred_choices
empty_value

required
label
read_only
error_bubbling

form (con expanded), altrimenti field


Symfony\Component\Form\Extension\Core\Type\Choice

Esempio di utilizzo

Il modo pi facile per usare questo campo specificare le scelte direttamente con lopzione choices. La chiave
dellarray diventa il valore effettivamente impostato nel proprio oggetto (p.e. m), mentre il valore quello che
lutente vede nel form (p.e. Maschio).
$builder->add(gender, choice, array(
choices
=> array(m => Maschio, f => Femmina),
required => false,
));

Impostando multiple a true, si consente allutente la scelta di pi valori. Il widget sar reso come un un tag
select con opzione mutliple oppure come una serie di checkbox, a seconda dellopzione expanded:
$builder->add(availability, choice, array(
choices
=> array(
morning
=> Mattina,
afternoon => Pomeriggio,
evening
=> Sera,
),
multiple => true,
));

Si pu anche usare lopzione choice_list, che accetta un oggetto che pu specificare le scelte per il widget.

5.1. Documenti di riferimento

493

Symfony2 documentation Documentation, Release 2

Tag select, checkbox o bottoni radio

Questo campo pu essere reso come uno tra diversi campi HTML, a seconda delle opzioni expanded e
multiple:
tipo di elemento
tag select
tag select (con attributo multiple)
bottoni radio
checkbox

expanded
false
false
true
true

multiple
false
true
false
true

Opzioni del campo

choices

tipo: array predefinito: array()

Questo il modo pi semplice per specificare le scelte da usare per questo campo. Lopzione choices un
array, in cui le chiavi sono il valore delloggetto e i valori sono letichetta:
$builder->add(gender, choice, array(
choices => array(m => Maschio, f => Femmina)
));

choice_list

tipo: Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface

Questo un modo per specificare le opzioni da usare per questo campo. Lopzione choice_list deve essere unistanza di ChoiceListInterface. Per classi avanzate, si pu creare una classe personalizzata che
implementi questa interfaccia e fornisca le scelte.
multiple

tipo: booleano predefinito: false

Se true, lutente potra selezionare pi opzioni (invece di sceglierne una sola). A seconda del valore dellopzione
expanded, sar reso o come un tag select o come dei checkbox, se true, e come un tag select o bottoni radio,
se false. Il valore restituito sar un array.
expanded

tipo: booleano predefinito: false

Se true, saranno resi dei bottoni radio o dei checkbox (a seconda del valore di multiple). Se false, sar
reso un elemento select.
preferred_choices

tipo: array predefinito: array()

Se questa opzione viene specificata, un sotto-insieme di tutte le opzioni sar spostato in cima al select. Il codice
seguente sposter lopzione Paperino in cima, con un separatore visuale tra essa e le opzioni restanti:
$builder->add(scelte_pippo, choice, array(
choices => array(pippo => Pippo, pluto => Pluto, paperino => Paperino),
preferred_choices => array(paperino),
));

Si noti che le scelte preferite hanno senso solo con la resa di un elemento select (cio se expanded false). Le
scelte preferite e le scelte normali sono separate visivamente da una serie di righe (-------------------).
Il separatore pu essere personalizzato durante la resa:
Twig
{{ form_widget(form.scelte_pippo, { separator: ===== }) }}

PHP

494

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

<?php echo $view[form]->widget($form[scelte_pippo], array(separator => =====)) ?>

empty_value

tipo: stringa o booleano

Questa opzione determina se apparir o meno una speciale opzione vuota (p.e. Scegliere unopzione) in cima
al select. Questa opzione si applica solamente se entrambe le opzioni expanded e multiple sono impstate a
false.
Aggiungere un valore vuoto con Scegliere unopzione come testo:
$builder->add(states, choice, array(
empty_value => Scegliere un\opzione,
));

Non mostrare alcun valore vuoto:


$builder->add(states, choice, array(
empty_value => false,
));

Se non si imposta lopzione empty_value, sar aggiunta automaticamente unopzione vuota (senza testo), ma
solo se lopzione required false:
// sar aggiunta unopzione vuota (senza testo)
$builder->add(states, choice, array(
required => false,
));

empty_data

tipo: mixed predefinito: array() se multiple o expanded, altrimenti

Questa opzione determina il valore restituito dal campo quando viene selezionato empty_value.
Per esempio, se si vuole che il campo gender sia impostato a null quando non viene scelto alcun valore, lo si
pu fare in questo modo:
$builder->add(gender, choice, array(
choices => array(
m => Maschio,
f => Femmina
),
required
=> false,
empty_value => Scegliere un genere,
empty_data => null
));

Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.

5.1. Documenti di riferimento

495

Symfony2 documentation Documentation, Release 2

label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo collection
Questo tipo di campo usato per rendere un insieme di campi o form. Nel senso pi semplice, potrebbe essere un
array di campi text che popolano un array di campi emails. In esempi pi complessi, si potrebbero includere
interi form, che utile quando si creano form che espongono relazioni molti-a-molti (p.e. un prodotto in cui si
possono gestire molte foto correlate).
Reso come
Opzioni

Opzioni ereditate

Tipo padre
Classe

dipende dallopzione type

type
options
allow_add
allow_delete
prototype

label
error_bubbling
by_reference

form
Symfony\Component\Form\Extension\Core\Type\Collec

Uso di base

Questo tipo usato quando si vuole gestire un insieme di elementi simili in un form. Per esempio, si supponga di
avere un campo emails, che corrisponde a un array di indirizzi email. Nel form, si vuole esporre ogni indirizzo
email con il proprio campo testuale:
$builder->add(emails, collection, array(
// ogni elemento nellarray sar un campo "email"
type
=> email,
// queste opzioni sono passate a ogni tipo "email"
options => array(
required => false,
attr
=> array(class => email-box)
),
));

Il modo pi semplice di renderlo tutto insieme:


Twig

496

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

{{ form_row(form.emails) }}

PHP
<?php echo $view[form]->row($form[ emails]) ?>

Un metodo molto pi flessibile sarebbe questo:


Twig
{{ form_label(form.emails) }}
{{ form_errors(form.emails) }}
<ul>
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}
</ul>

PHP
<?php echo $view[form]->label($form[emails]) ?>
<?php echo $view[form]->errors($form[emails]) ?>
<ul>
{% for emailField in form.emails %}
<?php foreach ($form[emails] as $emailField): ?>
<li>
<?php echo $view[form]->errors($emailField) ?>
<?php echo $view[form]->widget($emailField) ?>
</li>
<?php endforeach; ?>
</ul>

In entrambi i casi, non sarebbe reso alcun campo, a meno che larray emails non contenga almeno unemail.
In questo semplice esempio, non ancora possibile aggiungere nuovi indirizzi o rimuoverne di esistenti.
Laggiunta di nuovi indirizzi possibile tramite lopzione allow_add (e facoltativamente lopzione prototype)
(vedere esempio sotto). La rimozione di email possibile tramite lopzione allow_delete.
Aggiungere e rimuovere elementi Se allow_add true, se vengono inviati elementi non riconosciuti, saranno
aggiunti allarray di elementi. Questo in teoria buono, ma in pratica richiede un po di sforzi in pi per far
funzionare il JavaScript lato client.
Proseguendo con lesempio precedente, si supponga di partire con due email nellarray emails. In questo caso,
saranno resi due campi input, che assomiglieranno a questi (a seconda del nome del form):
<input type="email" id="form_emails_1" name="form[emails][0]" value="foo@foo.com" />
<input type="email" id="form_emails_1" name="form[emails][1]" value="bar@bar.com" />

Per consetnire laggiunta di altre email, impostare allow_add a true e, tramite JavaScript, rendere un altro campo
dal nome form[emails][2] (e cos via per ulteriori campi).
Per facilitare le cose, impostare lopzione prototype a true consente di rendere un campo template, utilizzabile
poi nel codice JavaScript per la creazione dinamica di questi nuovi campi. Un campo prototipo assomiglier a
questo:
<input type="email" id="form_emails_$$name$$" name="form[emails][$$name$$]" value="" />

Sostituendo $$name$$ con un valore unico (p.e. 2), si possono costruire e inserire nuovi campi HTML nel form.

5.1. Documenti di riferimento

497

Symfony2 documentation Documentation, Release 2

Usando jQuery, un semplic esempio assomiglierebbe a questo. Se si rendono i campi collection tutti insieme (p.e.
form_row(form.emails)), le cose si semplificano ulteriormente, perch lattributo data-prototype
viene reso automaticamente (con una piccola differenza, vedere la nota sotto) e tutto ci che occorre il JavaScript:
Twig
<form action="..." method="POST" {{ form_enctype(form) }}>
{# ... #}

{# memorizza il prototipo nellattributo data-prototype #}


<ul id="email-fields-list" data-prototype="{{ form_widget(form.emails.get(prototype)) |
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}
</ul>
<a href="#" id="add-another-email">Aggiungere email</a>
{# ... #}
</form>
<script type="text/javascript">
// tiene traccia di quanti campi email sono stati resi
var emailCount = {{ form.emails | length }};
jQuery(document).ready(function() {
jQuery(#add-another-email).click(function() {
var emailList = jQuery(#email-fields-list);
// prende il template prototipo
var newWidget = emailList.attr(data-prototype);
// sostiuisce "$$name$$" usato nellid e il nome del prototipo
// con un numero univoco per le email
// lattributo finale assomiglia a name="contact[emails][2]"
newWidget = newWidget.replace(/\$\$name\$\$/g, emailCount);
emailCount++;
// crea un nuovo elemento nella lista e lo aggiunge
var newLi = jQuery(<li></li>).html(newWidget);
newLi.appendTo(jQuery(#email-fields-list));
return false;
});
})
</script>

Tip: Se si rende tutto linsieme in una volta sola, il prototipo sar disponibile automaticamente nellattributo
data-prototype dellelemento (p.e. div o table) che circonda linsieme. Lunica differenza che la riga
del form viene resa automaticamente, quindi non occorre inserirla in un elemento contenitore, come stato fatto
in precedenza.

Opzioni del campo

type

tipo: stringa o Symfony\Component\Form\FormTypeInterface obbligatorio

Il tipo per ogni elemento dellinsieme (p.e. text, choice, ecc.). Per esempio, con un array di indirizzi email,
si userebbe il tipo email. Se si vuole includere un insieme di un qualche altro form, creare una nuova istanza del
tipo di form e passarlo in questa opzione.
498

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

options

tipo: array predefinito: array()

Larray passato al tipo di form specificato nellopzione type. Per esempio, se si usato il tipo choice come opzione
type (p.e. per un insieme di men a tendina), si dovrebbe passare almeno lopzione choices al tipo sottostante:
$builder->add(favorite_cities, collection, array(
type
=> choice,
options => array(
choices => array(
nashville => Nashville,
paris
=> Paris,
berlin
=> Berlin,
london
=> London,
),
),
));

allow_add

tipo: Booleano predefinito: false

Se true, ogni elemento non riconosciuto inviato allinsieme sar aggiunto come nuovo elemento. Larray finale
conterr gli elementi esistenti e il nuovo elemento, appena inviato. Si veda lesempio sopra per maggiori dettagli.
Si pu usare lopzione prototype per rendere un elemento prototipo, che pu essere usato, con JavaScript, per
creare dinamicamente nuovi elementi lato client. Per maggiori informazioni, vedere lesempio sopra e Permettere
nuovi todo con prototipo.
Caution: Se si includono altri form, per riflettere una relazione uno-a-molti nella base dati, potrebbe essere
necessario assicurarsi a mano che la chiave esterna di questi nuovi oggetti sia impostata correttamente. Se si
usa Doctrine, questo non avverr automaticamente. Vedere il collegamento sopra per maggiori dettagli.

allow_delete

tipo: Booleano predefinito: false

Se true, se un elemento esistente non compare tra i dati inviati, sar assente dallarray finale di elementi. Questo
vuol dire che si pu implementare un bottone cancella tramite JavaScript, che rimuove semplicemente un elemento del form dal DOM. Quando lutente invia il form, lassenza dai dati inviati implicher una rimozione
dallarray finale.
Per maggiori informazioni, vedere Permettere la rimozione di todo.
Caution: Si faccia attenzione nellusare questa opzione quando si include un insieme di oggetti. In questo
caso, se un form incluso viene rimosso, sar correttamente mancante dallarray finale di oggetti. Tuttavia, a
seconda della logica dellapplicazione, quando uno di questi oggetti viene rimosso, si potrebbe volerlo cancellare o almeno rimuovere la sua chiave esterna riferita alloggetto principale. Questi casi non sono gestiti
automaticamente. Per maggiori informazioni, vedere Permettere la rimozione di todo.

prototype

tipo: Booleano predefinito: true

Questa opzione utile quando si usa lopzione allow_add. Se true (e se anche allow_add true), sar disponibile uno speciale attributo prototype, in modo che si possa rendere nella pagina un template esempio di come
ogni nuovo elemento dovrebbe apparire. Lattributo name dato a tale elemento $$name$$. Questo consente
laggiunta di un bottone aggiungi tramite JavaScript, che legge il prototipo, sostituisce $$name$$ con un
nome o numero univoco e lo rende allinterno del form. Quando inviato, sar aggiunto allarray sottostante, grazie
allopzione allow_add.
Il campo prototipo pu essere reso tramite la variabile prototype nel campo collection:
Twig
{{ form_row(form.emails.get(prototype)) }}

5.1. Documenti di riferimento

499

Symfony2 documentation Documentation, Release 2

PHP
<?php echo $view[form]->row($form[emails]->get(prototype)) ?>

Si noti che tutto quello di cui si ha effettivamente bisogno il widget, ma a seconda di come si rende il form, avere
lintera riga del form potrebbe essere pi facile.
Tip: Se si rende lintero campo collection in una volta sola, la riga del prototipo sar disponibile automaticamente
nellattributo data-prototype dellelemento (p.e. div o table) che contiene linsieme.
Per dettagli su come usare effettivamente questa opzione, vedere lesempio sopra o Permettere nuovi todo con
prototipo.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field. Non sono elencate tutte le opzioni, solo quelle pi attinenti a questo
tipo:
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

error_bubbling

tipo: Booleano predefinito: true

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
by_reference

tipo: Booleano predefinito: true

Se il valore sottostante a un campo un oggetto e questa opzione true, loggetto risultante non sar effettivamente impostato al bind del form. Per esempio, se si ha un campo author nelloggetto sottostante, istanza
di una classe Author, allora se by_reference false, loggetto Author sar aggiornato con i dati sottostanti, ma setAuthor non sar effettivamente richiamata sulloggetto principale. Poich loggetto Author
un riferimento, questo fa veramente differenza solo se si ha nel metodo setAuthor una logica personalizzata,
che si vuol essere sicuri sia eseguita. In questo caso, impostare questa opzione a false.
Tipo di campo country
Il tipo country un sotto-insieme di ChoiceType, che mostra i paesi del mondo. Come bonus aggiuntivo, i
nomi dei paesi sono mostrati nella lingua dellutente.
Il valore di ogni paese un codice da due lettere.
Note: Il locale dellutente indovinato tramite Locale::getDefault()
Diversamente dal tipo choice, non occorre specificare lopzione choices o choice_list, poich il campo
usaa automaticamente tutti i paesi del mondo. Si pu specificare una di queste opzioni manulmente, ma allora si
dovrebbe usare il tipo choice direttamente.

500

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Reso come

possono essere varitag (vedere Tag select, checkbox o


bottoni radio)

Opzioni ereditate

Tipo genitore
Classe

multiple
expanded
preferred_choices
empty_value
error_bubbling
required
label
read_only

choice
Symfony\Component\Form\Extension\Core\Type\Countr

Opzioni ereditate

Queste opzioni sono ereditate dal tipo choice:


multiple

tipo: booleano predefinito: false

Se true, lutente potra selezionare pi opzioni (invece di sceglierne una sola). A seconda del valore dellopzione
expanded, sar reso o come un tag select o come dei checkbox, se true, e come un tag select o bottoni radio,
se false. Il valore restituito sar un array.
expanded

tipo: booleano predefinito: false

Se true, saranno resi dei bottoni radio o dei checkbox (a seconda del valore di multiple). Se false, sar
reso un elemento select.
preferred_choices

tipo: array predefinito: array()

Se questa opzione viene specificata, un sotto-insieme di tutte le opzioni sar spostato in cima al select. Il codice
seguente sposter lopzione Paperino in cima, con un separatore visuale tra essa e le opzioni restanti:
$builder->add(scelte_pippo, choice, array(
choices => array(pippo => Pippo, pluto => Pluto, paperino => Paperino),
preferred_choices => array(paperino),
));

Si noti che le scelte preferite hanno senso solo con la resa di un elemento select (cio se expanded false). Le
scelte preferite e le scelte normali sono separate visivamente da una serie di righe (-------------------).
Il separatore pu essere personalizzato durante la resa:
Twig
{{ form_widget(form.scelte_pippo, { separator: ===== }) }}

PHP
<?php echo $view[form]->widget($form[scelte_pippo], array(separator => =====)) ?>

empty_value

tipo: stringa o booleano

Questa opzione determina se apparir o meno una speciale opzione vuota (p.e. Scegliere unopzione) in cima
al select. Questa opzione si applica solamente se entrambe le opzioni expanded e multiple sono impstate a
false.
Aggiungere un valore vuoto con Scegliere unopzione come testo:

5.1. Documenti di riferimento

501

Symfony2 documentation Documentation, Release 2

$builder->add(states, choice, array(


empty_value => Scegliere un\opzione,
));

Non mostrare alcun valore vuoto:


$builder->add(states, choice, array(
empty_value => false,
));

Se non si imposta lopzione empty_value, sar aggiunta automaticamente unopzione vuota (senza testo), ma
solo se lopzione required false:
// sar aggiunta unopzione vuota (senza testo)
$builder->add(states, choice, array(
required => false,
));

error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Queste opzioni sono ereditate dal tipo field:
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
Tipo di campo
Il tipo csrf un campo nascosto che contiene un token per il CSRF.
Reso come
Opzioni

campo input hidden field

Tipo genitore
Classe

hidden
Symfony\Component\Form\Extension\Csrf\Type\CsrfTy

502

csrf_provider
page_id
property_path

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Opzioni del campo

csrf_provider

tipo: Symfony\Component\Form\CsrfProvider\CsrfProviderInterface

Loggetto CsrfProviderInterface che dovrebbe generare il token CSRF. Se non impostato, viene usato il
provider predefinito.
intention

tipo: stringa

Un identificatore univoco, facoltativo, usato per generare il token CSRF.


property_path

tipo: qualsiasi predefinito: il valore del campo

I campi mostrano il valore di una propriet delloggetto del dominio del form. Quando il form inviato, il valore
immesso scritto nelloggetto.
Se si vuole sovrascrivere la propriet che un campo legge e scrive, si pu impostare lopzione property_path.
Il suo valore predefinito il nome del campo.
Se si vuole che il campo sia ignorato in lettura o in scrittura delloggetto, si pu impostare lopzione
property_path a false
Tipo di campo date
Un campo che consente allutente di modificare le informazioni sulle date tramite una serie di diversi elementi
HTML.
I dati sottostanti usati per questo campo possono essere un oggetto DateTime, una stringa, un timestamp o un
array. Se lopzione input impostata correttamente, il campo si occuper di tutti i dettagli.
Il campo pu essere reso come una singola casella di testo, tre caselle di testo (mese, giorno e anno) oppure tre
select (vedere lopzione widget_).
Tipo di dato sottostante
Reso come
Opzioni

Tipo genitore
Classe

uno tra DateTime, stringa, timestamp o array


(vedere opzione input)
single text box or three select fields

widget
input
empty_value
years
months
days
format
pattern
data_timezone
user_timezone

field (se testo), form altrimenti


Symfony\Component\Form\Extension\Core\Type\DateTy

Uso di base

Questo tipo di campo altamente configurabile, ma facile da usare. Le opzioni pi importanti sono input e
widget.
Si supponga di avere un campo publishedAt, la cui data sottostante sia un oggetto DateTime. Il codice
seguente configura il tipo date per tale campo come tre diversi campi di scelta:

5.1. Documenti di riferimento

503

Symfony2 documentation Documentation, Release 2

$builder->add(publishedAt, date, array(


input => datetime,
widget => choice,
));

Lopzione input deve essere cambiata per corrispondere al tipo di dato della data sottostante. Per esempio, se i
dati del campo publishedAt fossero un timestamp, si dovrebbe impostare input a timestamp:
$builder->add(publishedAt, date, array(
input => timestamp,
widget => choice,
));

Il campo supporta anche array e string come valori validi dellopzione input.
Opzioni del campo

widget

tipo: stringa predefinito: choice

Il modo di base in cui questo campo andrebbe reso. Pu essere uno tra:
choice: rende tre campi select. Lordine dei select definito nellopzione pattern.
text: rende tre campi testuali (mese, giorno, anno).
single_text: rende un singolo campo testuale. I dati inseriti dallutente sono validati in base allopzione
format.
input

tipo: stringa predefinito: datetime

Il formato in ingresso dei dati, cio il formato in cui la data memorizzata nelloggetto sottostante. Valori validi
sono:
stringa (p.e. 2011-06-05)
datetime (un oggetto DateTime)
array (p.e. array(year => 2011, month => 06, day => 05))
timestamp (p.e. 1307232000)
Anche il valore proveniente dal form sar normalizzato in tale formato.
empty_value

tipo: stringa|array

Se lopzione del widget choice, il campo sar rappresentato come una serie di select.
empty_value pu essere usata per aggiungere una voce vuota in cima a ogni select:

Lopzione

$builder->add(dueDate, date, array(


empty_value => ,
));

In alternativa, si pu specificare una stringa da mostrare per ogni voce vuota:


$builder->add(dueDate, date, array(
empty_value => array(year => Anno, month => Mese, day => Giorno)
));

years

tipo: array predefinito: da 5 anni prima a 5 dopo lanno corrente

Lista di ore disponibili per il tipo di campo year. Questa opzione rilevante solo se lopzione widget
impostata a choice.

504

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

months

tipo: array predefinito: da 1 a 12

Lista di ore disponibili per il tipo di campo month. Questa opzione rilevante solo se lopzione widget
impostata a choice.
days

tipo: array predefinito: da 1 a 31

Lista di giorni disponibili per il tipo di campo day. Questa opzione rilevante solo quando lopzione widget
impostata a choice:
days => range(1, 31)

format

tipo: intero o stringa predefinito: IntlDateFormatter::MEDIUM

Opzione passata alla classe IntlDateFormatter, usata per trasformare il dato inserito dallutente nel formato
appropriato. Quest molto importante quando lopzione widget single_text, e definisce il modo in cui il
dato viene trasformato. Per impostazione predefinita, il formato determinato in base al locale dellutente; si pu
sovrascriverlo passando il formato come stringa.
Per maggiori informazioni sui formati validi, si veda sintassi del formato Date/Time. Per esempio, per rendere un
singolo campo testuale che si aspetta che lutente inserisca yyyy-MM-dd, usare le seguenti opzioni:
$builder->add(date_created, date, array(
widget => single_text,
format => yyyy-MM-dd,
));

pattern

tipo: stringa

Questa opzione rilevante solo se widget impostato a choice. Lo schema predefinito basato sullopzione
format e prova a combaciare con i caratteri M, d e y nello schema del formato. Se non viene trovata corrispondenza,
il valore predefinito la stringa {{ year }}-{{ month }}-{{ day }}. I possibili segnaposto di questa
opzione sono:
{{ year }}: Sostituito con year
{{ month }}: Sostituito con month
{{ day }}: Sostituito con day
data_timezone

tipo: stringa predefinito: fuso orario di sistema

Fuso orario in cui la data inserita memorizzata. Deve essere uno dei fusi orari supportati da PHP
user_timezone

tipo: stringa predefinito: fuso orario di sistema

Fuso orario usato per mostrare la data allutente (e quindi anche la data inviata dallutente stesso). Deve essere
uno dei fusi orari supportati da PHP.
Tipo di campo datetime
Questo tipo di campo consente allutente di modicare dati che rappresentano una data e unora (p.e. 1984-06-05
12:15:30).
Pu essere reso come una casella di testo o con tag select. Il formato sottostante dei dati pu essere un oggetto
DateTime, una stringa, un timestamp o un array.

5.1. Documenti di riferimento

505

Symfony2 documentation Documentation, Release 2

Tipo di dato sottostante


Reso come
Opzioni

uno tra DateTime, stringa, timestamp o array


(vedere opzione input)
casella di testo o tre select

Tipo genitore
Classe

date_widget
time_widget
input
date_format
hours
minutes
seconds
years
months
days
with_seconds
data_timezone
user_timezone

form
Symfony\Component\Form\Extension\Core\Type\DateTi

Opzioni del campo

date_widget

tipo: stringa predefinito: choice

Definisce lopzione widget per il tipo date


time_widget

tipo: stringa predefinito: choice

Definisce lopzione widget per il tipo time


input

tipo: stringa predefinito: datetime

IL formato dei dati di input, cio il formato in cui la data memorizzata nelloggetto sottostante. Valori validi
sono:
string (p.e. 2011-06-05 12:15:00)
datetime (un oggetto DateTime)
array (p.e. array(2011, 06, 05, 12, 15, 0))
timestamp (p.e. 1307276100)
Il valore che arriva dal form sar anche normalizzato in questo formato.
date_format

tipo: intero o stringa predefinito: IntlDateFormatter::MEDIUM

Definisce lopzione format che sar passata al campo date.


hours

tipo: intero predefinito: da 1 a 23

Lista di ore disponibili per il tipo di campo hours. Questa opzione rilevante solo se lopzione widget
impostata a choice.
minutes

tipo: intero predefinito: da 1 a 59

Lista di minuti disponibili per il tipo di campo minutes. Questa opzione rilevante solo se lopzione widget
impostata a choice.

506

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

seconds

tipo: intero predefinito: da 1 a 59

Lista di secondi disponibili per il tipo di campo seconds. Questa opzione rilevante solo se lopzione widget
impostata a choice.
years

tipo: array predefinito: da 5 anni prima a 5 dopo lanno corrente

Lista di ore disponibili per il tipo di campo year. Questa opzione rilevante solo se lopzione widget
impostata a choice.
months

tipo: array predefinito: da 1 a 12

Lista di ore disponibili per il tipo di campo month. Questa opzione rilevante solo se lopzione widget
impostata a choice.
days

tipo: array predefinito: da 1 a 31

Lista di giorni disponibili per il tipo di campo day. Questa opzione rilevante solo quando lopzione widget
impostata a choice:
days => range(1, 31)

with_seconds

tipo: booleano predefinito: false

Se includere o meno i secondi nellinput. Se true, ci sar un campo aggiuntivo per inserire i secondi.
data_timezone

tipo: stringa predefinito: fuso orario di sistema

Fuso orario in cui la data inserita memorizzata. Deve essere uno dei fusi orari supportati da PHP
user_timezone

tipo: stringa predefinito: fuso orario di sistema

Fuso orario usato per mostrare la data allutente (e quindi anche la data inviata dallutente stesso). Deve essere
uno dei fusi orari supportati da PHP.
Tipo di campo email
Il campo email un campo di testo reso usando il tag <input type="email" /> di HTML5.
Reso come
Opzioni ereditate

campo input email (casella di testo)

Tipo genitore
Classe

field
Symfony\Component\Form\Extension\Core\Type\EmailT

max_length
required
label
trim
read_only
error_bubbling

Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:

5.1. Documenti di riferimento

507

Symfony2 documentation Documentation, Release 2

max_length

tipo: intero

Questa opzione aggiunge un attributo max_length, usato da alcuni browser per limitare la lunghezza del testo
in un campo.
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

trim

tipo: booleano predefinito: true

Se true, gli spazi vuoti della stringa inviata saranno eliminati, tramite la funzione trim(). Questo garantisce
che se un valore viene inserito con spazi vuoti superflui, questi vengano rimossi prima che il valore sia inserito
nelloggetto sottostante.
read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo entity
Un campo speciale choice pensato per caricare opzioni da unentit Doctrine. Per esempio, se si ha unentit
Category, si pu usare questo campo per mostrare un campo select di tutti, o di alcuni, oggetti Category
presi dal database.

508

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Reso come

possono essere vari tag (vedere Tag select, checkbox o


bottoni radio)

Opzioni

Opzioni ereditate

Tipo genitore
Classe

class
property
query_builder
em

required
label
multiple
expanded
preferred_choices
empty_value
read_only
error_bubbling

choice
Symfony\Bridge\Doctrine\Form\Type\EntityType

Utilizzo di base

Il tipo entity ha una sola opzione obbligatoria: lentit da elencare allinterno del campo choice:
$builder->add(users, entity, array(
class => AcmeHelloBundle:User,
));

In questo caso, tutti gli oggetti User saranno caricati dal database e resti come un tag select, dei radio o una
serie di checkbox (a seconda dei valori di multiple ed expanded).
Usare una query personalizzata per le entit Se occorre specificare una query personalizzata da usare per
recuperare le entit (p.e.e si vogliono solo alcune entit o le si vuole ordinare), usare lopzione query_builder.
Il modo pi facile fare come segue:
use Doctrine\ORM\EntityRepository;
// ...
$builder->add(users, entity, array(
class => AcmeHelloBundle:User,
query_builder => function(EntityRepository $er) {
return $er->createQueryBuilder(u)
->orderBy(u.username, ASC);
},
));

Tag select, checkbox o bottoni radio

Questo campo pu essere reso come uno tra diversi campi HTML, a seconda delle opzioni expanded e
multiple:
tipo di elemento
tag select
tag select (con attributo multiple)
bottoni radio
checkbox

5.1. Documenti di riferimento

expanded
false
false
true
true

multiple
false
true
false
true

509

Symfony2 documentation Documentation, Release 2

Opzioni del campo

class

tipo: stringa required

La classe dellentit (p.e. AcmeStoreBundle:Category). Pu essere un nome di classe pienamente qualificato (p.e. Acme\StoreBundle\Entity\Category) o il suo alias (come mostrato sopra).
property

tipo: stringa

La propriet da usare per mostrare le entit come testi nellelemento HTML. Se lasciata vuota, gli oggetti saranno
formattati come stringhe, quindi occorre avere un metodo __toString().
query_builder

tipo: Doctrine\ORM\QueryBuilder oppure una closure

Se specificato, usato per cercare un sotto-insieme di opzioni (e il loro ordina) che dovrebbero essere usate per
il campo. Il valore di questa opzione pu essere un oggetto QueryBuilder oppure una closure. Se su usa una
closure, dovrebbe accettare un singolo parametro, che lEntityRepository dellentit.
em

tipo: stringa predefinito: lentity manager predefinito

Se specificato, lentity manager da usare per caricare le scelte, al posto di quello predefinito.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo choice:


multiple

tipo: booleano predefinito: false

Se true, lutente potra selezionare pi opzioni (invece di sceglierne una sola). A seconda del valore dellopzione
expanded, sar reso o come un tag select o come dei checkbox, se true, e come un tag select o bottoni radio,
se false. Il valore restituito sar un array.
expanded

tipo: booleano predefinito: false

Se true, saranno resi dei bottoni radio o dei checkbox (a seconda del valore di multiple). Se false, sar
reso un elemento select.
preferred_choices

tipo: array predefinito: array()

Se questa opzione viene specificata, un sotto-insieme di tutte le opzioni sar spostato in cima al select. Il codice
seguente sposter lopzione Paperino in cima, con un separatore visuale tra essa e le opzioni restanti:
$builder->add(scelte_pippo, choice, array(
choices => array(pippo => Pippo, pluto => Pluto, paperino => Paperino),
preferred_choices => array(paperino),
));

Si noti che le scelte preferite hanno senso solo con la resa di un elemento select (cio se expanded false). Le
scelte preferite e le scelte normali sono separate visivamente da una serie di righe (-------------------).
Il separatore pu essere personalizzato durante la resa:
Twig
{{ form_widget(form.scelte_pippo, { separator: ===== }) }}

PHP

510

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

<?php echo $view[form]->widget($form[scelte_pippo], array(separator => =====)) ?>

empty_value

tipo: stringa o booleano

Questa opzione determina se apparir o meno una speciale opzione vuota (p.e. Scegliere unopzione) in cima
al select. Questa opzione si applica solamente se entrambe le opzioni expanded e multiple sono impstate a
false.
Aggiungere un valore vuoto con Scegliere unopzione come testo:
$builder->add(states, choice, array(
empty_value => Scegliere un\opzione,
));

Non mostrare alcun valore vuoto:


$builder->add(states, choice, array(
empty_value => false,
));

Se non si imposta lopzione empty_value, sar aggiunta automaticamente unopzione vuota (senza testo), ma
solo se lopzione required false:
// sar aggiunta unopzione vuota (senza testo)
$builder->add(states, choice, array(
required => false,
));

Queste opzioni sono ereditate dal tipo field:


required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.

5.1. Documenti di riferimento

511

Symfony2 documentation Documentation, Release 2

Tipo di campo file


Il tipo file rappresenta un input per caricare file.
Reso come
Opzioni ereditate

campo input file

Tipo genitore
Classe

form
Symfony\Component\Form\Extension\Core\Type\FileTy

required
label
read_only
error_bubbling

Utilizzo di base

Si supponga di avere in un form:


$builder->add(attachment, file);

Caution: Non dimenticare di aggiungere lattributo enctype nel tag form: <form action="#"
method="post" {{ form_enctype(form) }}>.
Quando
il
form
viene
inviato,
il
campo
attachment
sar
unistanza
di
Symfony\Component\HttpFoundation\File\UploadedFile. Pu essere usata per spotare il
file attachment in una posizione permanente:
use Symfony\Component\HttpFoundation\File\UploadedFile;
public function uploadAction()
{
// ...
if ($form->isValid()) {
$someNewFilename = ...
$form[attachment]->getData()->move($dir, $someNewFilename);
// ...
}
// ...
}

Il metodo move() accetta come parametri una cartella e un nome di file. Si pu calcolare il nome del file in uno
dei modi seguenti:
// usare il nome del file originale
$file->move($dir, $file->getClientOriginalName());
// calcolare un nome casuale e provare a indovinare lestensione (pi sicuro)
$extension = $file->guessExtension();
if (!$extension) {
// lestensione non pu essere indovinata
$extension = bin;
}
$file->move($dir, rand(1, 99999)...$extension);

Luso del nome originale, tramite getClientOriginalName(), non sicuro, perch potrebbe essere stato
manipolato dallutente. Inoltre, pu contenere caratteri che non sono consentiti nei nomi di file. Si dovrebbe
ripulire il nome prima di utilizzarlo direttamente.

512

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Leggere il ricettario per un esempio di come gestire un caricamento di file associato con unentit Doctrine.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Il tipo astratto field
Il tipo field non un vero tipo di campo da poter usare, ma funge da tipo di campo genitore per molti altri
campi.
Il tipo field ha alcune opzioni predefinite:
data

tipo: mixed predefinito: Predefinito al campo delloggetto sottostante (se presente)


Quando si crea un form, ogni campo inizialmente mostra il valore della propriet corrispondente delloggetto del
dominio del form (se un oggetto legato al form). Se si vuole sovrascrivere il valore iniziale per il form o solo per
un singolo campo, lo si pu fare con lopzione data:
$builder->add(token, hidden, array(
data => abcdef,
));

required

tipo: booleano predefinito: true


Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.

5.1. Documenti di riferimento

513

Symfony2 documentation Documentation, Release 2

Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
disabled [tipo: booleano, predefinito: false] Se non si vuole che lutente modifichi il valore di un campo,
si pu impostare questa opzione a true. Ogni valore inserito sar ignorato.
use Symfony\Component\Form\TextField
$field = new TextField(status, array(
data => Vecchi dati,
disabled => true,
));
$field->submit(Nuovi dati);
// mostra "Vecchi dati"
echo $field->getData();

trim

tipo: booleano predefinito: true


Se true, gli spazi vuoti della stringa inviata saranno eliminati, tramite la funzione trim(). Questo garantisce
che se un valore viene inserito con spazi vuoti superflui, questi vengano rimossi prima che il valore sia inserito
nelloggetto sottostante.
property_path

tipo: qualsiasi predefinito: il valore del campo


I campi mostrano il valore di una propriet delloggetto del dominio del form. Quando il form inviato, il valore
immesso scritto nelloggetto.
Se si vuole sovrascrivere la propriet che un campo legge e scrive, si pu impostare lopzione property_path.
Il suo valore predefinito il nome del campo.
Se si vuole che il campo sia ignorato in lettura o in scrittura delloggetto, si pu impostare lopzione
property_path a false
attr

tipo: array predefinito: array vuoto


Se si vogliono aggiungere attributi extra a un campo HTML, si pu usare lopzione attr. un array associativo
con attributi HTML come chiavi. Pu essere utile quando occorre impostare una classe personalizzata per un
widget:
$builder->add(body, textarea, array(
attr => array(class => tinymce),
));

translation_domain

tipo: stringa predefinito: messages


Il dominio di traduzione che sar usato per qualsiasi label o option rese per questo campo.
Tipo di campo form
Vedere Symfony\Component\Form\Extension\Core\Type\FormType.
514

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Tipo di campo hidden


Il tipo hidden rappresenta un campo input nascosto.
Reso come
Opzioni ereditate

campo input hidden

Tipo genitore
Classe

field
Symfony\Component\Form\Extension\Core\Type\Hidden

data
property_path

Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


data

tipo: mixed predefinito: Predefinito al campo delloggetto sottostante (se presente)

Quando si crea un form, ogni campo inizialmente mostra il valore della propriet corrispondente delloggetto del
dominio del form (se un oggetto legato al form). Se si vuole sovrascrivere il valore iniziale per il form o solo per
un singolo campo, lo si pu fare con lopzione data:
$builder->add(token, hidden, array(
data => abcdef,
));

property_path

tipo: qualsiasi predefinito: il valore del campo

I campi mostrano il valore di una propriet delloggetto del dominio del form. Quando il form inviato, il valore
immesso scritto nelloggetto.
Se si vuole sovrascrivere la propriet che un campo legge e scrive, si pu impostare lopzione property_path.
Il suo valore predefinito il nome del campo.
Se si vuole che il campo sia ignorato in lettura o in scrittura delloggetto, si pu impostare lopzione
property_path a false
Tipo di campo integer
Rende un campo input per un numero. Di base, un campo testo che va bene per gestire dati che siano in forma
di interi. Il campo number appare come una casella di testo, ma con alcune funzionalit aggiuntive, a patto che
il browser dellutente supporti HTML5.
Questo campo ha diverse opzioni su come gestire i valori ricevuti che non siano interi. Per impostazione predefinita, tutti i valori non interi (p.e. 6.78) saranno arrotondati per difetto (p.e. 6)
Reso come
Opzioni

Opzioni ereditate

Tipo genitore
Classe

5.1. Documenti di riferimento

campo input text


rounding_mode
grouping

required
label
read_only
error_bubbling

field
Symfony\Component\Form\Extension\Core\Type\Intege

515

Symfony2 documentation Documentation, Release 2

Opzioni del campo

rounding_mode

tipo: intero predefinito: IntegerToLocalizedStringTransformer::ROUND_DOWN

Per impostazione predefinita, se lutente inserisce un numero non intero, sar arrotondato
per difetto.
Ci sono molti altri metodi di arrotondamento e ognuno una costante di
Symfony\Component\Form\Extension\Core\DataTransformer\IntegerToLocalizedStringTransforme
IntegerToLocalizedStringTransformer::ROUND_DOWN Arrotondamento verso lo zero.
IntegerToLocalizedStringTransformer::ROUND_FLOOR Arrotondamento verso meno infinito.
IntegerToLocalizedStringTransformer::ROUND_UP Arrotondamento verso lalto.
IntegerToLocalizedStringTransformer::ROUND_CEILING Arrotondamento verso infinito.
grouping

tipo: intero predefinito: false

Questo valore usato intermante come valore NumberFormatter::GROUPING_USED quando si usa la classe
NumberFormatter. Non documentato, ma pare che se lo si imposta a true, i numeri saranno raggruppati
con una virgola o un punto (a seconda del locale): 12345.123 sarebbe mostrato come 12,345.123.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo language
Il tipo language un sotto-insieme di ChoiceType, che consente allutente di scegliere da un lungo elenco
di lingue. Come bonus aggiuntivo, i nomi delle lingue sono mostrati nella lingua dellutente.
Il valore per ogni locale il codice della lingua a due lettere ISO639-1 (p.e. it).

516

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Note: Il locale degli utenti indovinato tramite Locale::getDefault()


Diversamente dal tipo choice, non occorre specificare lopzione choices o choice_list, perch il tipo di
campo usa automaticamente la lista delle lingue. Si pu specificare una di queste opzioni manualmente, ma allora
si dovrebbe usare il tipo choice direttamente.
Reso come

possono essere diversi tag (vedere Tag select, checkbox o bottoni radio)

Opzioni ereditate

Tipo genitore
Classe

multiple
expanded
preferred_choices
empty_value
error_bubbling
required
label
read_only

choice
Symfony\Component\Form\Extension\Core\Type\Langua

Opzioni ereditate

Queste opzioni sono ereditate dal tipo choice:


multiple

tipo: booleano predefinito: false

Se true, lutente potra selezionare pi opzioni (invece di sceglierne una sola). A seconda del valore dellopzione
expanded, sar reso o come un tag select o come dei checkbox, se true, e come un tag select o bottoni radio,
se false. Il valore restituito sar un array.
expanded

tipo: booleano predefinito: false

Se true, saranno resi dei bottoni radio o dei checkbox (a seconda del valore di multiple). Se false, sar
reso un elemento select.
preferred_choices

tipo: array predefinito: array()

Se questa opzione viene specificata, un sotto-insieme di tutte le opzioni sar spostato in cima al select. Il codice
seguente sposter lopzione Paperino in cima, con un separatore visuale tra essa e le opzioni restanti:
$builder->add(scelte_pippo, choice, array(
choices => array(pippo => Pippo, pluto => Pluto, paperino => Paperino),
preferred_choices => array(paperino),
));

Si noti che le scelte preferite hanno senso solo con la resa di un elemento select (cio se expanded false). Le
scelte preferite e le scelte normali sono separate visivamente da una serie di righe (-------------------).
Il separatore pu essere personalizzato durante la resa:
Twig
{{ form_widget(form.scelte_pippo, { separator: ===== }) }}

PHP
<?php echo $view[form]->widget($form[scelte_pippo], array(separator => =====)) ?>

5.1. Documenti di riferimento

517

Symfony2 documentation Documentation, Release 2

empty_value

tipo: stringa o booleano

Questa opzione determina se apparir o meno una speciale opzione vuota (p.e. Scegliere unopzione) in cima
al select. Questa opzione si applica solamente se entrambe le opzioni expanded e multiple sono impstate a
false.
Aggiungere un valore vuoto con Scegliere unopzione come testo:
$builder->add(states, choice, array(
empty_value => Scegliere un\opzione,
));

Non mostrare alcun valore vuoto:


$builder->add(states, choice, array(
empty_value => false,
));

Se non si imposta lopzione empty_value, sar aggiunta automaticamente unopzione vuota (senza testo), ma
solo se lopzione required false:
// sar aggiunta unopzione vuota (senza testo)
$builder->add(states, choice, array(
required => false,
));

error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Queste opzioni sono ereditate dal tipo field:
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
Tipo di campo locale
Il tipo locale un sotto-insieme di ChoiceType, che consente allutente di scegliere tra una lunga lista di
locale (lingua + paese). Come bonus aggiuntivo, i nomi dei locale sono mostrati nella lingua dellutente.
Il valore di ogni locale un codice di lingua a due lettere ISO639-1 (p.e. it) oppure il codice della lingua seguito
da un trattino basso (_) e dal codice del paese ISO3166 (p.e. it_IT per Italiano/Italia).

518

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Note: Il locale dellutente indovinato tramite Locale::getDefault()


Diversamente dal tipo choice, non occorre specificare lopzione choices o choice_list, perch il tipo di
campo usa automaticamente la lista dei locale. Si pu specificare una di queste opzioni manualmente, ma allora
si dovrebbe usare il tipo choice direttamente.
Reso come

possono essere diversi tag (vedere Tag select, checkbox o bottoni radio)

Opzioni ereditate

Tipo genitore
Classe

multiple
expanded
preferred_choices
empty_value
error_bubbling
required
label
read_only

choice
Symfony\Component\Form\Extension\Core\Type\Langua

Opzioni ereditate

Queste opzioni sono ereditate dal tipo choice:


multiple

tipo: booleano predefinito: false

Se true, lutente potra selezionare pi opzioni (invece di sceglierne una sola). A seconda del valore dellopzione
expanded, sar reso o come un tag select o come dei checkbox, se true, e come un tag select o bottoni radio,
se false. Il valore restituito sar un array.
expanded

tipo: booleano predefinito: false

Se true, saranno resi dei bottoni radio o dei checkbox (a seconda del valore di multiple). Se false, sar
reso un elemento select.
preferred_choices

tipo: array predefinito: array()

Se questa opzione viene specificata, un sotto-insieme di tutte le opzioni sar spostato in cima al select. Il codice
seguente sposter lopzione Paperino in cima, con un separatore visuale tra essa e le opzioni restanti:
$builder->add(scelte_pippo, choice, array(
choices => array(pippo => Pippo, pluto => Pluto, paperino => Paperino),
preferred_choices => array(paperino),
));

Si noti che le scelte preferite hanno senso solo con la resa di un elemento select (cio se expanded false). Le
scelte preferite e le scelte normali sono separate visivamente da una serie di righe (-------------------).
Il separatore pu essere personalizzato durante la resa:
Twig
{{ form_widget(form.scelte_pippo, { separator: ===== }) }}

PHP
<?php echo $view[form]->widget($form[scelte_pippo], array(separator => =====)) ?>

5.1. Documenti di riferimento

519

Symfony2 documentation Documentation, Release 2

empty_value

tipo: stringa o booleano

Questa opzione determina se apparir o meno una speciale opzione vuota (p.e. Scegliere unopzione) in cima
al select. Questa opzione si applica solamente se entrambe le opzioni expanded e multiple sono impstate a
false.
Aggiungere un valore vuoto con Scegliere unopzione come testo:
$builder->add(states, choice, array(
empty_value => Scegliere un\opzione,
));

Non mostrare alcun valore vuoto:


$builder->add(states, choice, array(
empty_value => false,
));

Se non si imposta lopzione empty_value, sar aggiunta automaticamente unopzione vuota (senza testo), ma
solo se lopzione required false:
// sar aggiunta unopzione vuota (senza testo)
$builder->add(states, choice, array(
required => false,
));

error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Queste opzioni sono ereditate dal tipo field:
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
Tipo di campo money
Rende un campo di testo specializzato nella gestione di dati di valuta (money).
Questo tipo di campo consente di specificare una valuta, il cui simbolo viene reso accanto al campo testuale. Ci
sono diverse altre opzioni per personalizzare la gestione dellinput e delloutput dei dati.

520

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Reso come
Opzioni

Opzioni ereditate

Tipo genitore
Classe

campo input text

currency
divisor
precision
grouping

required
label
read_only
error_bubbling

field
Symfony\Component\Form\Extension\Core\Type\MoneyT

Opzioni del campo

currency

tipo: stringa predefinito: EUR

Specifica la valuta. Ci determina il simbolo di valuta che dovrebbe essere mostrato accanto alla casella di testo.
A seconda della valuta, il simbolo potrebbe essere mostrato prima o dopo la casella di testo.
Pu anche essere impostato a false, per nascondere il simbolo della valuta.
divisor

tipo: intero predefinito: 1

Se, per qualche ragione, occorre dividere il valore di partenza per un numero, prima di renderlo allutente, si pu
usare lopzione divisor. Per esempio:
$builder->add(price, money, array(
divisor => 100,
));

In questo caso, se il campo price impostato a 9900, allora il valore reso allutente sar 99. Quando lutente
invia il valore 99, sar moltiplicato per 100 e 9900 sar infine inviato al proprio oggetto.
precision

tipo: intero predefinito: 2

Se, per qualche ragione, occore una precisione diversa da due cifre decimali, si pu modificare questo valore.
Probabilmente non se ne avr bisogno, a meno che, per esempio, non si voglia arrotondare allunit pi vicina
(impostare in questo caso a 0).
grouping

tipo: intero predefinito: false

Questo valore usato intermante come valore NumberFormatter::GROUPING_USED quando si usa la classe
NumberFormatter. Non documentato, ma pare che se lo si imposta a true, i numeri saranno raggruppati
con una virgola o un punto (a seconda del locale): 12345.123 sarebbe mostrato come 12,345.123.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.

5.1. Documenti di riferimento

521

Symfony2 documentation Documentation, Release 2

Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo number
Rende un campo testuale specializzato nella gestione di numeri. Questo tipo offre diverse opzioni per la precisione,
larrotondamento, il raggruppamento da usare per i numeri.
Reso come
Opzioni

campo input text


rounding_mode
precision
grouping

Opzioni ereditate

Tipo genitore
Classe

required
label
read_only
error_bubbling

field
Symfony\Component\Form\Extension\Core\Type\Number

Opzioni del campo

precision

tipo: intero predefinito: dipende dal locale (solitamente 3)

Specifica quanti decimali saranno consentiti prima che il campo arrotondi il valore inviato (tramite
rounding_mode). Per esempio, se precision impostato a 2, un valore inviato di 20.123 sar arrotondato, per esempio, a 20.12 (a seconda dellopzione rounding_mode).
rounding_mode

tipo: intero predefinito: IntegerToLocalizedStringTransformer::ROUND_DOWN

Se un numero inviato ha bisogno di essere arrotondato (in base allopzione precision), si


dispone di varie opzioni configurabili per tale arrotondamento.
Ogni opzione una costante di
Symfony\Component\Form\Extension\Core\DataTransformer\IntegerToLocalizedStringTransforme
IntegerToLocalizedStringTransformer::ROUND_DOWN Arrotondamento verso lo zero.
IntegerToLocalizedStringTransformer::ROUND_FLOOR Arrotondamento verso meno infinito.

522

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

IntegerToLocalizedStringTransformer::ROUND_UP Arrotondamento verso lalto.


IntegerToLocalizedStringTransformer::ROUND_CEILING Arrotondamento verso infinito.
IntegerToLocalizedStringTransformer::ROUND_HALFDOWN Arrotondamento verso il numero pi vicino, a meno che entrambi i numeri pi vicini siano equidistanti, nel qual caso arrotonda verso
il basso.
IntegerToLocalizedStringTransformer::ROUND_HALFEVEN Arrotondamento verso il numero pi vicino, a meno che entrambi i numeri pi vicini siano equidistanti, nel qual caso arrotonda verso
numero pari pi vicino.
IntegerToLocalizedStringTransformer::ROUND_HALFUP Arrotondamento verso il numero
pi vicino, a meno che entrambi i numeri pi vicini siano equidistanti, nel qual caso arrotonda verso lalto.
grouping

tipo: intero predefinito: false

Questo valore usato intermante come valore NumberFormatter::GROUPING_USED quando si usa la classe
NumberFormatter. Non documentato, ma pare che se lo si imposta a true, i numeri saranno raggruppati
con una virgola o un punto (a seconda del locale): 12345.123 sarebbe mostrato come 12,345.123.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo password
Il campo password rende una casella di testo per una password.

5.1. Documenti di riferimento

523

Symfony2 documentation Documentation, Release 2

Reso come
Opzioni

campo input password


always_empty

Opzioni ereditate

Tipo genitore
Classe

max_length
required
label
trim
read_only
error_bubbling

text
Symfony\Component\Form\Extension\Core\Type\Passwo

Opzioni del campo

always_empty

tipo: booleano predefinito: true

Se impostato a true, il campo sar reso sempre vuoto, anche se il campo corrispondente ha un valore. Quando
impostato a false, il campo password sar reso con lattributo value impostate alleffettivo valore.
In parole povere, se per qualche ragione si vuole rendere il campo password con la password gi inserita nel
campo, impostare questa opzione a false.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


max_length

tipo: intero

Questa opzione aggiunge un attributo max_length, usato da alcuni browser per limitare la lunghezza del testo
in un campo.
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

trim

tipo: booleano predefinito: true

Se true, gli spazi vuoti della stringa inviata saranno eliminati, tramite la funzione trim(). Questo garantisce
che se un valore viene inserito con spazi vuoti superflui, questi vengano rimossi prima che il valore sia inserito
nelloggetto sottostante.

524

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo percent
Il tipo percent rende un campo testuale specializzato nella gestione di dati percentuali. Se i dati percentuali
sono memorizzati come decimali (p.e. .95), si pu usare questo campo senza modifiche. Se si memorizzano i
dati come numeri (p.e. 95), bisogna impostare lopzione type a integer.
Questo campo aggiunge un simbolo di percentuale, %, dopo linput.
Reso come
Opzioni

Opzioni ereditate

Tipo genitore
Classe

campo input text


type
precision

required
label
read_only
error_bubbling

field
Symfony\Component\Form\Extension\Core\Type\Percen

Opzioni

type

tipo: stringa predefinito: fractional

Controlla come i dati sono memorizzati nel proprio oggetto. Per esempio, una percentuale di 55% potrebbe
essere memorizzata come .55 o 55 nel proprio oggetto. I due tipi gestiscono questi due casi:
fractional Se i dati sono memorizzati come decimale (p.e. .55), usare questo tipo. I dati saranno
moltiplicati per 100 prima di essere mostrati allutente (p.e. 55). I dati inviati saranno divisi per 100 dopo
linvio del form, in modo che il valore sia memorizzato come decimale (.55);
integer Se i dati sono memorizzati come intero (p.e. 55), usare questa opzione. Il valore grezzo (55)
mostrato allutente e memorizzato nel proprio oggetto. Notare che ci funziona solo per valori interi.
precision

tipo: intero predefinito: 0

Per impostazione predefinita, i numeri inseriti sono arrotondanti. Per consentire ultetiori posizioni decimali, usare
questa opzione.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:

5.1. Documenti di riferimento

525

Symfony2 documentation Documentation, Release 2

required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo radio
Crea un singolo bottone radio. Dovrebbe essere sempre usato per un campo con un valore booleano: se il radio
selezionato, il campo sar impostato a true, altrimenti sar impostato a false.
Il tipo radio solitamente non usato direttamente. Pi comunemente, usato internamente da altri tipo, come
choice. Se si desidere un campo booleano, usare checkbox.
Reso come
Opzioni
Opzioni ereditate

Tipo genitore
Classe

campo input radio


value

required
label
read_only
error_bubbling

field
Symfony\Component\Form\Extension\Core\Type\RadioT

Opzioni del campo

value

tipo: mixed predefinito: 1

Il valore usato effettivamente come valore del bottone radio. Non ha effetti sul valore impostato sul proprio
oggetto.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:

526

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo repeated
Un campo speciale group, che crea due campi identici, i cui valori devono combaciare (altrimenti, viene lanciato
un errore di validazionoe). Luso pi comune quando serve che lutente ripeta la sua password o la sua email,
per verificarne laccuratezza.
Reso come
Opzioni

Opzioni ereditate

Tipo genitore
Classe

solitamente campo input text,


lopzione type

ma vedere

type
options
first_name
second_name

invalid_message
invalid_message_parameters
error_bubbling

field
Symfony\Component\Form\Extension\Core\Type\Repeat

Esempio di utilizzo
$builder->add(password, repeated, array(
type => password,
invalid_message => Le password devono combaciare.,
options => array(label => Password),
));

Dopo un invio di form con successo, il valore inserito in entrambi i campi password diventa il dato della voce
password. In altre parole, anche se i due campi sono effettivamente resi, i dati finali del form conterranno il
singolo valore (solitamente una stringa) necessario.

5.1. Documenti di riferimento

527

Symfony2 documentation Documentation, Release 2

Lopzione pi importante type, che pu essere un qualsiasi tipo di campo e determina il tipo effettivo dei
due campi. Lopzione options passata a ciascuno dei due campi, il che vuol dire, in questo esempio, che
supportata qualsiasi opzione supportata dal tipo password.
Validazione Una delle caratteristiche fondamentali del campo repeated la validazione interna (non occorre
fare nulla per impostarla), che forza i due campi ad avere valori combacianti. Se i due campi non combaciani, sar
mostrato un errore allutente.
si pu usare lopzione invalid_message per personalizzare lerrore che sar mostrato quando i due campi
non combaciano.
Opzioni del campo

type

tipo: stringa predefinito: text

I due campi sottostanti saranno di questo tipo. Per esempio, passare un tipo password render due campi
password.
options

tipo: array predefinito: array()

Questa opzione sar passata a ciascuno dei due campi sottostanti. In altre parole, queste opzioni personalizzano
i singoli campi. Per esempio, se lopzione type password, questo array potrebbe contenere le opzioni
always_empty o required, che sono opzioni supportate dal tipo di campo password.
first_name

tipo: stringa predefinito: first

Leffettivo nome del campo usato per il primo campo. Per lo pi non ha significato, tuttavia, essendo i dati effettivi
inseriti in entrambi i campi disponibili sotto la chiave associata al campo repeated medesimo (p.e password).
Tuttavia, se non si specifica una label, questo nome di campo usato per indovinare la label.
second_name

tipo: stringa predefinito: second

Come first_name, ma per il secondo campo.


Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


invalid_message

tipo: stringa predefinito: This value is not valid

Questo il messaggio di errore di validazione usato quando i dati inseriti sono determinati dalla validazione interna
di un tipo di campo. Questo pu accadere, per esempio, se lutente inserire una stringa dentro un campo time che
non pu essere convertito in un tempo reale. Per i normali messaggi di validazione (come quando si imposta
la lunghezza minima per un campo), impostare i messaggi di validazione con le proprie regole di validazione
(riferimento).
invalid_message_parameters

tipo: array predefinito: array()

Impostando lopzione invalid_message, si potrebbe aver bisogno di includere alcune variabili nella stringa.
Lo si pu fare aggiungendo dei segnaposto allopzione e includendo le variabili in questa opzione:

$builder->add(un_campo, un_tipo, array(


// ...
invalid_message
=> Valore inserito non valido: deve includere almeno %num% carat
invalid_message_parameters => array(%num% => 6),
));

528

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo search
Campo reso come <input type="search" />, che un campo testuale con speciali funzionalit supportate
da alcuni browser.
Maggiori informazioni su DiveIntoHTML5.info
Reso come
Opzioni ereditate

campo input search

Tipo genitore
Classe

text
Symfony\Component\Form\Extension\Core\Type\Search

max_length
required
label
trim
read_only
error_bubbling

Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


max_length

tipo: intero

Questa opzione aggiunge un attributo max_length, usato da alcuni browser per limitare la lunghezza del testo
in un campo.
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

trim

tipo: booleano predefinito: true

Se true, gli spazi vuoti della stringa inviata saranno eliminati, tramite la funzione trim(). Questo garantisce
che se un valore viene inserito con spazi vuoti superflui, questi vengano rimossi prima che il valore sia inserito
nelloggetto sottostante.
read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.

5.1. Documenti di riferimento

529

Symfony2 documentation Documentation, Release 2

error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo text
Il campo text rappresenta il campo testuale di base.
Reso come
Opzioni ereditate

campo input text

Tipo genitore
Classe

field
Symfony\Component\Form\Extension\Core\Type\TextTy

max_length
required
label
trim
read_only
error_bubbling

Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


max_length

tipo: intero

Questa opzione aggiunge un attributo max_length, usato da alcuni browser per limitare la lunghezza del testo
in un campo.
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

trim

tipo: booleano predefinito: true

Se true, gli spazi vuoti della stringa inviata saranno eliminati, tramite la funzione trim(). Questo garantisce
che se un valore viene inserito con spazi vuoti superflui, questi vengano rimossi prima che il valore sia inserito
nelloggetto sottostante.
read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.

530

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo textarea
Rende un elemento HTML textarea.
Reso come
Opzioni ereditate

tag textarea

Tipo genitore
Classe

field
Symfony\Component\Form\Extension\Core\Type\Textar

max_length
required
label
trim
read_only
error_bubbling

Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


max_length

tipo: intero

Questa opzione aggiunge un attributo max_length, usato da alcuni browser per limitare la lunghezza del testo
in un campo.
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

trim

tipo: booleano predefinito: true

Se true, gli spazi vuoti della stringa inviata saranno eliminati, tramite la funzione trim(). Questo garantisce
che se un valore viene inserito con spazi vuoti superflui, questi vengano rimossi prima che il valore sia inserito
nelloggetto sottostante.
read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.

5.1. Documenti di riferimento

531

Symfony2 documentation Documentation, Release 2

error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo time
Un campo per inserire un tempo.
Pu essere reso come campo testuale, una serie di campi testuali (p.e. ora, minuto, secondo) o una serie di select.
I dati sottostanti possono essere memorizzati come oggetto DateTime, stringa, timestamp o array.
Tipo di dato sottstante
Reso come
Opzioni

Tipo genitore
Classe

DateTime, stringa, timestamp o array (vedere


opzione input)
pu essere vari tag (vedere sotto)

widget
input
with_seconds
hours
minutes
seconds
data_timezone
user_timezone

form
Symfony\Component\Form\Extension\Core\Type\TimeTy

Utilizzo di base

Questo tipo di campo altamente configurabile, ma facile da usare. Le opzioni pi importanti sono input e
widget.
Si supponga di avere un campo startTime, il cui datp sottostante sia un oggetto DateTime. Il codice seguente
configura il tipo time per il campo come tre campi di scelta separati:
$builder->add(startTime, time, array(
input => datetime,
widget => choice,
));

Lopzione input deve essere cambiata per corrispondere al tipo di dato sottostante. Per esempio, se il campo
startTime fosse un timestamp, bisognerebbe impostare input a timestamp:
$builder->add(startTime, time, array(
input => timestamp,
widget => choice,
));

Il campo supporta anche i valori array e string per lopzione input.


Opzioni del campo

widget

tipo: stringa predefinito: choice

Il modo di base in cui il campo andrebbe reso. Pu essere uno dei seguenti:
choice: rende due (o tre, se with_seconds true) select.
text: rende due o tre input testuali (ora, minuto, secondo).

532

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

single_text: rende un singolo input testuale. Il valore inserito dallutente sar validato nella forma
hh:mm (o hh:mm:ss, se si usano i secondi).
input

tipo: stringa predefinito: datetime

IL formato dei dati di ingresso, cio il formato in cui la data memorizzata nelloggetto sottostante. Valori validi
sono:
stringa (p.e. 12:17:26)
datetime (un oggetto DateTime)
array (p.e. array(hour => 12, minute => 17, second => 26))
timestamp (p.e. 1307232000)
Il valore proveniente dal form sar normalizzato nello stesso formato.
with_seconds

tipo: booleano predefinito: false

Se includere o meno i secondi nellinput. Se true, ci sar un campo aggiuntivo per inserire i secondi.
hours

tipo: intero predefinito: da 1 a 23

Lista di ore disponibili per il tipo di campo hours. Questa opzione rilevante solo se lopzione widget
impostata a choice.
minutes

tipo: intero predefinito: da 1 a 59

Lista di minuti disponibili per il tipo di campo minutes. Questa opzione rilevante solo se lopzione widget
impostata a choice.
seconds

tipo: intero predefinito: da 1 a 59

Lista di secondi disponibili per il tipo di campo seconds. Questa opzione rilevante solo se lopzione widget
impostata a choice.
data_timezone

tipo: stringa predefinito: fuso orario di sistema

Fuso orario in cui la data inserita memorizzata. Deve essere uno dei fusi orari supportati da PHP
user_timezone

tipo: stringa predefinito: fuso orario di sistema

Fuso orario usato per mostrare la data allutente (e quindi anche la data inviata dallutente stesso). Deve essere
uno dei fusi orari supportati da PHP.
Tipo di campo timezone
Il tipo timezone un sotto-insieme di ChoiceType, che consente allutente di scegliere da tutti i possibili fusi
orari.
Il valore di ogni fuso orario il nome completo del fuso orario, come America/Chicago o
Europe/Istanbul.
Diversamente dal tipo choice, non occorre specificare lopzione choices o choice_list, perche il campo
usa automaticamente una lunga lista di locale. Si pu specificare una di queste opzioni manualmente, ma allora si
dovrebbe usare direttamente il tipo choice.

5.1. Documenti di riferimento

533

Symfony2 documentation Documentation, Release 2

Reso come

pu essere vari tag (vedere Tag select, checkbox o bottoni radio)

Opzioni ereditate

Tipo genitore
Classe

multiple
expanded
preferred_choices
empty_value
error_bubbling
required
label
read_only

choice
Symfony\Component\Form\Extension\Core\Type\Timezo

Opzioni ereditate

Queste opzioni sono ereditate dal tipo choice:


multiple

tipo: booleano predefinito: false

Se true, lutente potra selezionare pi opzioni (invece di sceglierne una sola). A seconda del valore dellopzione
expanded, sar reso o come un tag select o come dei checkbox, se true, e come un tag select o bottoni radio,
se false. Il valore restituito sar un array.
expanded

tipo: booleano predefinito: false

Se true, saranno resi dei bottoni radio o dei checkbox (a seconda del valore di multiple). Se false, sar
reso un elemento select.
preferred_choices

tipo: array predefinito: array()

Se questa opzione viene specificata, un sotto-insieme di tutte le opzioni sar spostato in cima al select. Il codice
seguente sposter lopzione Paperino in cima, con un separatore visuale tra essa e le opzioni restanti:
$builder->add(scelte_pippo, choice, array(
choices => array(pippo => Pippo, pluto => Pluto, paperino => Paperino),
preferred_choices => array(paperino),
));

Si noti che le scelte preferite hanno senso solo con la resa di un elemento select (cio se expanded false). Le
scelte preferite e le scelte normali sono separate visivamente da una serie di righe (-------------------).
Il separatore pu essere personalizzato durante la resa:
Twig
{{ form_widget(form.scelte_pippo, { separator: ===== }) }}

PHP
<?php echo $view[form]->widget($form[scelte_pippo], array(separator => =====)) ?>

empty_value

tipo: stringa o booleano

Questa opzione determina se apparir o meno una speciale opzione vuota (p.e. Scegliere unopzione) in cima
al select. Questa opzione si applica solamente se entrambe le opzioni expanded e multiple sono impstate a
false.
Aggiungere un valore vuoto con Scegliere unopzione come testo:

534

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

$builder->add(states, choice, array(


empty_value => Scegliere un\opzione,
));

Non mostrare alcun valore vuoto:


$builder->add(states, choice, array(
empty_value => false,
));

Se non si imposta lopzione empty_value, sar aggiunta automaticamente unopzione vuota (senza testo), ma
solo se lopzione required false:
// sar aggiunta unopzione vuota (senza testo)
$builder->add(states, choice, array(
required => false,
));

Queste opzioni sono ereditate dal tipo field:


required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.
error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Tipo di campo url
Il campo url un campo testuale, che aggiunge al valore inserito un prefisso con un protocollo dato (p.e.
http://), se il valore inserito non ha gi tale protocollo.

5.1. Documenti di riferimento

535

Symfony2 documentation Documentation, Release 2

Reso come
Opzioni

campo input url


default_protocol

Opzioni ereditate

Tipo genitore
Classe

max_length
required
label
trim
read_only
error_bubbling

text
Symfony\Component\Form\Extension\Core\Type\UrlTyp

Opzioni del campo

default_protocol

tipo: stringa predefinito: http

Se un valore inserito non inizia per nessun protocollo (p.e. http://, ftp://, ecc.), questo protocollo sar
aggiunto come prefisso alla stringa, quando i dati saranno legati al form.
Opzioni ereditate

Queste opzioni sono ereditate dal tipo field:


max_length

tipo: intero

Questa opzione aggiunge un attributo max_length, usato da alcuni browser per limitare la lunghezza del testo
in un campo.
required

tipo: booleano predefinito: true

Se true, sar reso un attributo required HTML5. La label corrispondente sar anche resa con una classe
required.
Lattributo indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo
di campo, il valore di questa opzione sar indovinato dalle informazioni di validazione.
label

tipo: stringa predefinito: indovinato dal nome del campo

Imposta la label usata per la resa del campo. La label pu anche essere inserita direttamente allinterno del
template:
{{ form_label(form.name, Il tuo nome) }}

trim

tipo: booleano predefinito: true

Se true, gli spazi vuoti della stringa inviata saranno eliminati, tramite la funzione trim(). Questo garantisce
che se un valore viene inserito con spazi vuoti superflui, questi vengano rimossi prima che il valore sia inserito
nelloggetto sottostante.
read_only

tipo: booleano predefinito: false

Se questa opzione true, il campo sar reso con lattributo disabled, in modo che il campo non sia modificabile.

536

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

error_bubbling

tipo: booleano predefinito: false

Se true, qualsiasi errore per questo campo sar passato al campo genitore o al form. Per esempio, se impostato a
true su un campo normale, qualsiasi errore per il campo sar collegato al form principale, non al campo stesso.
Un form composto da campi, ciascuno dei quali costruito con laiuto di un tipo di campo (p.e. un tipo text,
un tipo choice, eccetera). Symfony2 dispone di un gran numero di tipi di campo, che possono essere usati nella
propria applicazione.
Tipi di campo supportati
I seguenti tipi di campo sono disponibili nativamente in Symfony2:
Campi testo

text
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

5.1. Documenti di riferimento

537

Symfony2 documentation Documentation, Release 2

Altri campi

checkbox
file
radio
Gruppi di campi

collection
repeated
Campi nascosti

hidden
csrf
Campi di base

field
form

5.1.10 Riferimento delle funzioni per i form nei template Twig


Questo manuale di riferimento copre tutte le possibili funzioni di Twig disponibili per rendere i form. Ci sono
diverse funzioni disponibili e ognuna responsabile della resa di diverse parti di un form (p.e. label, errori, widget,
eccetera).
form_label(form.name, label, variables)
Rende la label per un dato campo. Si pu opzionalmente passare la label specifica che si vuole mostrare, come
secondo parametro.
{{ form_label(form.name) }}
{# Le seguenti due sintassi sono equivalenti #}
{{ form_label(form.name, Your Name, { attr: {class: foo} }) }}
{{ form_label(form.name, null, { label: Your name, attr: {class: foo} }) }}

form_errors(form.name)
Renders any errors for the given field.
{{ form_errors(form.name) }}
{# rende tutti gli errori "globali" #}
{{ form_errors(form) }}

538

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

form_widget(form.name, variables)
Rende il widget HTML del campo dato. Se lo si applica allintero form o a un insieme di campi, ogni riga di form
sottostante sar resa.
{# rende un widget, ma gli aggiunge la classe "pippo" #}
{{ form_widget(form.name, { attr: {class: pippo} }) }}

Il secondo parametro di form_widget un array di variabili. La variabile pi comune attr, che un array
di attributi HTML da applicare al widget. In alcuni casi, certi tipi hanno anche altre opzioni legate ai template,
che possono essere passate. Tali opzioni saranno discusse per ogni singolo tipo.
form_row(form.name, variables)
Rende la riga di un dato campo, cio la combinazione di label, errori e widget del campo.
{# rende la riga di un campo, ma con label "pippo" #}
{{ form_row(form.name, { label: pippo }) }}

Il secondo parametro di form_row un array di variabili. I template forniti in Symfony consentono solo di
sovrascrivere la label come mostrato nellesempio precedente.
form_rest(form, variables)
Rende tutti i campi che non sono ancora stati resi nel form dato. sempre una buona idea averlo da qualche parte
nel proprio form, perch render i campi nascosti, oltre a tutti i campi che sono stati dimenticati.
{{ form_rest(form) }}

form_enctype(form)
Se il form contiene almeno un campo di caricamento file, render lattributo obbligatorio
enctype="multipart/form-data". sempre una buona idea includerlo nel tag del proprio form:
<form action="{{ path(form_submit) }}" method="post" {{ form_enctype(form) }}>

5.1.11 Riferimento per i vincoli di validazione


NotBlank
Valida che un valore non sia vuoto, cio sia diverso da una stringa vuota e anche diverso da null. Per forzare un
valore a essere solo diverso da null, vedere il vincolo NotNull.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\NotBlank
Symfony\Component\Validator\Constraints\NotBlankV

message

Uso di base

Se ci si vuole assicurare che la propriet firstName di una classe Author non sia vuota, si pu fare come
segue:
YAML

5.1. Documenti di riferimento

539

Symfony2 documentation Documentation, Release 2

properties:
firstName:
- NotBlank: ~

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

Opzioni

message

tipo: stringa predefinito: This value should not be blank

Messaggio mostrato se il valore vuoto.


Blank
Valida che un valore sia vuoto, definito come uguale alla stringa vuota o uguale a null. Per forzare un valore
per essere strettamente uguale a null, vedere il vincolo Null. Per forzare un valore a non essere vuoto, vedere
NotBlank.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Blank
Symfony\Component\Validator\Constraints\BlankVali

message

Uso di base

Se, per qualche ragione, ci si vuole assicurare che la propriet firstName di una classe Author sia vuota, si
pu fare come segue:
YAML
properties:
firstName:
- Blank: ~

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

540

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Opzioni

message

tipo: stringa predefinito: This value should be blank

Messaggio che sar mostrato se il valore non vuoto.


NotNull
Valida che un valore non sia esattamente uguale a null. Per forzare un valore a non essere vuoto (stringa vuota),
vedere il vincolo NotBlank.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\NotNull
Symfony\Component\Validator\Constraints\NotNullVa

message

Uso di base

Se ci si vuole assicurare che la propriet firstName di una classe Author non sia null, si pu fare come
segue:
YAML
properties:
firstName:
- NotNull: ~

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

Opzioni

message

tipo: stringa predefinito: This value should not be null

Messaggio mostrato se il valore null.


Null
Valida che un valore sia esattamente uguale a null. Per forzare una propriet a essere vuota (stringa vuota o
null), vedere il vincolo Blank. Per assicurarsi che una propriet non sia null, vedere NotNull.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Null
Symfony\Component\Validator\Constraints\NullValid

5.1. Documenti di riferimento

message

541

Symfony2 documentation Documentation, Release 2

Uso di base

Se, per qualche ragione, ci si vuole assicurare che la propriet firstName di una classe Author sia esttamente
uguale a null, si pu fare come segue:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
firstName:
- Null: ~

Annotations
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Null()
*/
protected $firstName;
}

Opzioni

message

tipo: stringa predefinito: This value should be null

Messaggio mostrato se il valore non null.


True
Valida che un valore sia true. Nello specifico, controlla se il valore sia esattamente true, esattamente lintero
1 o esattamente la stringa 1.
Veder anche False.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\True
Symfony\Component\Validator\Constraints\TrueValid

message

Uso di base

Questo vincolo si pu applicare a propriet (p.e. una propriet termsAccepted in un modello di registrazione)
o a un metodo getter. molto potente nel secondo caso, in cui si pu asserire che un metodo restituisca il valore
true. Per esempio, si supponga di avere il seguente metodo:
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
class Author
{
protected $token;

542

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

public function isTokenValid()


{
return $this->token == $this->generateToken();
}
}

Si pu vincolare questo metodo con True.


YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
getters:
tokenValid:
- "True": { message: "The token is invalid" }

Annotations
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
protected $token;
/**
* @Assert\True(message = "The token is invalid")
*/
public function isTokenValid()
{
return $this->token == $this->generateToken();
}
}

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

<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">
<getter property="tokenValid">
<constraint name="True">
<option name="message">The token is invalid...</option>
</constraint>
</getter>
</class>
</constraint-mapping>

PHP
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\True;
class Author
{
protected $token;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{

5.1. Documenti di riferimento

543

Symfony2 documentation Documentation, Release 2

$metadata->addGetterConstraint(tokenValid, new True(array(


message => The token is invalid,
)));
}
public function isTokenValid()
{
return $this->token == $this->generateToken();
}
}

Se isTokenValid() restituisce false, la validazione fallisce.


Opzioni

message

tipo: stringa predefinito: This value should be true

Messaggio mostrato se il dato sottostante non true.


False
Valida che un valore sia false. Nello specifico, verifica se il valore sia esattamente false, esattamente lintero
0 o esattamente la stringa 0.
Vedere anche True.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\False
Symfony\Component\Validator\Constraints\FalseVali

message

Uso di base

Il vincolo False pu essere applicato ad una propriet o a un metodo getter, ma usato pi comunemente nel
secondo caso. Per esempio, si supponga di voler garantire che una propriet state non sia in un array dinamico
invalidStates. Per prima cosa, creare un metodo getter:
protected $state;
protectd $invalidStates = array();
public function isStateInvalid()
{
return in_array($this->state, $this->invalidStates);
}

In questo caso, loggetto sottostante valido solamente se il metodo isStateInvalid restituisce false:
YAML
# src/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author
getters:
stateInvalid:
- "False":
message: Youve entered an invalid state.

Annotations

544

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\False()
*/
public function isStateInvalid($message = "Youve entered an invalid state.")
{
// ...
}
}

Caution: Usando YAML, assicurarsi di inserire False tra virgolette ("False"), altrimenti YAML convertir questo valore in un booleano.

Opzioni

message

tipo: stringa predefinito: This value should be false

Messaggio mostrato se i dati sottostanti non sono false.


Type
Valida che un valore sia di uno specifico tipo. Per esempio, se una variabile deve essere un array, si pu usare
questo vincolo con lopzione tipo array, per validarla.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Type
Symfony\Component\Validator\Constraints\TypeValid

type
message

Uso di base

YAML
# src/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
age:
- Type:
type: integer
message: The value {{ value }} is not a valid {{ type }}.

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

class Author
{
/**
* @Assert\Type(type="integer", message="The value {{ value }} is not a valid {{ type }}.

5.1. Documenti di riferimento

545

Symfony2 documentation Documentation, Release 2

*/
protected $age;
}

Opzioni

type

tipo: stringa [opzione predefinita]

Questa opzione obbligatoria il nome pienamente qualificato della classe, oppure uno dei tipi di dato di PHP,
come stabilito dalle funzioni is_ di PHP.
array
bool
callable
float
double
int
integer
long
null
numeric
object
real
resource
scalar
string
message

tipo: stringa predefinito: This value should be of type {{ type }}

Messaggio mostrato se i dati sottostanti non sono del tipo dato.


Email
Valida che un valore sia un indirizzo email valido. Il valore sottostante forzato a stringa, prima di essere validato.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Email
Symfony\Component\Validator\Constraints\EmailVali

message
checkMX

Uso di base

YAML

546

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

# src/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
email:
- Email:
message: The email "{{ value }}" is not a valid email.
checkMX: true

Annotations
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Email(
message = "The email {{ value }} is not a valid email.",
*
checkMX = true
*
)
*
*/
protected $email;
}

Opzioni

message

tipo: stringa predefinito: This value is not a valid email address

Messaggio mostrato se il dato sottostante non un indirizzo email valido.


checkMX

tipo: booleano predefinito: false

Se true, sar usata la funzione checkdnsrr di PHP per verificare la validit del record MX dellhost dellemail
fornita.
MinLength
Valida che la lulnghezza di una stringa sia almeno pari al limite dato.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\MinLength
Symfony\Component\Validator\Constraints\MinLength

limit
message
charset

Uso di base

YAML

# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Blog:
properties:
firstName:
- MinLength: { limit: 3, message: "Your name must have at least {{ limit}} charac

5.1. Documenti di riferimento

547

Symfony2 documentation Documentation, Release 2

Annotations
// src/Acme/BlogBundle/Entity/Blog.php
use Symfony\Component\Validator\Constraints as Assert;
class Blog
{
/**
* @Assert\MinLength(
limit=3,
*
message="Your name must have at least {{ limit}} characters."
*
)
*
*/
protected $summary;
}

XML

<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->


<class name="Acme\BlogBundle\Entity\Blog">
<property name="summary">
<constraint name="MinLength">
<option name="limit">3</option>
<option name="message">Your name must have at least {{ limit}} characters.</optio
</constraint>
</property>
</class>

Opzioni

limit

tipo: intero [opzione predefinita]

Questa opzione obbligatoria il valore minimo. La validazione fallisce se la lunghezza della stringa fornita
minore di questo numero.
message tipo:
stringa predefinito:
limit }} characters or more

This value is too short.

It should have {{

Messaggio mostrato se la stringa sottostante ha una lunghezza inferiore allopzione limit.


charset

tipo: charset predefinito: UTF-8

Se lestensione mbstring di PHP installata, sar usata la funzione mb_strlen di PHP per calcolare la lunghezza
della stringa. Il valore dellopzione charset passato come secondo parametro a tale funzione.
MaxLength
Valida che la lulnghezza di una stringa non sia superiore al limite dato.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\MaxLength
Symfony\Component\Validator\Constraints\MaxLength

548

limit
message
charset

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Uso di base

YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Blog:
properties:
summary:
- MaxLength: 100

Annotations
// src/Acme/BlogBundle/Entity/Blog.php
use Symfony\Component\Validator\Constraints as Assert;
class Blog
{
/**
* @Assert\MaxLength(100)
*/
protected $summary;
}

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Blog">
<property name="summary">
<constraint name="MaxLength">
<value>100</value>
</constraint>
</property>
</class>

Opzioni

limit

tipo: intero [opzione predefinita]

Questa opzione obbligatoria il valore massimo. La validazione fallisce se la lunghezza della stringa fornita
maggiore di questo numero.
message tipo: stringa predefinito: This value is too long.
}} characters or less

It should have {{ limit

Messaggio mostrato se la stringa sottostante ha una lunghezza superiore allopzione limit.


charset

tipo: charset predefinito: UTF-8

Se lestensione mbstring di PHP installata, sar usata la funzione mb_strlen di PHP per calcolare la lunghezza
della stringa. Il valore dellopzione charset passato come secondo parametro a tale funzione.
Url
Valida che un valore sia un valid URL string.

5.1. Documenti di riferimento

549

Symfony2 documentation Documentation, Release 2

Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Url
Symfony\Component\Validator\Constraints\UrlValida

message
protocols

Uso di base

YAML
# src/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
bioUrl:
- Url:

Annotations
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Url()
*/
protected $bioUrl;
}

Opzioni

message

tipo: stringa predefinito: This value is not a valid URL

Messaggio mostrato se lURL non valido.


protocols

tipo: array predefinito: array(http, https)

I protocolli che saranno considerati validi. Per esempio, se occorre che anche gli URL ftp:// siano validi, si
pu ridefinire larray protocols, elencando http, https e ftp.
Regex
Valida che un valore corrisponda a unespressione regolare.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Regex
Symfony\Component\Validator\Constraints\RegexVali

550

pattern
match
message

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Uso di base

Si supponga di avere un campo description e di voler verificare che inizi con un carattere alfanumerico
valido. Lespressione regolare da testare sarebbe /^\w+/, che indica che si sta cercando almeno uno o pi
caratteri alfanumerici allinizio della stringa:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
description:
- Regex: "/^\w+/"

Annotations
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Regex("/^\w+/")
*/
protected $description;
}

In alternativa, si pu impostare lopzione match a false, per asserire che una stringa data non debba corrispondere. Nellesempio seguente, si asserisce che il campo firstName non contenga numeri e si imposta un messaggio personalizzato:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
firstName:
- Regex:
pattern: "/\d/"
match:
false
message: Il nome non pu contenere numeri

Annotations
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Regex(
pattern="/\d/",
*
match=false,
*
message="Il nome non pu contenere numeri"
*
* )
*/
protected $firstName;
}

5.1. Documenti di riferimento

551

Symfony2 documentation Documentation, Release 2

Opzioni

pattern

tipo: stringa [opzione predefinita]

Questa opzione obbligatoria lespressione regolare a cui il valore inserito deve corrispondere. Per impostazione
predefinita, il validatore fallisce se la stringa inserita non corrisponde a questa espressione regolare (tramite la
funzione preg_match di PHP). Se tuttavia match false, la validazione fallisce se la stringa inserita corrisponde
a questo schema.
match

tipo: booleano default: true

Se true (o non impostato), questo validatore passer se la stringa data corrisponde allespressione regolare contenuta in pattern. Se invece lopzione false, sar il contrario: la validazione passer solo se la stringa data non
corrisponder allespressione regolare contenuta in pattern.
message

tipo: stringa predefinito: This value is not valid

Messaggio mostrato se il validatore fallisce.


Ip
Valida che un valore sia un indirizzo IP valido. Per impostazione predefinita, valida un valore come IPv4, ma ci
sono diverse opzioni per validare come IPv6 e altre combinazioni.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Ip
Symfony\Component\Validator\Constraints\IpValidat

version
message

Uso di base

YAML
# src/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
ipAddress:
- Ip:

Annotations
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Ip
*/
protected $ipAddress;
}

552

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Opzioni

version

tipo: stringa predefinito: 4

Determina esattamente come validato lindirizzo IP e acceta uno dei tanti valori a disposizione:
Tutte le fasce
4 - Valida indirizzi IPv4
6 - Valida indirizzi IPv6
all - Valida tutti i formati IP
Nessuna fascia privata
4_no_priv - Valida IPv4, ma senza le fasce IP private
6_no_priv - Valida IPv6, ma senza le fasce IP private
all_no_priv - Valida tutti i formati IP, ma senza le fasce IP private
Nessuna fascia riservata
4_no_res - Valida IPv4, ma senza le fasce IP riservate
6_no_res - Valida IPv6, ma senza le fasce IP riservate
all_no_res - Valida tutti i formati IP, ma senza le fasce IP riservate
Solo fasce pubbliche
4_public - Valida IPv4, ma senza fasce private e riservate
6_public - Valida IPv6, ma senza fasce private e riservate
all_public - VValida tutti i formati IP, ma senza le fasce IP private e riservate
message

tipo: stringa predefinito: This is not a valid IP address

Messaggio mostrato se la stringa non un indirizzo IP valido.


Max
Valida che un dato numero sia minore di un numero massimo.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Max
Symfony\Component\Validator\Constraints\MaxValida

limit
message
invalidMessage

Uso di base

Per verificare che il campo age di una classe non sia maggiore di 50, si potrebbe aggiungere il seguente:
YAML
# src/Acme/EventBundle/Resources/config/validation.yml
Acme\EventBundle\Entity\Participant:
properties:
age:
- Max: { limit: 50, message: You must be 50 or under to enter. }

5.1. Documenti di riferimento

553

Symfony2 documentation Documentation, Release 2

Annotations
// src/Acme/EventBundle/Entity/Participant.php
use Symfony\Component\Validator\Constraints as Assert;
class Participant
{
/**
* @Assert\Max(limit = 50, message = "You must be 50 or under to enter.")
*/
protected $age;
}

Opzioni

limit

tipo: intero [opzione predefinita]

Questa opzione obbligatoria il valore massimo. La validazione fallisce se il valore fornito maggiore di questo.
message

tipo: stringa predefinito: This value should be {{ limit }} or less

Messaggio mostrato se il valore sottostante maggiore dellopzione limit.


invalidMessage

tipo: stringa predefinito: This value should be a valid number

Messaggio mostrato se il valore sottostante non un numero (in base alla funzione is_numeric di PHP).
Min
Valida che un dato numero sia maggiore di un numero minimo.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Min
Symfony\Component\Validator\Constraints\MinValida

limit
message
invalidMessage

Uso di base

Per verificare che il campo age di una classe sia 18 o pi, si potrebbe aggiungere il seguente:
YAML
# src/Acme/EventBundle/Resources/config/validation.yml
Acme\EventBundle\Entity\Participant:
properties:
age:
- Min: { limit: 18, message: You must be 18 or older to enter. }

Annotations
// src/Acme/EventBundle/Entity/Participant.php
use Symfony\Component\Validator\Constraints as Assert;
class Participant
{

554

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

/**
* @Assert\Min(limit = "18", message = "You must be 18 or older to enter")
*/
protected $age;
}

Opzioni

limit

tipo: intero [opzione predefinita]

Questa opzione obbligatoria il valore minimo. La validazione fallisce se il valore fornito minore di questo.
message

tipo: stringa predefinito: This value should be {{ limit }} or more

Messaggio mostrato se il valore sottostante minore dellopzione limit.


invalidMessage

tipo: stringa predefinito: This value should be a valid number

Messaggio mostrato se il valore sottostante non un numero (in base alla funzione is_numeric di PHP).
Date
Valida che un valore sia una data valida, cio o un oggetto DateTime o una strina (o un oggetto che possa essere
forzato a stringa) che segue un formato valido YYYY-MM-DD.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Date
Symfony\Component\Validator\Constraints\DateValid

message

Uso di base

YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
birthday:
- Date: ~

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

5.1. Documenti di riferimento

555

Symfony2 documentation Documentation, Release 2

Opzioni

message

tipo: stringa predefinito: This value is not a valid date

Messaggio mostrato se i dati sottostanti non sono una data valida.


DateTime
Valida che un valore sia una data/ora valida, cio o un oggetto DateTime o una stringa (o un oggetto che possa
essere forzato a stringa) con un formato valido YYYY-MM-DD HH:MM:SS.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\DateTime
Symfony\Component\Validator\Constraints\DateTimeV

message

Uso di base

YAML
# src/Acme/EventBundle/Resources/config/validation.yml
Acme\BlobBundle\Entity\Author:
properties:
createdAt:
- DateTime: ~

Annotations
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\DateTime()
*/
protected $createdAt;
}

Opzioni

message

tipo: stringa predefinito: This value is not a valid datetime

Messaggio mostrato se i dati sottostanti non sono una data/ora valida.


Time
Valida che un valore sia un tempo valido, cio o un oggetto DateTime o una stringa (o un oggetto che possa
essere forzato a stringa) che segua il formato HH:MM:SS.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Time
Symfony\Component\Validator\Constraints\TimeValid

556

message

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Uso di base

Si supponga di avere una classe Event, con un campo startAt, che indica lora del giorno in cui levento
inizia:
YAML
# src/Acme/EventBundle/Resources/config/validation.yml
Acme\EventBundle\Entity\Event:
properties:
startsAt:
- Time: ~

Annotations
// src/Acme/EventBundle/Entity/Event.php
namespace Acme\EventBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Event
{
/**
* @Assert\Time()
*/
protected $startsAt;
}

Opzioni

message

tipo: stringa predefinito: This value is not a valid time

Messaggio mostrato se il dato sottostante non un orario valido.


Choice
Questo vincolo usato per assicurarsi che il valore fornito faccia parte dellinsieme di scelte valide. Pu anche
essere usato per validare che ogni elemento in un array sia una di tali scelte valide.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Choice
Symfony\Component\Validator\Constraints\ChoiceVal

choices
callback
multiple
min
max
message
multipleMessage
minMessage
maxMessage
strict

Uso di base

Lidea di base di questo vincolo quella che, una volta fornito un array di valori validi (lo si pu fare in diversi
modi), esso validi che il valore della data propriet esista in tale array.

5.1. Documenti di riferimento

557

Symfony2 documentation Documentation, Release 2

Se la lista di scelta semplice, la si pu passare direttamente tramite lopzione choices:


YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
gender:
- Choice:
choices: [maschio, femmina]
message: Scegliere un genere valido.

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\EntityAuthor">
<property name="gender">
<constraint name="Choice">
<option name="choices">
<value>maschio</value>
<value>femmina</value>
</option>
<option name="message">Scegliere un genere valido.</option>
</constraint>
</property>
</class>

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

class Author
{
/**
* @Assert\Choice(choices = {"maschio", "femmina"}, message = "Scegliere un genere valido
*/
protected $gender;
}

PHP
// src/Acme/BlogBundle/EntityAuthor.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(
choices => array(maschio, femmina),
message => Scegliere un genere valido.,
));
}
}

Fornire le scelte con una funzione callback

Si pu anche usare una funzione callback per specificare le opzioni. Questo utile, se si vogliono mantenere
le scelte in un posto centralizzato, in modo da poter accedere facilmente a tali scelte, per la validazione o per

558

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

costruire un elemento select di un form.


// src/Acme/BlogBundle/Entity/Author.php
class Author
{
public static function getGenders()
{
return array(maschio, femmina);
}
}

Si pu passare il nome di questo metodo allopzione callback_ del vincolo Choice.


YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
gender:
- Choice: { callback: getGenders }

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

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Author">
<property name="gender">
<constraint name="Choice">
<option name="callback">getGenders</option>
</constraint>
</property>
</class>

Se il callback statico posto in una classe diversa, per esempio Util, si pu passare il nome della classe e del
metodo come array.
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
properties:
gender:
- Choice: { callback: [Util, getGenders] }

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Author">
<property name="gender">
<constraint name="Choice">
<option name="callback">
<value>Util</value>
<value>getGenders</value>
</option>

5.1. Documenti di riferimento

559

Symfony2 documentation Documentation, Release 2

</constraint>
</property>
</class>

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

Opzioni disponibili

choices

tipo: array [opzione predefinita]

Unopzione obbligatoria (a meno che non sia specificato callback), larray di opzioni da considerare nellinsieme
valido. Il valore di input dovr corrispondere a questo array.
callback

tipo: string|array|Closure

Un metodo callback che pu essere usato, al posto dellopzione choices, per restituire larray delle scelte. Vedere
Fornire le scelte con una funzione callback per maggiori dettagli sul suo utilizzo.
multiple

tipo: booleano predefinito: false

Se questa opzione vale true, ci si aspetta come valore di input un array, invece di un singolo valore. Il vincolo
verificher che ogni valore dellarray di input possa essere trovato nellarray di scelte valide. Se anche uno solo
dei valori di input non viene trovato, la validazione fallisce.
min

tipo: intero

Se lopzione multiple vale true, si pu usare lopzione min per forzare la scelta di una quantit minima di
valori. Per esempio, se min 3, ma larray di input contiene solo 2 valori validi, la validazione fallisce.
max

tipo: intero

Se lopzione multiple vale true, si pu usare lopzione max per forzare la scelta di una quantit massima di
valori. Per esempio, se max 3, ma larray di input contiene 4 valori validi, la validazione fallisce.
message

tipo: stringa predefinito: The value you selected is not a valid choice

Il messaggio che si ricever se lopzione multiple impostata a false e il valore sottostante non tra quelli
dellarray di scelte valide.
multipleMessage

tipo: stringa predefinito: One or more of the given values is invalid

Il messaggio che si ricever se lopzione multiple impostata a false e uno dei valori dellarray in corso di
validazione non tra quelli dellarray di scelte valide.
minMessage

tipo: stringa predefinito: You must select at least {{ limit }} choices

Messaggi di errore mostrato quanto lutente seleziona troppo poche scelte, in base allopzione min.
560

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

maxMessage

tipo: stringa predefinito: You must select at most {{ limit }} choices

Messaggi di errore mostrato quanto lutente seleziona troppe scelte, in base allopzione max.
strict

tipo: booleano predefinito: false

Se true, il validatore verificher anche il tipo del valore di input. In particolare, questo valore passato al terzo
parametro della funzione in_array di PHP, durante la verifica se un valore nellarray di scelte valide.
Collection
Questo vincolo si usa quando i dati sottostanti sono un insieme (cio un array o un oggetto che implementi
Traversable e ArrayAccess), ma si preferisce validare diverse chiavi di tale insieme in modi diversi. Per
esempio, si potrebbe voler validare la chiave email con il vincolo Email e la chiave inventory con il vincolo
Min.
Questo vincolo pu anche assicurare che alcune chiavi dellinsieme siano presenti e e che chiavi extra non siano
presenti.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Collectio
Symfony\Component\Validator\Constraints\Collectio

fields
allowExtraFields
extraFieldsMessage
allowMissingFields
missingFieldsMessage

Uso di base

Il vincolo Collection consente di validare le diverse chiavi di un insieme in modo individuale. Si consideri il
seguente esempio:
namespace Acme\BlogBundle\Entity;
class Author
{
protected $profileData = array(
personal_email,
short_bio,
);
public function setProfileData($key, $value)
{
$this->profileData[$key] = $value;
}
}

Per validare che lelemento personal_email della propriet profileData dellarray sia un indirizzo email
valido e che lelemento short_bio non sia vuoto e non pi lungo di 100 caratteri, si potrebbe fare nel seguente
modo:
YAML
properties:
profileData:
- Collection:
fields:
personal_email: Email

5.1. Documenti di riferimento

561

Symfony2 documentation Documentation, Release 2

short_bio:
- NotBlank
- MaxLength:
limit:
100
message: Your short bio is too long!
allowMissingfields: true

Annotations
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\Collection(
fields = {
*
"personal_email" = @Assert\Email,
*
"short_bio" = {
*
@Assert\NotBlank(),
*
@Assert\MaxLength(
*
limit = 100,
*
message = "Your bio is too long!"
*
)
*
}
*
},
*
allowMissingfields = true
*
* )
*/
protected $profileData = array(
personal_email,
short_bio,
);
}

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Author">
<property name="profileData">
<constraint name="Collection">
<option name="fields">
<value key="personal_email">
<constraint name="Email" />
</value>
<value key="short_bio">
<constraint name="NotBlank" />
<constraint name="MaxLength">
<option name="limit">100</option>
<option name="message">Your bio is too long!</option>
</constraint>
</value>
</option>
<option name="allowMissingFields">true</option>
</constraint>
</property>
</class>

PHP
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\Email;

562

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

use Symfony\Component\Validator\Constraints\MaxLength;
class Author
{
private $options = array();
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(profileData, new Collection(array(
fields => array(
personal_email => new Email(),
lastName => array(new NotBlank(), new MaxLength(100)),
),
allowMissingFields => true,
)));
}
}

Presenza e assenza di campi Per impostazione predefinita, questo vincolo valida pi del semplice fatto che i
singoli campi dellinsieme passino o meno i loro rispettivi vincoli. Infatti, se una chiave dellinsieme manca o se
ci sono chiavi non riconosciute nellinsieme, saranno lanciati degli errori di validazione.
Se si vogliono consentire chiavi assenti dallinsieme o se si vuole che chiavi extra siano consentite nellinsieme,
si possono modificare rispettivamente le opzioni allowMissingFields e allowExtraFields. Nellesempio precedente, lopzione allowMissingFields era impostata a true, quindi, se gli elementi personal_email
o short_bio fossero stati mancanti dalla propriet $personalData, non sarebbe occorso alcun errore di
validazione.
Opzioni

fields

tipo: array [opzione predefinita]

Questa opzione, obbligatorio, un array associativo, che definisce tutte le chiavi nellinsieme e, per ogni chiave,
esattamente quale validatore (o quali validatori) vanno eseguiti su quellelemento dellinsieme.
allowExtraFields

tipo: booleano predefinito: false

Se questa opzione false e linsieme sottostante contiene uno o pi elementi non inclusi nellopzione fields,
sar restituto un errore di validazione. Se true, i campi extra sono consentiti.
extraFieldsMessage
expected

tipo:

booleano

predefinito:

The fields {{ fields }} were not

Messaggio mostrato se allowExtraFields false e viene trovato un campo extra.


allowMissingFields

tipo: booleano predefinito: false

Se questa opzione false e uno o pi campi dellopzione fields mancano nellinsieme sottostante, sar restituito
un errore di validazione. Se true, alcuni campi dellopzione fields_ possono mancare nellinsieme sottostante.
missingFieldsMessage

tipo: booleano predefinito: The fields {{ fields }} are missing

Messaggio mostrato se allowMissingFields false e uno o pi campo mancano dallinsieme sottostante.

5.1. Documenti di riferimento

563

Symfony2 documentation Documentation, Release 2

UniqueEntity
Valida che un particolare campo (o campi) in un entit Doctrine sia unico. Si usa di solito, per esempio, per
prevenire che un nuovo utente si registri usando un indirizzo email gi esistente nel sistema.
Si applica a
Opzioni

class

Classe
Validatore

Symfony\Bridge\Doctrine\Validator\Constraints\Uni
Symfony\Bridge\Doctrine\Validator\Constraints\Uni

fields
message
em

Uso di base

Si supponga di avere un AcmeUserBundle con un entit User, che ha un campo email. Si pu usare il
vincolo Unique per garantire che il campo email rimanga unico tra tutti i vincoli della propria tabella degli
utenti:
Annotations
// Acme/UserBundle/Entity/User.php
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
// NON dimenticare questa istruzione!!!
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* @ORM\Entity
* @UniqueEntity("email")
*/
class Author
{
/**
* @var string $email
*
* @ORM\Column(name="email", type="string", length=255, unique=true)
* @Assert\Email()
*/
protected $email;
// ...
}

YAML
# src/Acme/UserBundle/Resources/config/validation.yml
Acme\UserBundle\Entity\Author:
constraints:
- Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: email
properties:
email:
- Email: ~

XML
<class name="Acme\UserBundle\Entity\Author">
<constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">email</option>
<option name="message">This email already exists.</option>
</constraint>

564

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

<property name="email">
<constraint name="Email" />
</property>
</class>

Opzioni

fields

tipo: array|stringa [opzione predefinita]

Questa opzione obbligatoria il campo (o la lista di campi) per cui lentit deve essere unica. Per esempio, si pu
specificare che i campi email e nome nellesempio precedente debbano essere unici.
message

tipo: stringa predefinito: This value is already used.

Messaggio mostrato quanto il vincolo fallisce.


em

tipo: stringa

Nome del gestore di entit da usare per eseguire la query che determina lunicit. Se lasciato vuoto, sar determinato il gestore di entit corretto per questa classe. Per questo motivo, probabilmente non occorre usare questa
opzione.
Language
Valida che un valore sia un codice di lingua valido.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Language
Symfony\Component\Validator\Constraints\LanguageV

message

Uso di base

YAML
# src/UserBundle/Resources/config/validation.yml
Acme\UserBundle\Entity\User:
properties:
preferredLanguage:
- Language:

Annotations
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class User
{
/**
* @Assert\Language
*/
protected $preferredLanguage;
}

5.1. Documenti di riferimento

565

Symfony2 documentation Documentation, Release 2

Opzioni

message

tipo: stringa predefinito: This value is not a valid language

Messaggio mostrato se la stringa non un codice di lingua valido.


Locale
Valida che un valore sia un locale valido.
Il valore di ogni locale ubn codice di lingua ISO639-1 (p.e. fr) oppure il codice di lingua seguito dal trattino
basso (_), quindi un codice paese ISO3166 (p.e. fr_FR per Francese/Francia).
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Locale
Symfony\Component\Validator\Constraints\LocaleVal

message

Uso di base

YAML
# src/UserBundle/Resources/config/validation.yml
Acme\UserBundle\Entity\User:
properties:
locale:
- Locale:

Annotations
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class User
{
/**
* @Assert\Locale
*/
protected $locale;
}

Opzioni

message

tipo: stringa predefinito: This value is not a valid locale

Messaggio mostrato se la stringa non un locale valido.


Country
Valida che un valore sia un codice valido per un paese.

566

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\Country
Symfony\Component\Validator\Constraints\CountryVa

message

Uso di base

YAML
# src/UserBundle/Resources/config/validation.yml
Acme\UserBundle\Entity\User:
properties:
country:
- Country:

Annotations
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class User
{
/**
* @Assert\Country
*/
protected $country;
}

Opzioni

message

tipo: stringa predefinito: This value is not a valid country

Messaggio mostrato se la stringa non un codice valido per un paese.


File
Valida che un valore sia un file valido, che pu essere uno dei seguenti:
Una stringa (o oggetto con metodo __toString()) con un percorso di un file esistente;
Un oggetto Symfony\Component\HttpFoundation\File\File valido (inclusi oggetti della
classe Symfony\Component\HttpFoundation\File\UploadedFile).
Qursto vincolo si usa solitamente in form con il tipo di form file.
Tip: Se il file da validare unimmagine, prrovare il vincolo Image.

5.1. Documenti di riferimento

567

Symfony2 documentation Documentation, Release 2

Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\File
Symfony\Component\Validator\Constraints\FileValid

maxSize
mimeTypes
maxSizeMessage
mimeTypesMessage
notFoundMessage
notReadableMessage
uploadIniSizeErrorMessage
uploadFormSizeErrorMessage
uploadErrorMessage

Uso di base

Questo vincolo si usa saolitamente su una propriet che sar resa in un form come tipo di form file. Per esempio,
si supponga di aver creato un form autore, in cui si possa caricare un file PDF con una biografia. Nel proprio form,
la propriet bioFile di tipo file. La classe Author potrebbe essere come la seguente:
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\HttpFoundation\File\File;
class Author
{
protected $bioFile;
public function setBioFile(File $file = null)
{
$this->bioFile = $file;
}
public function getBioFile()
{
return $this->bioFile;
}
}

Per assicurarsi che loggetto File bioFile sia valido e che sia al di sotto di una certa dimensione e un PDF
valido, aggiungere il seguente:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author
properties:
bioFile:
- File:
maxSize: 1024k
mimeTypes: [application/pdf, application/x-pdf]
mimeTypesMessage: Please upload a valid PDF

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

568

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

/**
* @Assert\File(
maxSize = "1024k",
*
mimeTypes = {"application/pdf", "application/x-pdf"},
*
mimeTypesMessage = "Please upload a valid PDF"
*
* )
*/
protected $bioFile;
}

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Author">
<property name="bioFile">
<constraint name="File">
<option name="maxSize">1024k</option>
<option name="mimeTypes">
<value>application/pdf</value>
<value>application/x-pdf</value>
</option>
<option name="mimeTypesMessage">Please upload a valid PDF</option>
</constraint>
</property>
</class>

PHP
// src/Acme/BlogBundle/Entity/Author.php
// ...
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\File;
class Author
{
// ...
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(bioFile, new File(array(
maxSize => 1024k,
mimeTypes => array(
application/pdf,
application/x-pdf,
),
mimeTypesMessage => Please upload a valid PDF,
)));
}
}

La propriet bioFile validata per garantire che sia un vero file. Anche la sua dimensione e il suo tipo mime
sono validati, perch le opzioni appropriate sono state specificate.
Opzioni

maxSize

tipo: mixed

Se impostata, la dimensione del file sottostante deve essere inferiore, per essere valido. La dimensione del file pu
essere fornita in uno dei seguenti formati:
bytes: Per specificare maxSize in byte, passare un valore che sia interamente numerico (p.e. 4096);

5.1. Documenti di riferimento

569

Symfony2 documentation Documentation, Release 2

kilobytes: Per specificare maxSize in kilobyte, passare un numero e un suffisso con una k minuscola
(p.e. 200k);
megabytes: Per specificare maxSize in megabyte, passare un numero e un suffisso con una M maiuscola
(p.e. 4M).
mimeTypes

tipo: array o stringa

Se impostata, il validatore verificher che il tipo mime del file sottostante sia uguale al tipo mime dato (se stringa)
o a uno dei tipi mime dati (se array).
maxSizeMessage tipo:
stringa predefinito:
Allowed maximum size is {{ limit }}

The file is too large ({{ size }}).

Messaggio mostrato se il file pi grande dellopzione maxSize.


mimeTypesMessage tipo: stringa predefinito: The mime type of the file is invalid
({{ type }}). Allowed mime types are {{ types }}
Messaggio mostrato se il tipo mime del file non un tipo mime valido, in base allopzione mimeTypes.
notFoundMessage

tipo: stringa predefinito: The file could not be found

Messaggio mostrato se non viene trovato alcun file nel percorso fornito. Questo errore pu avvenire solo in caso
di valore stringa, perch un oggetto File non pu essere costruito con un percorso non valido.
notReadableMessage

tipo: stringa predefinito: The file is not readable

Messaggio mostrato se il file esiste, ma la funzione is_readable di PHP fallisce, quando gli si passa il percorso
del file.
uploadIniSizeErrorMessage tipo: stringa predefinito:
maximum size is {{ limit }}

The file is too large.

Allowed

Messaggio mostrato se il file caricato pi grande dellimpostazione upload_max_filesize di php.ini.


uploadFormSizeErrorMessage

tipo: stringa predefinito: The file is too large

Messaggio mostrato se il file caricato pi grande di quanto consentito dal campo input HTML.
uploadErrorMessage

tipo: stringa predefinito: The file could not be uploaded

Messaggio mostrato se il file caricato non pu essere caricato per una ragione sconosciuta, per esempio se il file
non pu essere scritto su disco.
Image
Questo vincolo funziona esattamente come File, tranne per il fatto che le opzioni mimeTypes e mimeTypesMessage
sono impostate automaticamente per lavorare specificatamente su file di tipo immagine.
Inoltre, da Symfony 2.1, ha delle opzioni che consentono di validare larghezza e altezza dellimmagine.
Vedere il vincolo File per la documentazione completa su questo vincolo.

570

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\File
Symfony\Component\Validator\Constraints\FileValid

mimeTypes
minWidth
maxWidth
maxHeight
minHeight
mimeTypesMessage
sizeNotDetectedMessage
maxWidthMessage
minWidthMessage
maxHeightMessage
minHeightMessage
See File for inherited options

Uso di base

Questo vincolo si usa solitamente su una propriet che sar resa in un form come tipo file. Per esempio, si
supponga di creare un form per un autore, in cui si pu caricare una sua immagine in primo piano. Nel form, la
propriet headshot sarebbe di tipo file. La classe Author potrebbe assomigliare a questa:
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;
use Symfony\Component\HttpFoundation\File\File;
class Author
{
protected $headshot;
public function setHeadshot(File $file = null)
{
$this->headshot = $file;
}
public function getHeadshot()
{
return $this->headshot;
}
}

Per assicurare che loggetto headshot sa unimmagine valida e che sia entro certe dimensioni, aggiungere il
seguente:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author
properties:
headshot:
- Image:
minWidth: 200
maxWidth: 400
minHeight: 200
maxHeight: 400

Annotations

5.1. Documenti di riferimento

571

Symfony2 documentation Documentation, Release 2

// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* @Assert\File(
minWidth = 200,
*
maxWidth = 400,
*
minHeight = 200,
*
maxHeight = 400,
*
* )
*/
protected $headshot;
}

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Author">
<property name="headshot">
<constraint name="File">
<option name="minWidth">200</option>
<option name="maxWidth">400</option>
<option name="minHeight">200</option>
<option name="maxHeight">400</option>
</constraint>
</property>
</class>

PHP
// src/Acme/BlogBundle/Entity/Author.php
// ...
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\File;
class Author
{
// ...
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(headshot, new File(array(
minWidth => 200,
maxWidth => 400,
minHeight => 200,
maxHeight => 400,
)));
}
}

La propriet headshot validata per assicurare che sia una vera immagine e e che abbia altezza e larghezza
entro i limiti.
Opzioni

Questo vincolo condivide tutte le sue opzioni con il vincolo File. Tuttavia, modifica due dei valori predefiniti delle
opzioni e aggiunge diverse altre opzioni:

572

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

mimeTypes

tipo: array o stringa predefinito: un array di tipi mime jpg, gif e png

mimeTypesMessage tipo: stringa predefinito: This file is not a valid image New in version 2.1: Tutte le opzioni min/max width/height sono state aggiunte in Symfony 2.1.
minWidth

tipo: intero

Se impostato, la larghezza del file immagine deve essere maggiore o uguale di questo valore in pixel.
maxWidth

tipo: intero

Se impostato, la larghezza del file immagine deve essere minore o uguale di questo valore in pixel.
minHeight

tipo: intero

Se impostato, laltezza del file immagine deve essere maggiore o uguale di questo valore in pixel.
maxHeight

tipo: intero

Se impostato, laltezza del file immagine deve essere minore o uguale di questo valore in pixel.
sizeNotDetectedMessage
detected

tipo:

string predefinito:

The size of the image could not be

Se il sistema non in grado di determinare la dimensione dellimmagine, sar mostrato questo errore. Questo si
verificher solo se almeno uno dei quattro vincoli di dimensione stato impostato.
maxWidthMessage tipo:
string predefinito:
The image width is too big ({{ width
}}px). Allowed maximum width is {{ max_width }}px
Il messaggio di errore se la larghezza dellimmagine eccede maxWidth.
minWidthMessage tipo: string predefinito: The image width is too small ({{ width
}}px). Minimum width expected is {{ min_width }}px
Il messaggio di errore se la larghezza dellimmagine inferiore a maxWidth.
maxHeightMessage tipo: string predefinito: The image height is too big ({{ height
}}px). Allowed maximum height is {{ max_height }}px
Il messaggio di errore se laltezza dellimmagine eccede maxHeight.
minHeightMessage tipo: string predefinito: The image height is too small ({{ height
}}px). Minimum height expected is {{ min_height }}px
Il messaggio di errore se laltezza dellimmagine inferiore a maxHeight.
Callback
Lo scopo del vincolo Callback di poter creare delle regole di validazioni completamente personalizzate e di
assegnare qualsiasi errore di validazione a campi specifici del proprio oggetto. Se si usa la validazione con i form,
questo vuol dire che si possono mostrare questi errori personalizzati accanto a campi specifici, invece di mostrarli
semplicemente in cima al form.

5.1. Documenti di riferimento

573

Symfony2 documentation Documentation, Release 2

Questo processo funziona specificando uno o pi metodi di callback, ciascuno dei quali sar richiamato durante
il processo di validazione. Ognuno di questi metodi pu fare qualsiasi cosa, incluso creare e assegnare errori di
validazione.
Note: Un metodo callback per s stesso non fallisce, n restituisce valori. Invece, come vedremo nellesempio,
un metodo callback ha labilit di aggiungere direttamente violazioni al validatore.
Si applica a
Opzioni

classi

Classe
Validatore

Symfony\Component\Validator\Constraints\Callback
Symfony\Component\Validator\Constraints\CallbackV

methods

Preparazione

YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
constraints:
- Callback:
methods:
[isAuthorValid]

XML
<!-- src/Acme/BlogBundle/Resources/config/validation.xml -->
<class name="Acme\BlogBundle\Entity\Author">
<constraint name="Callback">
<option name="methods">
<value>isAuthorValid</value>
</option>
</constraint>
</class>

Annotations
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
/**
* @Assert\Callback(methods={"isAuthorValid"})
*/
class Author
{
}

Il metod callback

Il metod callback passato a uno speciale oggetto ExecutionContext. Si possono impostare le violazioni
direttamente su questo oggetto e determinare a quale campo questi errori vadano attribuiti:
// ...
use Symfony\Component\Validator\ExecutionContext;
class Author
{
// ...
private $firstName;

574

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

public function isAuthorValid(ExecutionContext $context)


{
// somehow you have an array of "fake names"
$fakeNames = array();
// check if the name is actually a fake name
if (in_array($this->getFirstName(), $fakeNames)) {
$property_path = $context->getPropertyPath() . .firstName;
$context->setPropertyPath($property_path);
$context->addViolation(This name sounds totally fake!, array(), null);
}
}

Opzioni

methods

tipo: array predefinito: array() [opzione predefinita]

Un array di metodi che andrebbero eseguiti durante il processo di validazione. Ogni metodo pu avere uno dei
seguenti formati:
1. Stringa con il nome del metodo
Se il nome di un metodo una semplice stringa (p.e. isAuthorValid), quel metodo sar
richiamato sullo stesso oggetto in corso di validazione e ExecutionContext sar lunico
parametro (vedere esempio precedente).
2. Array statico callback
Ogni metodo pu anche essere specificato con un array callback:
YAML
# src/Acme/BlogBundle/Resources/config/validation.yml
Acme\BlogBundle\Entity\Author:
constraints:
- Callback:
methods:
[Acme\BlogBundle\MyStaticValidatorClass, isAuthorValid]

Annotations
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Constraints as Assert;
/**
* @Assert\Callback(methods={
{ "Acme\BlogBundle\MyStaticValidatorClass", "isAuthorValid"}
*
* })
*/
class Author
{
}

PHP
// src/Acme/BlogBundle/Entity/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\Callback;
class Author
{

5.1. Documenti di riferimento

575

Symfony2 documentation Documentation, Release 2

public $name;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addConstraint(new Callback(array(
methods => array(isAuthorValid),
)));
}
}

In questo caso, sar richiamato il metodo statico isAuthorValid della classe


Acme\BlogBundle\MyStaticValidatorClass. Gli verr passato sia loggetto originale in corso di validazione (p.e. Author) che ExecutionContext:
namespace Acme\BlogBundle;
use Symfony\Component\Validator\ExecutionContext;
use Acme\BlogBundle\Entity\Author;
class MyStaticValidatorClass
{
static public function isAuthorValid(Author $author, ExecutionContext $context)
{
// ...
}
}

Tip: Se si specifica il vincolo Callback tramite PHP, c anche lopzione di rendere il callback una closure PHP o un callback non statico. Tuttavia, non attualmente possibile specificare
un servizio come vincolo. Per validare usando un servizio, si dovrebbe creare un vincolo personalizzato e aggiungere il nuovo vincolo alla propria classe.

All
Quando applicato a un array (o a un oggetto Traversable), questo vincolo consente di applicare un insieme di
vincoli a ogni elemento dellarray.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\All
Symfony\Component\Validator\Constraints\AllValida

vincoli

Uso di base

Si supponga di avere un array di stringhe e di voler validare ogni elemento dellarray:


YAML
# src/UserBundle/Resources/config/validation.yml
Acme\UserBundle\Entity\User:
properties:
favoriteColors:
- All:
- NotBlank: ~
- MinLength: 5

Annotations

576

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class User
{
/**
* @Assert\All({
@Assert\NotBlank
*
@Assert\MinLength(5),
*
* })
*/
protected $favoriteColors = array();
}

Ora, ogni elemento nellarray favoriteColors sar validato per non essere vuoto e per avere almeno 5 caratteri.
Opzioni

vincoli

tipo: array [opzione predefinita]

Questa opzione obbligatoria larray dei vincoli di validazione che si vuole applicre a ogni elemento dellarray
sottostante.
UserPassword
New in version 2.1. Valida che un valore inserito sia uguale alla password dellutente attualmente autenticato. Pu
tornare utile in un form in cui un utente pu cambiare la propria password, ma deve inserire quella vecchia per
motivi di sicurezza.
Note: Questo vincolo non va usato per la validazione di un form di login, che viene fatta automaticamente dal
sistema di sicurezza.
Se applicato a un array (o a un oggetto Traversable), questo vincolo consente di applicare un insieme di
vincoli a ogni elemento dellarray.
Si applica a
Opzioni

propriet o metodo

Classe
Validatore

Symfony\Component\Validator\Constraints\UserPassw
Symfony\Bundle\SecurityBundle\Validator\Constrain

message

Uso di base

Si supponga di avere una classe PasswordChange, usata in un form in cui lutente possa cambiare la sua password,
inserendo la sua vecchia password e quella nuova. Questo vincolo valida che la vecchia password corrisponda alla
password attuale dellutente:
YAML
# src/UserBundle/Resources/config/validation.yml
Acme\UserBundle\Form\Model\ChangePassword:
properties:
oldPassword:
- UserPassword:
message: "Wrong value for your current password"

5.1. Documenti di riferimento

577

Symfony2 documentation Documentation, Release 2

Annotations
// src/Acme/UserBundle/Form/Model/ChangePassword.php
namespace Acme\UserBundle\Form\Model;
use Symfony\Component\Validator\Constraints as Assert;
class ChangePassword
{
/**
* @Assert\UserPassword(
message = "Wrong value for your current password"
*
* )
*/
protected $oldPassword;
}

Opzioni

message

tipo: message predefinito: This value should be the user current password

Messaggio mostrato quando la stringa sottostante non corrisponde alla password attuale dellutente.
Valid
Questo vincolo usato per abilitare la validazione su oggetti che sono inseriti come propriet su un oggetto in
corso di validazione. Consente di validare un oggetto e tutti i sotto-oggetti a esso associati.
Si applica a
Opzioni

propriet o metodo

Classe

Symfony\Component\Validator\Constraints\Type

traverse

Uso di base

Nel seguente esempio, vengono create le due classi Author e Address, entrambe con vincoli sulle loro propriet. Inoltre, Author memorizza unistanza di Address nella propriet $address.
// src/Acme/HelloBundle/Address.php
class Address
{
protected $street;
protected $zipCode;
}
// src/Acme/HelloBundle/Author.php
class Author
{
protected $firstName;
protected $lastName;
protected $address;
}

YAML
# src/Acme/HelloBundle/Resources/config/validation.yml
Acme\HelloBundle\Address:
properties:
street:

578

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

- NotBlank: ~
zipCode:
- NotBlank: ~
- MaxLength: 5
Acme\HelloBundle\Author:
properties:
firstName:
- NotBlank: ~
- MinLength: 4
lastName:
- NotBlank: ~

XML
<!-- src/Acme/HelloBundle/Resources/config/validation.xml -->
<class name="Acme\HelloBundle\Address">
<property name="street">
<constraint name="NotBlank" />
</property>
<property name="zipCode">
<constraint name="NotBlank" />
<constraint name="MaxLength">5</constraint>
</property>
</class>
<class name="Acme\HelloBundle\Author">
<property name="firstName">
<constraint name="NotBlank" />
<constraint name="MinLength">4</constraint>
</property>
<property name="lastName">
<constraint name="NotBlank" />
</property>
</class>

Annotations
// src/Acme/HelloBundle/Address.php
use Symfony\Component\Validator\Constraints as Assert;
class Address
{
/**
* @Assert\NotBlank()
*/
protected $street;
/**
* @Assert\NotBlank
* @Assert\MaxLength(5)
*/
protected $zipCode;
}
// src/Acme/HelloBundle/Author.php
class Author
{
/**
* @Assert\NotBlank
* @Assert\MinLength(4)
*/
protected $firstName;

5.1. Documenti di riferimento

579

Symfony2 documentation Documentation, Release 2

/**
* @Assert\NotBlank
*/
protected $lastName;
protected $address;
}

PHP
// src/Acme/HelloBundle/Address.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\MaxLength;
class Address
{
protected $street;
protected $zipCode;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(street, new NotBlank());
$metadata->addPropertyConstraint(zipCode, new NotBlank());
$metadata->addPropertyConstraint(zipCode, new MaxLength(5));
}
}
// src/Acme/HelloBundle/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\MinLength;
class Author
{
protected $firstName;
protected $lastName;
protected $address;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(firstName, new NotBlank());
$metadata->addPropertyConstraint(firstName, new MinLength(4));
$metadata->addPropertyConstraint(lastName, new NotBlank());
}
}

Con questa mappatura, possibile validare con successo un autore con un indirizzo non valido. Per prevenire
questo problema, aggiungere il vincolo Valid alla propriet $address.
YAML
# src/Acme/HelloBundle/Resources/config/validation.yml
Acme\HelloBundle\Author:
properties:
address:
- Valid: ~

XML

580

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

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


<class name="Acme\HelloBundle\Author">
<property name="address">
<constraint name="Valid" />
</property>
</class>

Annotations
// src/Acme/HelloBundle/Author.php
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/* ... */
/**
* @Assert\Valid
*/
protected $address;
}

PHP
// src/Acme/HelloBundle/Author.php
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\Valid;
class Author
{
protected $address;
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint(address, new Valid());
}
}

Se ora si valida un autore con indirizzo non valido, si pu vedere che la validazione dei campi Address non
passa.
AcmeHelloBundleAuthor.address.zipCode: This value is too long. It should have 5 characters or less
Opzioni

traverse

tipo: stringa predefinito: true

Se questo vincolo applicato a una propriet che contiene un array di oggetti, allora ogni oggetto in tale array sar
validato solo se questa opzione true.
Il validatore progettato per validare oggetto in base a vincoli. Nella vita reale, un vincolo potrebbe essere: la
torta non deve essere bruciata. In Symfony2, i vincoli sono simili: sono asserzioni che una condizione sia vera.
Vincoli supportati
I seguenti vincoli sono disponibili nativamente in Symfony2:
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.
5.1. Documenti di riferimento

581

Symfony2 documentation Documentation, Release 2

NotBlank
Blank
NotNull
Null
True
False
Type
Vincoli stringhe

Email
MinLength
MaxLength
Url
Regex
Ip
Vincoli numerici

Max
Min
Vincoli date

Date
DateTime
Time
Vincoli di insiemi

Choice
Collection
UniqueEntity
Language
Locale
Country
Vincoli di file

File
Image

582

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Altri vincoli

Callback
All
UserPassword
Valid

5.1.12 I tag della dependency injection


Tag:
data_collector
form.type
form.type_extension
form.type_guesser
kernel.cache_warmer
kernel.event_listener
kernel.event_subscriber
monolog.logger
monolog.processor
templating.helper
routing.loader
translation.loader
twig.extension
validator.initializer
Abilitare helper di template personalizzati in PHP
Per abilitare un helper di template personalizzato, aggiungerlo come normale servizio in una delle proprie configurazioni, taggarlo con templating.helper e definire un attributo alias (lhelper sar accessibile nei
template tramite questo alias):
YAML
services:
templating.helper.your_helper_name:
class: Fully\Qualified\Helper\Class\Name
tags:
- { name: templating.helper, alias: alias_name }

XML
<service id="templating.helper.your_helper_name" class="Fully\Qualified\Helper\Class\Name">
<tag name="templating.helper" alias="alias_name" />
</service>

PHP
$container
->register(templating.helper.your_helper_name, Fully\Qualified\Helper\Class\Name)
->addTag(templating.helper, array(alias => alias_name))
;

5.1. Documenti di riferimento

583

Symfony2 documentation Documentation, Release 2

Abilitare estensioni personalizzate in Twig


Per abilitare unestensione di Twig, aggiungerla come normale servizio in una delle proprie configurazioni e
taggarla con twig.extension:
YAML
services:
twig.extension.your_extension_name:
class: Fully\Qualified\Extension\Class\Name
tags:
- { name: twig.extension }

XML

<service id="twig.extension.your_extension_name" class="Fully\Qualified\Extension\Class\Name"


<tag name="twig.extension" />
</service>

PHP
$container
->register(twig.extension.your_extension_name, Fully\Qualified\Extension\Class\Name)
->addTag(twig.extension)
;

Per informazioni su come creare la classe estensione di Twig, vedere la documentazione di Twig sullargomento.
Prima di scrivere la propria estensione, dare unocchiata al repository ufficiale di estensioni Twig , che include
tante estensioni utili. Per esempio, Intl e il suo filtro localizeddate, che formatta una data a seconda del
locale dellutente. Anche queste estensioni ufficiali devono essere aggiunte come servizi:
YAML
services:
twig.extension.intl:
class: Twig_Extensions_Extension_Intl
tags:
- { name: twig.extension }

XML
<service id="twig.extension.intl" class="Twig_Extensions_Extension_Intl">
<tag name="twig.extension" />
</service>

PHP
$container
->register(twig.extension.intl, Twig_Extensions_Extension_Intl)
->addTag(twig.extension)
;

Abilitare ascoltatori personalizzati


Per abilitare un ascoltatore personalizzato, aggiungerlo come normale servizio in una della proprie configurazioni
e taggarlo con kernel.event_listener. Bisogna fornire il nome dellevento che il proprio ascolta, come
anche il metodo che sar richiamato:
YAML
services:
kernel.listener.your_listener_name:
class: Fully\Qualified\Listener\Class\Name

584

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

tags:
- { name: kernel.event_listener, event: xxx, method: onXxx }

XML

<service id="kernel.listener.your_listener_name" class="Fully\Qualified\Listener\Class\Name">


<tag name="kernel.event_listener" event="xxx" method="onXxx" />
</service>

PHP
$container
->register(kernel.listener.your_listener_name, Fully\Qualified\Listener\Class\Name)
->addTag(kernel.event_listener, array(event => xxx, method => onXxx))
;

Note: Si pu anche specificare una priorit, come attributo del tag kernel.event_listener (probabilmente
il metodo o gli attributi dellevento), con un intero positivo oppure negativo. Questo consente di assicurarsi che i
propri ascoltatori siano sempre richiamati prima o dopo altri ascoltatori che ascoltano lo stesso evento.

Abilitare sottoscrittori personalizzati


New in version 2.1. Per abilitare un sottoscrittore personalizzato, aggiungerlo come normale servizio in una delle
proprie configurazioni, con il tag kernel.event_subscriber:
YAML
services:
kernel.subscriber.your_subscriber_name:
class: Fully\Qualified\Subscriber\Class\Name
tags:
- { name: kernel.event_subscriber }

XML

<service id="kernel.subscriber.your_subscriber_name" class="Fully\Qualified\Subscriber\Class\


<tag name="kernel.event_subscriber" />
</service>

PHP

$container
->register(kernel.subscriber.your_subscriber_name, Fully\Qualified\Subscriber\Class\Na
->addTag(kernel.event_subscriber)
;

Note: Il servizio deve implementare linterfaccia SymfonyComponentEventDispatcherEventSubscriberInterface

Note: Se il servizio creato da un factory, si DEVE impostare correttamente il parametro class per questo tag,
per far s che funzioni correttamente.

Abilitare motori di template personalizzati


Per abilitare un motore di template personalizzato, aggiungerlo come normale servizio in una delle proprie configurazioni, con il tag templating.engine:
YAML

5.1. Documenti di riferimento

585

Symfony2 documentation Documentation, Release 2

services:
templating.engine.your_engine_name:
class: Fully\Qualified\Engine\Class\Name
tags:
- { name: templating.engine }

XML
<service id="templating.engine.your_engine_name" class="Fully\Qualified\Engine\Class\Name">
<tag name="templating.engine" />
</service>

PHP
$container
->register(templating.engine.your_engine_name, Fully\Qualified\Engine\Class\Name)
->addTag(templating.engine)
;

Abilitare caricatori di rotte personalizzati


Per abilitare un caricatore personalizzato di rotte, aggiungerlo come normale servizio in una delle proprie configurazioni, con il tag routing.loader:
YAML
services:
routing.loader.your_loader_name:
class: Fully\Qualified\Loader\Class\Name
tags:
- { name: routing.loader }

XML
<service id="routing.loader.your_loader_name" class="Fully\Qualified\Loader\Class\Name">
<tag name="routing.loader" />
</service>

PHP
$container
->register(routing.loader.your_loader_name, Fully\Qualified\Loader\Class\Name)
->addTag(routing.loader)
;

Usare un canale di log personalizzato con Monolog


Monolog consente di condividere i suoi gestori tra diversi canali di log. Il servizio di log usa il canale app, ma si
pu cambiare canale quando si inietta il logger in un servizio.
YAML
services:
mio_servizio:
class: Fully\Qualified\Loader\Class\Name
arguments: [@logger]
tags:
- { name: monolog.logger, channel: acme }

XML

586

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

<service id="mio_servizio" class="Fully\Qualified\Loader\Class\Name">


<argument type="service" id="logger" />
<tag name="monolog.logger" channel="acme" />
</service>

PHP

$definition = new Definition(Fully\Qualified\Loader\Class\Name, array(new Reference(logger


$definition->addTag(monolog.logger, array(channel => acme));
$container->register(mio_servizio, $definition);;

Note: Funziona solo quando il servizio di log il parametro di un costruttore, non quando iniettato tramite
setter.

Aggiungere un processore per Monolog


Monolog consente di aggiungere processori nel logger o nei gestori, per aggiungere dati extra nelle registrazioni. Un processore riceve la registrazione come parametro e deve restituirlo dopo aver aggiunto dei dati
extra, nellattributo extra della registrazione.
Vediamo come poter usare IntrospectionProcessor per aggiungere file, riga, classe e metodo quando il
logger viene attivato.
Si pu aggiungere un processore globalmente:
YAML
services:
mio_servizio:
class: Monolog\Processor\IntrospectionProcessor
tags:
- { name: monolog.processor }

XML
<service id="mio_servizio" class="Monolog\Processor\IntrospectionProcessor">
<tag name="monolog.processor" />
</service>

PHP
$definition = new Definition(Monolog\Processor\IntrospectionProcessor);
$definition->addTag(monolog.processor);
$container->register(mio_servizio, $definition);

Tip: Se il proprio servizio non un metodo (che usa __invoke), si pu aggiungere lattributo method nel tag,
per usare un metodo specifico.
Si pu anche aggiungere un processore per un gestore specifico, usando lattributo handler:
YAML
services:
mio_servizio:
class: Monolog\Processor\IntrospectionProcessor
tags:
- { name: monolog.processor, handler: firephp }

XML

5.1. Documenti di riferimento

587

Symfony2 documentation Documentation, Release 2

<service id="mio_servizio" class="Monolog\Processor\IntrospectionProcessor">


<tag name="monolog.processor" handler="firephp" />
</service>

PHP
$definition = new Definition(Monolog\Processor\IntrospectionProcessor);
$definition->addTag(monolog.processor, array(handler => firephp);
$container->register(mio_servizio, $definition);

Si pu anche aggiungere un processore per uno specifico canale di log, usando lattributo channel. Registrer
il processore solo per il canale di log security, usato nel componente Security:
YAML
services:
mio_servizio:
class: Monolog\Processor\IntrospectionProcessor
tags:
- { name: monolog.processor, channel: security }

XML
<service id="mio_servizio" class="Monolog\Processor\IntrospectionProcessor">
<tag name="monolog.processor" channel="security" />
</service>

PHP
$definition = new Definition(Monolog\Processor\IntrospectionProcessor);
$definition->addTag(monolog.processor, array(channel => security);
$container->register(mio_servizio, $definition);

Note: Non si possono usare entrambi gli attributi handler e channel per lo stesso tag, perch i gestori sono
condivisi tra tutti i canali.

5.1.13 Requisiti per far girare Symfony2


Per far girare Symfony2, un sistema deve soddisfare un elenco di requisiti. Si pu verificare facilmente se un
sistema abbia tutti i requisiti, eseguendo web/config.php nella propria distribuzione di Symfony. Poich la
CLI spesso usa un file di configurazione php.ini diverso, una buona idea verificare i requisiti anche tramite
riga di comando, con:
php app/check.php

Di seguito la lista dei requisiti e dei requisiti opzionali.


Requisiti
PHP deve essere almeno alla versione 5.3.2
Sqlite3 deve essere abilitato
JSON deve essere abilitato
ctype deve essere abilitato
php.ini deve avere limpostazione date.timezone

588

Chapter 5. Documenti di riferimento

Symfony2 documentation Documentation, Release 2

Opzionali
Occorre avere il modulo PHP-XML installato
Occorre avere almeno la versione 2.6.21 di libxml
Il tokenizer di PHP deve essere abilitato
Le funzioni mbstring devono essere abilitate
iconv deve essere abilitato
POSIX deve essere abilitato (solo su *nix)
Intl deve essere installato con ICU 4+
APC 3.0.17+ (o unaltra cache di opcode) deve essere installato
Impostazioni raccomandate di php.ini
short_open_tag = Off
magic_quotes_gpc = Off
register_globals = Off
session.autostart = Off
Doctrine
Se si vuole usare Doctrine, bisogna avere PDO installato. Inoltre, bisogna avere installato il driver PDO per il
database che si vuole utilizzare.
Opzioni di configurazione:
Vi siete mai chiesti quali opzioni di configurazione sono disponibili nei file come
app/config/config.yml? In questa sezione, tutte le configurazioni disponibili sono elencate
per ogni voce (per esempio framework) che definisce ogni possibile sezione della configurazione di
Symfony2.
framework
doctrine
security
assetic
swiftmailer
twig
monolog
web_profiler
Form e validazione
Riferimenti dei tipi di campo dei form
Riferimenti dei vincoli di validazione
Riferimenti delle funzioni dei template di Twig
Altre aree
I tag della dependency injection
Requisiti per far girare Symfony2

5.1. Documenti di riferimento

589

Symfony2 documentation Documentation, Release 2

Opzioni di configurazione:
Vi siete mai chiesti quali opzioni di configurazione sono disponibili nei file come
app/config/config.yml? In questa sezione, tutte le configurazioni disponibili sono elencate
per ogni voce (per esempio framework) che definisce ogni possibile sezione della configurazione di
Symfony2.
framework
doctrine
security
assetic
swiftmailer
twig
monolog
web_profiler
Form e validazione
Riferimenti dei tipi di campo dei form
Riferimenti dei vincoli di validazione
Riferimenti delle funzioni dei template di Twig
Altre aree
I tag della dependency injection
Requisiti per far girare Symfony2

590

Chapter 5. Documenti di riferimento

CHAPTER

SIX

BUNDLE
Ledizione standard di Symfony distribuita con alcuni bundle. Approfondimenti:

6.1 Bundle di Symfony SE


6.1.1 SensioFrameworkExtraBundle
Il bundle FrameworkBundle di Symfony2 implementa un framework MVC semplice, ma robusto. Il bundle
SensioFrameworkExtraBundle lo estende, aggiungendo utili convenzioni e annotazioni. Inoltre consente controllori pi concisi.
Installazione
Scaricare il bundle e metterlo sotto lo spazio dei nomi Sensio\Bundle\. Quindi, come per ogni altro bundle,
includerlo nella propria classe Kernel:
public function registerBundles()
{
$bundles = array(
...
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
);
...
}

Configurazione
Tutte le caratteristiche fornite dal bundle sono abilitate in modo predefinito alla registrazione del bundle stesso
nella propria classe Kernel.
La configurazione predefinita la seguente:
YAML
sensio_framework_extra:
router: { annotations: true }
request: { converters: true }
view:
{ annotations: true }
cache:
{ annotations: true }

XML

591

Symfony2 documentation Documentation, Release 2

<!-- xmlns:sensio-framework-extra="http://symfony.com/schema/dic/symfony_extra" -->


<sensio-framework-extra:config>
<router annotations="true" />
<request converters="true" />
<view annotations="true" />
<cache annotations="true" />
</sensio-framework-extra:config>

PHP
// load the profiler
$container->loadFromExtension(sensio_framework_extra, array(
router => array(annotations => true),
request => array(converters => true),
view
=> array(annotations => true),
cache
=> array(annotations => true),
));

Si possono disabilitare alcune annotazioni e convenzioni, definendo una o pi impostazioni come false.
Annotazioni per i controllori
Le annotazioni sono un bel modo per configurare facilmente i proprio controllori, dalle rotte alla configurazione
della cache.
Anche se le annotazioni non sono una caratteristica nativa di PHP, hanno comunque molti vantaggi rispetto ai
metodi classici di configurazione di Symfony2:
Il codice e la configurazione sono nello stesso posto (la classe del controllore);
Semplici da imparare e da usare;
Concise da scrivere;
Rendono snello il controllore (lasciandogli solo la responsabilit di prendere i dati dal modello).
Tip: Se si usano classi per le viste, le annotazioni sono un bel modo per evitare la creazioni di tali classi, per i
casi pi comuni.
Il bundle definisce le seguenti annotazioni:
@Route e @Method

Utilizzo Lannotazione @Route mappa lo schema di una rotta con un controllore:


use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class PostController extends Controller
{
/**
* @Route("/")
*/
public function indexAction()
{
// ...
}
}

Lazione index del controllore Post viene mappata sullURL /. Ci equivale alla seguente configurazione
YAML:

592

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

blog_home:
pattern: /
defaults: { _controller: SensioBlogBundle:Post:index }

Come ogni altro schema di rotta, si possono definire segnaposto, requisiti e valori predefiniti:
/**
* @Route("/{id}", requirements={"id" = "\d+"}, defaults={"foo" = "bar"})
*/
public function showAction($id)
{
}

Si pu anche far corisspondere la rotta a pi di un URL, definendo annotazioni @Route:


/**
* @Route("/", defaults={"id" = 1})
* @Route("/{id}")
*/
public function showAction($id)
{
}

Attivazione Le rotte devono essere importate, per essere attive come ogni altra risorsa di rotta (notare il tipo
annotation):
# app/config/routing.yml
# importa rotte da una classe controllore
post:
resource: "@SensioBlogBundle/Controller/PostController.php"
type:
annotation

Si pu anche importare unintera cartella:


# importa rotte da una cartella di controllori
blog:
resource: "@SensioBlogBundle/Controller"
type:
annotation

Come per ogni altra risorsa, si possono montare le rotte sotto un prefisso:
post:
resource: "@SensioBlogBundle/Controller/PostController.php"
prefix:
/blog
type:
annotation

Nome della rotta Una rotta definita con lannotazione @Route riceve un nome predefinito, composto dal
nome del bundle, il nome del controllore e il nome dellazione. Per lesempio precedente, il nome
sensio_blog_post_index.
Si pu usare lattributo name, per ridefinire il nome predefinito:
/**
* @Route("/", name="blog_home")
*/
public function indexAction()
{
// ...
}

6.1. Bundle di Symfony SE

593

Symfony2 documentation Documentation, Release 2

Prefisso della rotta Unannotazione @Route in una classe controllore definisce un prefisso per le rotte di tutte
le azioni:
/**
* @Route("/blog")
*/
class PostController extends Controller
{
/**
* @Route("/{id}")
*/
public function showAction($id)
{
}
}

Lazione show mappata sullo schema /blog/{id}.


Metodo della rotta C unannotazione scorciatoia @Method, per specificare il metodo HTTP consentito per
la rotta. Per usarlo, importare lo spazio dei nomi Method:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* @Route("/blog")
*/
class PostController extends Controller
{
/**
* @Route("/edit/{id}")
* @Method({"GET", "POST"})
*/
public function editAction($id)
{
}
}

Lazione edit mappata sullo schema /blog/edit/{id} se il metodo usato GET o POST.
Lannotazione @Method viene considerata solo se unazione annotata con @Route.
@ParamConverter

Utilizzo Lannotazione @ParamConverter richiama dei convertitori, per convertire parametri della richiesta
in oggetti. Tali oggetti sono memorizzati come attributi della richiesta e quindi possono essere iniettati come
parametri dei metodi del controllore:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* @Route("/blog/{id}")
* @ParamConverter("post", class="SensioBlogBundle:Post")
*/
public function showAction(Post $post)
{
}

Dietro le quinte, succedono diverse cose:


Il convertitore prova a prendere un oggetto SensioBlogBundle:Post dagli attributi della richiesta (gli
attributi della richiesta arrivano dai segnaposto delle rotte, in questo caso id);
594

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

Se non si trova nessun oggetto Post, viene generata una risposta 404;
Se si trova un oggetto Post, viene definito un nuovo attributo post della richiesta (accessibile tramite
$request->attributes->get(post));
Come ogni altro attributo della richiesta, iniettato automaticamente nel controllore, quando presente nella
firma del metodo.
Se si usa il type hinting, come nellesempio precedente, si pu anche omettere del tutto lannotazione
@ParamConverter:
// automatico, con la firma del metodo
public function showAction(Post $post)
{
}

Convertitori predefiniti Il bundle ha un solo convertitore predefinito, quello di Doctrine.


Convertitore Doctrine Il convertitore di Doctrine usa il gestore di entit predefinito. Si pu modificare tale
comportamento con lopzione entity_manager:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* @Route("/blog/{id}")
* @ParamConverter("post", class="SensioBlogBundle:Post", options={"entity_manager" = "foo"})
*/
public function showAction(Post $post)
{
}

Creare un convertitore Ogni convertitore deve implementare Sensio\Bundle\FrameworkExtraBundle\Request\Par


namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Symfony\Component\HttpFoundation\Request;
interface ParamConverterInterface
{
function apply(Request $request, ConfigurationInterface $configuration);
function supports(ConfigurationInterface $configuration);
}

Il metodo supports() deve restituire true quando in grando di convertire la configurazione data (unistanza
di ParamConverter).
Listanza ParamConverter ha tre informazioni sullannotazione:
name: Il nome dellattributo;
class: Il nome della classe dellattributo (pu essere una qualsiasi stringa che rappresenti il nome di una
classe);
options: Un array di opzioni
Il metodo apply() richiamato per ogni configurazione supportata. In base agli attributi della richiesta,
dovrebbe impostare un attributo chiamato $configuration->getName(), che memorizza un oggetto di
classe $configuration->getClass().
Tip: Si pu usare la classe DoctrineConverter come modello per i propri convertitori.
6.1. Bundle di Symfony SE

595

Symfony2 documentation Documentation, Release 2

@Template

Utilizzo Lannotazione @Template associa un controllore con un nome di template:


use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* @Template("SensioBlogBundle:Post:show")
*/
public function showAction($id)
{
// prendere il Post
$post = ...;
return array(post => $post);
}

Quando si usa lannotazione @Template, il controllore dovrebbe restituire un array di parametri da passare alla
vista, al posto delloggetto Response.
Tip: Se lazione restituisce un oggetto Response, lannotazione @Template viene semplicemente ignorata.
Se il template prende il suo nome da controllore e azione, che il caso dellesempio precedente, si pu anche
omettere il valore nellannotazione:
/**
* @Template
*/
public function showAction($id)
{
// prendere il Post
$post = ...;
return array(post => $post);
}

Inoltre, se gli unici parametri da passare al template sono parametri del metodo, si pu usare lattributo vars,
invece di restituire un array. Questo risulta molto utile in combinazione con l annotazione @ParamConverter:
/**
* @ParamConverter("post", class="SensioBlogBundle:Post")
* @Template("SensioBlogBundle:Post:show", vars={"post"})
*/
public function showAction(Post $post)
{
}

Che equivale, grazie alle convenzioni, alla configurazione seguente:


/**
* @Template(vars={"post"})
*/
public function showAction(Post $post)
{
}

Si pu sintetizzare ulteriormente, perch tutti i parametri del metodo sono passati automaticamente al template, se
il metodo restituisce null e nessun attributo vars stato definito:

596

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

/**
* @Template
*/
public function showAction(Post $post)
{
}

@Cache

Utilizzo Lannotazione @Cache rende facile la definizione della cache HTTP:


use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* @Cache(expires="tomorrow")
*/
public function indexAction()
{
}

Si pu anche usare lannotazione su una classe, per definire la cache per tutti i metodi:
/**
* @Cache(expires="tomorrow")
*/
class BlogController extends Controller
{
}

Se ci dovesse essere un conflitto tra la configurazione della classe e quella del metodo, lultima vincerebbe:
/**
* @Cache(expires="tomorrow")
*/
class BlogController extends Controller
{
/**
* @Cache(expires="+2 days")
*/
public function indexAction()
{
}
}

Attributi Ecco una lista di attributi validi, con i rispettivi header HTTP:
Annotazione
@Cache(expires="tomorrow")
@Cache(smaxage="15")
@Cache(maxage="15")

Metodo Response
$response->setExpires()
$response->setSharedMaxAge()
$response->setMaxAge()

Note: Lattributo expires accetta qualsiasi data valida interpretabile dalla funzione strtotime() di PHP.
Questo esempio mostra tutte le annotazioni disponibili in azione:
use
use
use
use
use

Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;

6.1. Bundle di Symfony SE

597

Symfony2 documentation Documentation, Release 2

/**
* @Route("/blog")
* @Cache(expires="tomorrow")
*/
class AnnotController extends Controller
{
/**
* @Route("/")
* @Template
*/
public function indexAction()
{
$posts = ...;
return array(posts => $posts);
}
/**
* @Route("/{id}")
* @Method("GET")
* @ParamConverter("post", class="SensioBlogBundle:Post")
* @Template("SensioBlogBundle:Annot:post", vars={"post"})
* @Cache(smaxage="15")
*/
public function showAction(Post $post)
{
}
}

Poich il metodo showAction segue alcune convenzioni, si possono omettere alcune annotazioni:
/**
* @Route("/{id}")
* @Cache(smaxage="15")
*/
public function showAction(Post $post)
{
}

6.1.2 SensioGeneratorBundle
Il bundle SensioGeneratorBundle estende linterfaccia a linea di comando di Symfony2, fornendo nuovi
comandi interattivi e intuitivi, per generare scheletri di codice, come bundle, classi di form o controllori CRUD
basati su uno schema di Doctrine 2.
Installazione
Scaricare il bundle e metterlo sotto lo spazio dei nomi Sensio\\Bundle\\. Quindi, come per ogni altro
bundle, includerlo nella propria classe Kernel:
public function registerBundles()
{
$bundles = array(
...
new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(),
);
...
}

598

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

Elenco dei comandi disponibili


Il bundle SensioGeneratorBundle dispone di quattro nuovi comandi, eseguibili in modalit interattiva o
meno. La modalit interattiva pone alcune domande, per configurare i parametri di generazione del codice.
Lelenco dei nuovi comandi il seguente:
Generare lo scheletro di un nuovo bundle

Utilizzo Il comando generate:bundle genera la struttura di un nuovo bundle, attivandolo automaticamente


nellapplicazione.
Per impostazione predefinita, il comando eseguito in modalit interattiva e pone domande volte a determinare
nome, posizione, formato di configurazione e struttura predefinita del bundle:
php app/console generate:bundle

Per disattivare la modalit interattiva, usare lopzione no-interaction, senza dimenticare di passare tutte le opzioni
necessarie:
php app/console generate:bundle --namespace=Acme/Bundle/BlogBundle --no-interaction

Opzioni disponibili
--namespace: Lo spazio dei nomi del bundle da creare. Lo spazio dei nomi dovrebbe iniziare con un
nome di venditore, come il nome della propria azienda, del proprio progetto o del cliente, seguito da uno
o pi sotto-spazi dei nomi, facoltativi, e terminare col nome del bundle stesso (che deve avere Bundle
come suffisso):
php app/console generate:bundle --namespace=Acme/Bundle/BlogBundle

--bundle-name: Il nomde del bundle, opzionale. Deve essere una stringa con suffisso Bundle:
php app/console generate:bundle --bundle-name=AcmeBlogBundle

--dir: La cartella in cui mettere il bundle. Per convenzione, il comando individua e usa la cartella src/
dellapplicazione:
php app/console generate:bundle --dir=/var/www/myproject/src

--format: (annotation) [valori: yml, xml, php o annotation] Determina il formato da usare per i file
di configurazione, come quello delle rotte. Il valore predefinito annotation. La scelta del formato
annotation si aspetta che SensioFrameworkExtraBundle sia gi installato:
php app/console generate:bundle --format=annotation

--structure: (no) [valori: yes|no] Se generare o meno una struttura di cartelle completa, incluse le
cartelle pubbliche vuote per la documentazione, per le risorse del web e per i dizionari delle traduzioni:
php app/console generate:bundle --structure=yes

Generare un controllore CRUD in base a un entit Doctrine

Utilizzo Il comando generate:doctrine:crud genera un controllore di base per una data entit, posta in
un dato bundle. Questo controllore consente di eseguire le cinque operazioni di base su un modello.
Elenco di tutte le righe,
Visualizzazioone di una riga data, identificata dalla sua chiave primaria,
Creazione di una nuova riga,
Modifica di una riga esistente,
6.1. Bundle di Symfony SE

599

Symfony2 documentation Documentation, Release 2

Cancellazione di una riga esistente.


Per impostazione predefinita, il comando gira in modalit interattiva e pone domande per determinare il nome
dellentit, il prefisso delle rotte e se generare o meno le azioni di scrittura:
php app/console generate:doctrine:crud

Per disattivare la modalit interattiva, usare lopzione no-interaction, senza dimenticare di passare tutte le opzioni
necessarie:

php app/console generate:doctrine:crud --entity=AcmeBlogBundle:Post --format=annotation --with-wri

Opzioni disponibili
--entity: Il nome dellentit, fornito nella notazione breve, contenente il nome del bundle che contiene
lentit e il nome dellentit stessa. Per esempio: AcmeBlogBundle:Post:
php app/console generate:doctrine:crud --entity=AcmeBlogBundle:Post

--route-prefix: Il prefisso da usare per ogni rotta che identifica unazione:


php app/console generate:doctrine:crud --route-prefix=acme_post

--with-write: (no) [valori: yes|no] Se generare o meno le azioni new, create, edit, update e delete:
php app/console generate:doctrine:crud --with-write

--format: (annotation) [valori: yml, xml, php o annotation] Determina il formato da usare per i file
di configurazione, come quelli delle rotte. Il valore predefinito annotation. La scelta del formato
annotation si aspetta che SensioFrameworkExtraBundle sia gi installato:
php app/console generate:doctrine:crud --format=annotation

Generare un nuovo stub per unentit Doctrine

Utilizzo Il comando generate:doctrine:entity genera un nuovo stub per unentit Doctrine, inclusa
la definizione della mappatura, le propriet delle classi, i getter e i setter.
Per impostazione predefinita, il comando gira in modalit interattiva e pone domande per determinare il nome del
bundle, la posizione, il formato di configurazione e la struttura:
php app/console generate:doctrine:entity

Il comando pu essere eseguito in modalit non interattiva, usando lopzione no-interaction, senza dimenticare
di passare tutte le opzioni necessarie:

php app/console generate:doctrine:entity --non-interaction --entity=AcmeBlogBundle:Post --fields="

Opzioni disponibili
--entity: Il nome dellentit, fornito nella notazione breve, contenente il nome del bundle che contiene
lentit e il nome dellentit stessa. Per esempio: AcmeBlogBundle:Post:
php app/console generate:doctrine:entity --entity=AcmeBlogBundle:Post

--fields: Lelenco dei campi da inserire nellentit:


php app/console generate:doctrine:entity --fields="title:string(100) body:text"

--format: (annotation) [valori: yml, xml, php o annotation] Determina il formato da usare per i file di
configurazione, come quelli delle rotte. Il valore predefinito annotation:

600

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

php app/console generate:doctrine:entity --format=annotation

--with-repository: Questa opzione dice se generare o meno la classe EntityRepository relativa:


php app/console generate:doctrine:entity --with-repository

Generare una nuova classe Typer per un form basato su unentit Doctrine

Utilizzo Il comando generate:doctrine:form genera una classe type di base per un form, usando i metadati di mappatura della classe dellentit fornita:
php app/console generate:doctrine:form AcmeBlogBundle:Post

Parametri obbligatori
entity: Il nome dellentit, fornito nella notazione breve, contenente il nome del bundle che contiene
lentit e il nome dellentit stessa. Per esempio: AcmeBlogBundle:Post:
php app/console generate:doctrine:form AcmeBlogBundle:Post

6.1.3 Panoramica
Questo bundle migliora il componente Security di Symfony2, aggiungendo molte caratteristiche.
Caratteristiche:
potente linguaggio di autorizzazioni basato su espressioni
autorizzazione metodo sicurezza
configurazione autorizzazioni tramite annotazioni
Installazione
Aggiungere le righe seguenti al proprio file deps:
[JMSSecurityExtraBundle]
git=https://github.com/schmittjoh/JMSSecurityExtraBundle.git
target=/bundles/JMS/SecurityExtraBundle
; Dependencies:
;-------------[metadata]
git=https://github.com/schmittjoh/metadata.git
version=1.1.0 ; <- make sure to get 1.1, not 1.0
; see https://github.com/schmittjoh/JMSAopBundle/blob/master/Resources/doc/index.rst
[JMSAopBundle]
git=https://github.com/schmittjoh/JMSAopBundle.git
target=/bundles/JMS/AopBundle
[cg-library]
git=https://github.com/schmittjoh/cg-library.git
; This dependency is optional (you need it if you are using non-service controllers):
; see https://github.com/schmittjoh/JMSDiExtraBundle/blob/master/Resources/doc/index.rst
[JMSDiExtraBundle]
git=https://github.com/schmittjoh/JMSDiExtraBundle.git
target=/bundles/JMS/DiExtraBundle

6.1. Bundle di Symfony SE

601

Symfony2 documentation Documentation, Release 2

Quindi registrare il bundle nel proprio Kernel:


// in AppKernel::registerBundles()
$bundles = array(
// ...
new JMS\AopBundle\JMSAopBundle(),
new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
new JMS\DiExtraBundle\JMSDiExtraBundle($this),
// ...
);

Assicurarsi anche di registrare gli spazi dei nomi nellautoloader:


// app/autoload.php
$loader->registerNamespaces(array(
// ...
JMS
=> __DIR__./../vendor/bundles,
Metadata
=> __DIR__./../vendor/metadata/src,
CG
=> __DIR__./../vendor/cg-library/src,
// ...
));

Configurazione
Quella che segue la configurazione predefinita:
# app/config/config.yml
jms_security_extra:
# Whether you want to secure all services (true), or only secure specific
# services (false); see also below
secure_all_services: false
# Enabling this setting will add an additional special attribute "IS_IDDQD".
# Anybody with this attribute will effectively bypass all security checks.
enable_iddqd_attribute: false
# Enables expression language
expressions: false
# Allows you to disable some, or all built-in voters
voters:
disable_authenticated: false
disable_role:
false
disable_acl:
false
# Allows you to specify access control rules for specific methods, such
# as controller actions
method_access_control: { }

Linguaggio di autorizzazione basato su espressioni


Il linguaggio di espressioni unalternativa molto potente ai semplici attributi del sistema di voti di sicurezza.
Consente lesecuzioni di verifiche complesse sugli accessi e, essendo compilato in PHP puro, molto pi veloce
dei voti. Inoltre, sfrutta il caricamento pigro, quindi si salveranno anche un po di risorse, per esempio non
dovendo inizializzare lintero sistema di ACL a ogni richiesta.
Uso programmatico

Si possono eseguire le espressioni in modo programmatico, usando il metodo isGranted di SecurityContext.


Alcuni esempi:
602

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

use JMS\SecurityExtraBundle\Security\Authorization\Expression\Expression;
$securityContext->isGranted(array(new
$securityContext->isGranted(array(new
$securityContext->isGranted(array(new
$securityContext->isGranted(array(new

Expression(hasRole("A"))));
Expression(hasRole("A") or (hasRole("B") and hasRole("C"))
Expression(hasPermission(object, "VIEW")), $object));
Expression(token.getUsername() == "Johannes")));

Uso in Twig

Si possono verificare espressioni dai template Twig, usando la funzione is_expr_granted. Alcuni esempi:
is_expr_granted("hasRole(FOO)")
is_expr_granted("hasPermission(object, VIEW)", object)

Uso in access_control

Si possono usare espressioni anche in access_control:


security:
access_control:
- { path: ^/foo, access: "hasRole(FOO) and hasRole(BAR)" }

Uso basato su annotazioni

vedere @PreAuthorize nel riferimento sulle annotazioni


Riferimento

Espressione
hasRole(ROLE)
hasAnyRole(ROLE1,
ROLE2, ...)
isAnonymous()
isRememberMe()
isFullyAuthenticated()
isAuthenticated()
hasPermission(var,
PERMISSION)
token
user
object
#*paramName*
and / &&
or / ||
==
not / !

Descrizione
Verifica se il token ha un certo ruolo.
Verifica se il token ha uno qualsiasi dei ruoli dati.
Verifica se il token anonimo.
Verifica se il token remember me.
Verifica se il token pienamente autenticato.
Verifica se il token non anonimo.
Verifica se il token ha il permesso dato per loggetto dato (richiede il sistema
ACL).
Variabile riferita al token attualmente nel contesto della sicurezza.
Variabile riferita allutente attualmente nel contesto della sicurezza.
Variabile riferita alloggetto per cui laccesso stato richiesto.
Un identificatore preceduto da # si riferisce a un parametro con lo stesso nome
passato al metodo in cui lespr. usata.
Operatore binario e
Operatore binario o
Operatore binario uguale
Operatore di negazione

Autorizzazione con metodo di sicurezza


Di solito, si possono proteggere tutti i metodi public o protected che non siano static o final. I metodi privati non
possono essere protetti. Si possono anche aggiungere meta-dati per metodi astratti o interfacce, che saranno poi
applicati alle loro effettive implementazioni in modo automatico.

6.1. Bundle di Symfony SE

603

Symfony2 documentation Documentation, Release 2

Controllo degli accessi tramite configurazione DI

Si possono specificare espressioni di controllo accessi nella configurazione DI:


# config.yml
jms_security_extra:
method_access_control:
:loginAction$: isAnonymous()
AcmeFooBundle:.*:deleteAction: hasRole("ROLE_ADMIN")
^MyNamespace\MyService::foo$: hasPermission(#user, "VIEW")

Lo schema unespressione regolare sensibile alle maiuscole, che viene verificata con due notazioni. Viene
usata la prima corrispondenza. Lo schema viene prima verificato con la notazione per i controllori che
non siano servizi. Ovviamente, questo viene fatto se la propria classe effettivamente un controllore, p.e.
AcmeFooBundle:Add:new per un controllore AddController e un metodo newAction in un sottospazio dei nomi Controller in un bundle AcmeFooBundle.
Poi, lo schema viene verificato con la concatenazione del nome della classe e del metodo che si sta richiamando,
p.e. Mia\Classe\Pienamente\Qualificata::mioMetodo.
Nota: Se si vogliono proteggere controlli che non siano servizi, si deve installare JMSDiExtraBundle.
Controllo degli accessi tramite annotazioni

Se si vuole proteggere un servizio con le annotazioni, occorre abilitare la configurazione delle annotazioni per tale
servizio:
<service id="foo" class="Bar">
<tag name="security.secure_service"/>
</service>

In caso si vogliano configurare tutti i servizi tramite le annotazioni, si pu anche impostare


secure_all_services a true. Non sar quindi necessario aggiungere un tag per ogni servizio.
Annotazioni
@PreAuthorize

Questa annotazione consente di definire unespressione (si veda il paragrafo sul linguaggio delle espressioni),
eseguita prima di invocare un metodo:
<?php
use JMS\SecurityExtraBundle\Annotation\PreAuthorize;
class MyService
{
/** @PreAuthorize("hasRole(A) or (hasRole(B) and hasRole(C))") */
public function secureMethod()
{
// ...
}
}

@Secure

Questa annotazione consente di definire a chi consentito invocare un metodo:

604

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

<?php
use JMS\SecurityExtraBundle\Annotation\Secure;
class MyService
{
/**
* @Secure(roles="ROLE_USER, ROLE_FOO, ROLE_ADMIN")
*/
public function secureMethod()
{
// ...
}
}

@SecureParam

Questa annotazione consente di definire restrizioni per i parametri passsati al metodo. utile solo se i parametri
sono oggetti del dominio:
<?php
use JMS\SecurityExtraBundle\Annotation\SecureParam;
class MyService
{
/**
* @SecureParam(name="comment", permissions="EDIT, DELETE")
* @SecureParam(name="post", permissions="OWNER")
*/
public function secureMethod($comment, $post)
{
// ...
}
}

@SecureReturn

Questa annotazione consente di definire restrizioni per il valore restituito dal metodo. utile solo se il valore
restituito un oggetto del dominio:
<?php
use JMS\SecurityExtraBundle\Annotation\SecureReturn;
class MyService
{
/**
* @SecureReturn(permissions="VIEW")
*/
public function secureMethod()
{
// ...
return $domainObject;
}
}

6.1. Bundle di Symfony SE

605

Symfony2 documentation Documentation, Release 2

@RunAs

Questa annotazione consente di specificare ruoli da aggiungere solo per la durata dellinvocazione del metodo.
Tali ruoli non saranno presi in considerazione per decisioni di accesso precedenti o successive allinvocazione.
Solitamente si usa per implementare un livelli di servizio doppio, in cui si hanno servizi pubblici e privati e in cui
i servizi privati devono essere invocati solamente tramite uno specifico servizio pubblico:
<?php
use JMS\SecurityExtraBundle\Annotation\Secure;
use JMS\SecurityExtraBundle\Annotation\RunAs;
class MyPrivateService
{
/**
* @Secure(roles="ROLE_PRIVATE_SERVICE")
*/
public function aMethodOnlyToBeInvokedThroughASpecificChannel()
{
// ...
}
}
class MyPublicService
{
protected $myPrivateService;
/**
* @Secure(roles="ROLE_USER")
* @RunAs(roles="ROLE_PRIVATE_SERVICE")
*/
public function canBeInvokedFromOtherServices()
{
return $this->myPrivateService->aMethodOnlyToBeInvokedThroughASpecificChannel();
}
}

@SatisfiesParentSecurityPolicy

Deve essere definito su un metodo che sovrascrive un altro metodo, che ha meta-dati di sicurezza. Serve ad
assicurare la consapevolezza che la sicurezza del metodo sovrascritto non pu pi essere assicurata e che si devono
copiare tutte le annotazioni che si vogliono mantenere.

6.1.4 DoctrineFixturesBundle
Le fixture sono usate per caricare un insieme controllato di dati in una base dati. Tali dati possono essere usati
per i test o potrebbero essere i dati iniziali necessari a una applicazione per essere eseguita. Symfony2 non ha un
modo predefinito per gestire le fixture, ma Doctrine2 ha una libreria che aiuta a scrivere fixture per lORM o per
lODM.
Configurazione
Se non si ancora configurata la libreria Doctrine Data Fixtures con Symfony2, seguire questi tre passi.
Se si usa la Standard Distribution, aggiungere al proprio file deps:

606

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

[doctrine-fixtures]
git=http://github.com/doctrine/data-fixtures.git
[DoctrineFixturesBundle]
git=http://github.com/symfony/DoctrineFixturesBundle.git
target=/bundles/Symfony/Bundle/DoctrineFixturesBundle

Aggiornare le librerie dei venditori:


$ php bin/vendors install

Se tutto ha funzionato,
si
vendor/doctrine-fixtures.

pu

ora

trovare

la

libreria

doctrine-fixtures

sotto

Registrare lo spazio dei nimi Doctrine\Common\DataFixtures in app/autoload.php.


// ...
$loader->registerNamespaces(array(
// ...
Doctrine\\Common\\DataFixtures => __DIR__./../vendor/doctrine-fixtures/lib,
Doctrine\\Common => __DIR__./../vendor/doctrine-common/lib,
// ...
));

Caution: Assicurarsi di registrare il nuovo spazio dei nomi prima di Doctrine\Common. In caso contrario,
Symfony cercher le classi delle fixture nella cartella Doctrine\Common. Lautoloader di Symfony cerca
sempre una classe nella cartella del primo spazio dei nomi corrispondente, quindi spazi dei nomi pi specifici
vanno sempre messi prima.
Infine, registrare il bundle DoctrineFixturesBundle in app/AppKernel.php.
// ...
public function registerBundles()
{
$bundles = array(
// ...
new Symfony\Bundle\DoctrineFixturesBundle\DoctrineFixturesBundle(),
// ...
);
// ...
}

Scrivere semplici fixture


Le fixture di Doctrine2 sono classi PHP, in cui si possono creare oggetti e persisterli nel database. Come tutte le
classi in Symfony2, le fixture dovrebbero stare allinterno di bundle della propria applicazione.
Per
un
bundle
in
src/Acme/HelloBundle,
le
classi
delle
fixture
dovrebbero
stare
in
src/Acme/HelloBundle/DataFixtures/ORM
oppure
in
src/Acme/HelloBundle/DataFixtures/MongoDB, rispettivamente per ORM e ODM. Questa
guida ipotizza che si stia usando lORM, ma le fixture si possono aggiungere altrettanto facilmente usando
lODM.
Si immagini di avere una classe User e di voler caricare un oggetto User:
// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php
namespace Acme\HelloBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Acme\HelloBundle\Entity\User;
class LoadUserData implements FixtureInterface

6.1. Bundle di Symfony SE

607

Symfony2 documentation Documentation, Release 2

{
public function load($manager)
{
$userAdmin = new User();
$userAdmin->setUsername(admin);
$userAdmin->setPassword(test);
$manager->persist($userAdmin);
$manager->flush();
}
}

In Doctrine2, le fixture sono semplici oggetti, in cui caricare dati tramite interazioni con le proprie entit, come si
fa normalmente. Ci consente di creare esattamente le fixture necessarie per la propria applicazione.
Il limite pi grosso in questo approccio limpossibilit di condividere oggetti tramite le fixture. Pi avanti,
vedremo come superare questo limite.
Eseguire le fixture
Una volta scritte le fixture, si possono caricarle tramite la linea di comando, usando il comando
doctrine:fixtures:load:
php app/console doctrine:fixtures:load

Se si usa lODM, usare invece il comando doctrine:mongodb:fixtures:load:


php app/console doctrine:mongodb:fixtures:load

Il comando cercher nella cartella DataFixtures/ORM (o DataFixtures/MongoDB per lODM) di ogni


bundle ed eseguir ogni classe che implementa FixtureInterface.
Entrambi i comandi hanno delle opzioni:
--fixtures=/path/to/fixture - Usare questa opzione per specificare manualmente la cartella in
cui le classi delle fixture vanno caricate;
--append - Usare questo flag per appendere dati, invece di cancellare e ricaricare i dati (la cancellazione
il comportamento predefinito);
--em=manager_name - Specifica manualmente il gestore di entit da usare per caricare i dati.
Note: Se si usa il comando doctrine:mongodb:fixtures:load, sostituire lopzione --em= con
--dm=, per specificare manualmente il gestore di documenti.
Un esempio completo potrebbe assomigliare a questo:

php app/console doctrine:fixtures:load --fixtures=/percorso/di/fixture1 --fixtures=/percorso/di/fi

Condividere oggetti tra le fixture


La scrittura di fixture di base semplice. Ma se si hanno molte classi di fixture e si vuole poter fare riferimento a
dati caricati in altre fixture, cosa fare? Per esempio, se si vuole caricare un oggetto User in una fixture e poi si
vuole farvi riferimento in unaltra fixture, per poter assegnare quellutente a un determinato gruppo?
La libreria delle fixture di Doctrine gestisce facilmente questa situazione, consentendo di specificare lordine in
cui le fixture sono caricate.
// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php
namespace Acme\HelloBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;

608

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Acme\HelloBundle\Entity\User;
class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
{
public function load($manager)
{
$userAdmin = new User();
$userAdmin->setUsername(admin);
$userAdmin->setPassword(test);
$manager->persist($userAdmin);
$manager->flush();
$this->addReference(admin-user, $userAdmin);
}
public function getOrder()
{
return 1; // ordine in cui le fixture saranno caricate
}
}

La classe fixture ora implementa OrderedFixtureInterface, che dice a Doctrine che si vuole controllare
lordine delle fixture. Creare unaltra classe fixture e farla caricare dopo LoadUserData restituendo lordine 2:
// src/Acme/HelloBundle/DataFixtures/ORM/LoadGroupData.php
namespace Acme\HelloBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Acme\HelloBundle\Entity\Group;
class LoadGroupData extends AbstractFixture implements OrderedFixtureInterface
{
public function load($manager)
{
$groupAdmin = new Group();
$groupAdmin->setGroupName(admin);
$manager->persist($groupAdmin);
$manager->flush();
$this->addReference(admin-group, $groupAdmin);
}
public function getOrder()
{
return 2; // ordine in cui le fixture saranno caricate
}
}

Entrambe le classi fixture estendono AbstractFixture, che consente di creare oggetti e impostarli come riferimenti, in modo che possano successivamente essere usati in altre fixture. Per esempio, gli oggetti $userAdmin
e $groupAdmin possono essere riferiti successivamente tramite i riferimenti admin-user e admin-group:
// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserGroupData.php
namespace Acme\HelloBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Acme\HelloBundle\Entity\UserGroup;

6.1. Bundle di Symfony SE

609

Symfony2 documentation Documentation, Release 2

class LoadUserGroupData extends AbstractFixture implements OrderedFixtureInterface


{
public function load($manager)
{
$userGroupAdmin = new UserGroup();
$userGroupAdmin->setUser($manager->merge($this->getReference(admin-user)));
$userGroupAdmin->setGroup($manager->merge($this->getReference(admin-group)));
$manager->persist($userGroupAdmin);
$manager->flush();
}
public function getOrder()
{
return 3;
}
}

Questa fixture sar ora eseguite nellordine del valore restituito dal metodo getOrder(). Si pu accedere a ogni
oggetto impostato con il metodo setReference(), tramite il metodo getReference() in classi fixture con
un ordine pi alto.
Le fixture consentono di creare ogni tipo di dato necessario, tramite la normale interfaccia di PHP per creare
e persistere oggetti. Controllando lordine delle fixture e impostando dei riferimenti, si pu gestire quasi tutto
tramite fixture.
Usare il contenitore nelle fixture
In alcuni casi occorre accedere ad alcuni servizi, per caricare le fixture.
Symfony2 rende il
processo molto facile:
il contenitore sar iniettato in tutte le classi fixture che implementano
Symfony\Component\DependencyInjection\ContainerAwareInterface.
Riscriviamo la prima fixture per codificare la password, prima che sia memorizzata nel database (una buona
pratica). User il factory encoder per codificare la password, assicurando che sia codificata nello stesso modo
usato dal componente security quando si verifica:
// src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php
namespace Acme\HelloBundle\DataFixtures\ORM;
use
use
use
use

Doctrine\Common\DataFixtures\FixtureInterface;
Symfony\Component\DependencyInjection\ContainerAwareInterface;
Symfony\Component\DependencyInjection\ContainerInterface;
Acme\HelloBundle\Entity\User;

class LoadUserData implements FixtureInterface, ContainerAwareInterface


{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function load($manager)
{
$userAdmin = new User();
$userAdmin->setUsername(admin);
$userAdmin->setSalt(md5(time()));
$encoder = $this->container->get(security.encoder_factory)->getEncoder($userAdmin);
$userAdmin->setPassword($encoder->encodePassword(test, $userAdmin->getSalt()));

610

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

$manager->persist($userAdmin);
$manager->flush();
}
}

Come si pu vedere, occorre solo aggiungere ContainerAwareInterface alla classe e poi creare un nuovo
metodo setContainer(), che implementi tale interfaccia. Prima che la fixture sia eseguita, Symfony richiamer il metodo setContainer() automaticamente. Se si memorizza il contenitore come propriet della
classe, come mostrato sopra, vi si pu accedere nel metodo load().

6.1.5 DoctrineMigrationsBundle
Le migrazioni dei database sono unestensione del livello di astrazione del database e offrono lopportunit di fare
deploy programmatici di nuove versioni del proprio schema del database in modo sicuro, facile e standardizzato.
Tip: Si possono approfondire le migrazioni di Doctrine nella documentazione del progetto.

Installazione
Le migrazioni di Doctrine per Symfony sono mantenute in DoctrineMigrationsBundle. Assicurarsi di avere entrambe le librerie doctrine-migrations e DoctrineMigrationsBundle configurate nel proprio progetto. Seguire questi passi per installare le librerie nella distribuzione Symfony Standard.
Aggiungere i seguenti a deps. Ci registrar il bundle delle migrazioni e la libreria doctrine-migrations come
dipendenze della propria applicazione:
[doctrine-migrations]
git=http://github.com/doctrine/migrations.git
[DoctrineMigrationsBundle]
git=http://github.com/symfony/DoctrineMigrationsBundle.git
target=/bundles/Symfony/Bundle/DoctrineMigrationsBundle

Aggiornare le librerie dei venditori:


$ php bin/vendors install

Quindi, assicurarsi che lo spazio dei nomi Doctrine\DBAL\Migrations sia caricato tramite
autoload.php. Lo spazio dei nomi Migrations deve essere posto prima di Doctrine\\DBAL, in modo
che lautoloader cerchi tali classi nella cartella delle migrazioni:
// app/autoload.php
$loader->registerNamespaces(array(
//...
Doctrine\\DBAL\\Migrations => __DIR__./../vendor/doctrine-migrations/lib,
Doctrine\\DBAL
=> __DIR__./../vendor/doctrine-dbal/lib,
));

Infine, assicurarsi di abilitare il bundle in AppKernel.php, includendo il seguente:


// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
//...
new Symfony\Bundle\DoctrineMigrationsBundle\DoctrineMigrationsBundle(),
);
}

6.1. Bundle di Symfony SE

611

Symfony2 documentation Documentation, Release 2

Utilizzo
Tutte le funzionalit di migrazione sono contenuto in alcuni comandi:

doctrine:migrations
:diff
Genera una migrazione, confrontando il database attuale alle informazioni di mappatura
:execute Esegue una singola migrazione manualmente, in su o in gi.
:generate Genera una classe migrazione vuota.
:migrate Esegue una migrazione a una specifica versione o allultima versione disponibile.
:status
Mostra lo stato di un insieme di migrazioni.
:version Aggiunge e cancella manualmente versioni di migrazione nella tabella delle versioni.

Iniziare verificando lo stato delle migrazioni della propria applicazione, eseguendo il comando status:
php app/console doctrine:migrations:status
== Configuration
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>

Name:
Configuration Source:
Version Table Name:
Migrations Namespace:
Migrations Directory:
Current Version:
Latest Version:
Executed Migrations:
Available Migrations:
New Migrations:

Application Migrations
manually configured
migration_versions
Application\Migrations
/path/to/project/app/DoctrineMigrations
0
0
0
0
0

Ora si pu iniziare a lavorare con le migrazioni, generando una nuova classe migrazione vuota. Successivamente,
si vedr come Doctrine pu generare automaticamente migrazioni al posto nostro.

php app/console doctrine:migrations:generate


Generated new migration class to "/path/to/project/app/DoctrineMigrations/Version20100621140655.ph

Aprendo la classe migrazione appena generata, si vedr qualcosa di simile a questo:


namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration,
Doctrine\DBAL\Schema\Schema;
class Version20100621140655 extends AbstractMigration
{
public function up(Schema $schema)
{
}
public function down(Schema $schema)
{
}
}

Se si esegue il comando status, esso ora mostrer che sia ha una nuova migrazione da eseguire:
php app/console doctrine:migrations:status
== Configuration
>> Name:
>> Configuration Source:
>> Version Table Name:

612

Application Migrations
manually configured
migration_versions

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

>>
>>
>>
>>
>>
>>
>>

Migrations Namespace:
Migrations Directory:
Current Version:
Latest Version:
Executed Migrations:
Available Migrations:
New Migrations:

Application\Migrations
/path/to/project/app/DoctrineMigrations
0
2010-06-21 14:06:55 (20100621140655)
0
1
1

== Migration Versions
>> 2010-06-21 14:06:55 (20100621140655)

not migrated

Ora si pu aggiungere del codice di migrazione ai metodi up() e down() e infine migrare, quando si pronti:
php app/console doctrine:migrations:migrate

Per ulteriori informazioni su come scrivere le migrazioni (cio su come riempire i metodi up() e down()), si
veda la documentazione ufficiale di Doctrine sulle migrazioni.
Eseguire migrazioni durante il deploy

Ovviamente, il fine ultimo della scrittura delle migrazioni la possibilit di usarle per aggiornare la struttura del
proprio database al momento del deploy dellapplicazione. Eseguendo le migrazioni localmente (o su un server di
stage), ci si pu assicurare che esse funzionino come ci si aspetta.
Quando infine si esegue il deploy della propria applicazione, occorre solo ricordarsi di eseguire il comando
doctrine:migrations:migrate. Internamente, Doctrine crea una tabella migration_versions dentro il proprio database e traccia le migrazioni eseguite al suo interno. Quindi, non importa quante migrazioni sono
state create ed eseguite localmente, quando si esegue il comando durante il deploy, Doctrine sapr esattamente
quali migrazioni non sono ancora state eseguite, guardando la tabella migration_versions del database
di produzione. Indipendentemente dal server su cui ci si trova, si pu sempre eseguire questo comando senza
problemi, per eseguire solo le migrazioni che non sono ancora state eseguite su quel particolare database.
Generare automaticamente le migrazioni
In realt, raramente si avr bisogno di scrivere migrazioni a mano, perch la libreria delle migrazioni pu generare
automaticamente le classi delle migrazioni, confrontando le informazioni di mappatura di Doctine (cio come il
proprio database dovrebbe essere) con lattuale struttura del database..
Per esempio, si supponga di creare una nuova entit User e di aggiungere le informazioni di mappatura per
lORM di Doctrine:
Annotations
// src/Acme/HelloBundle/Entity/User.php
namespace Acme\HelloBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="hello_user")
*/
class User
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;

6.1. Bundle di Symfony SE

613

Symfony2 documentation Documentation, Release 2

/**
* @ORM\Column(type="string", length="255")
*/
protected $name;
}

YAML
# src/Acme/HelloBundle/Resources/config/doctrine/User.orm.yml
Acme\HelloBundle\Entity\User:
type: entity
table: hello_user
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type: string
length: 255

XML
<!-- src/Acme/HelloBundle/Resources/config/doctrine/User.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\HelloBundle\Entity\User" table="hello_user">
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="name" column="name" type="string" length="255" />
</entity>
</doctrine-mapping>

Con queste informazioni, Doctrine pronto per persistere il nuovo oggetto User nella tabella hello_user.
Ovviamente, tale tabella non esiste ancora! Generare una nuova migrazione per tale tabella automaticamente,
eseguendo il seguente comando:
php app/console doctrine:migrations:diff

Un messaggio dovrebbe dire che una nuova classe migrazione stata generata, in base alle differenze con lo
schema. Aprendo questo file, si trover il codice SQL necessario per creare la tabella hello_user. Quindi,
eseguire la migrazione per aggiungere la tabella al proprio database:
php app/console doctrine:migrations:migrate

La morale della favola : dopo ogni modifica eseguita alle informazioni di mappatura di Docrtine, eseguire il
comando doctrine:migrations:diff, per generare automaticamente le proprie classi migrazione.
Se lo si fa gi dallinizio del proprio progetto (cio in modo tale che anche le prime tabelle siano caricate tramite
una classe migrazione), si sar sempre in grado di creare un nuovo database ed eseguire le proprie migrazioni
per portare lo schema al pieno aggiornamento. In effetti, un modo di lavorare facile e affidabile per il proprio
progetto.

614

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

6.1.6 DoctrineMongoDBBundle
Configurazione di DoctrineMongoDBBundle
Configurazione di esempio
# app/config/config.yml
doctrine_mongodb:
connections:
default:
server: mongodb://localhost:27017
options:
connect: true
default_database: hello_%kernel.environment%
document_managers:
default:
mappings:
AcmeDemoBundle: ~
metadata_cache_driver: array # array, apc, xcache, memcache

Se si vuole usare memcache per la cache dei meta-dati, occorre configurare listanza Memcache. Per esempio, si
pu fare come segue:
YAML
# app/config/config.yml
doctrine_mongodb:
default_database: hello_%kernel.environment%
connections:
default:
server: mongodb://localhost:27017
options:
connect: true
document_managers:
default:
mappings:
AcmeDemoBundle: ~
metadata_cache_driver:
type: memcache
class: Doctrine\Common\Cache\MemcacheCache
host: localhost
port: 11211
instance_class: Memcache

XML
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:doctrine_mongodb="http://symfony.com/schema/dic/doctrine/odm/mongodb"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/
http://symfony.com/schema/dic/doctrine/odm/mongodb http://symfony.com

<doctrine_mongodb:config default-database="hello_%kernel.environment%">
<doctrine_mongodb:document-manager id="default">
<doctrine_mongodb:mapping name="AcmeDemoBundle" />
<doctrine_mongodb:metadata-cache-driver type="memcache">
<doctrine_mongodb:class>Doctrine\Common\Cache\MemcacheCache</doctrine_mongodb
<doctrine_mongodb:host>localhost</doctrine_mongodb:host>
<doctrine_mongodb:port>11211</doctrine_mongodb:port>
<doctrine_mongodb:instance-class>Memcache</doctrine_mongodb:instance-class>
</doctrine_mongodb:metadata-cache-driver>

6.1. Bundle di Symfony SE

615

Symfony2 documentation Documentation, Release 2

</doctrine_mongodb:document-manager>
<doctrine_mongodb:connection id="default" server="mongodb://localhost:27017">
<doctrine_mongodb:options>
<doctrine_mongodb:connect>true</doctrine_mongodb:connect>
</doctrine_mongodb:options>
</doctrine_mongodb:connection>
</doctrine_mongodb:config>
</container>

Configurazione della mappatura La definizione esplicita di tutti i documenti mappati lunica configurazione
necessaria per lODM e ci sono numerose opzioni di configurazione che si possono conrtollare. La mappatura ha
le seguenti opzioni di configurazione:
type uno tra annotations, xml, yml, php o staticphp. Specifica il tipo di meta-dati usati dalla
mappatura.
dir percorso per la mappatura o per i file entit (a seconda del driver). Se questo percorso relativo, si
presume sia relativo alla radice del bundle. Questo funziona se il nome della propria mappatura il nome
di un bundle. Se si vuole usare questa opzione per specificare un percorso assoluto, occorre aggiungere un
prefisso con i parametri del kernel esistenti nel DIC (per esempio, %kernel.root_dir%).
prefix un prefisso comune dello spazio dei nomi, condiviso da tuti i documenti della mappatura.
Questo prefisso non deve essere in conflitto con i prefissi di altre mappature definite, altrimenti Doctrine potrebbe non trovare alcuni documenti. Il valore predefinito di questa opzione lo spazio dei nomi
del bundle + Document, per esempio per un bundle chiamato AcmeHelloBundle, il prefisso sarebbe
Acme\HelloBundle\Document.
alias Doctrine offre un sistema di alias degli spazi dei nomi dei documenti, con nomi pi semplici e pi
brevi da usare nelle query per accedere al repository.
is_bundle questa opzione ha un valore derivato da dir ed impostata in modo predefinito a true se
dir relativa, verificata con file_exists() che restituisca false. invece false se il controllo
di esistenza restituisce true. In questo caso, stato specificato un percorso assoluto e i file dei meta-dati
sono molto probabilmente in una cartella fuori da un bundle.
Per evitare di dover configurare un sacco di informazioni per la mappatura, si dovrebbero seguire le seguenti
convenzioni:
1. Inserire tutti i documenti in una cartella Document/ dentro al proprio bundle.
Acme/HelloBundle/Document/.

Per esempio

2. Se si usa una mappatura xml, yml o php, mettere tutti i file di configurazione nella cartella
Resources/config/doctrine/, con suffisso rispettivamente mongodb.xml, mongodb.yml o mongodb.php.
3. Se esiste una cartella Document/, ma nessuna cartella Resources/config/doctrine/, si presume
luso di annotazioni.
La configurazione seguente mostra tanto esempi di mappatura:
doctrine_mongodb:
document_managers:
default:
mappings:
MyBundle1: ~
MyBundle2: yml
MyBundle3: { type: annotation, dir: Documents/ }
MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping }
MyBundle5:
type: yml
dir: my-bundle-mappings-dir
alias: BundleAlias
doctrine_extensions:
type: xml

616

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

dir: %kernel.root_dir%/../src/vendor/DoctrineExtensions/lib/DoctrineExtensions
prefix: DoctrineExtensions\Documents\
alias: DExt

Connessioni multiple Se servono connessioni e gestori di documenti multipli, si pu usare la sintassi seguente:
Si possono quindi recuperare i servizi configurati:
$conn1 = $container->get(doctrine.odm.mongodb.conn1_connection);
$conn2 = $container->get(doctrine.odm.mongodb.conn2_connection);

E si pu anche recuperare il gestore di documenti configurato che usa i servizi di connessione visti sopra:
$dm1 = $container->get(doctrine.odm.mongodb.dm1_document_manager);
$dm2 = $container->get(doctrine.odm.mongodb.dm2_document_manager);

Connettersi a un pool di server mongodb con una connessione Ci si pu connettere a molti server mongodb
in ununica connessione, se si usa un insieme di replice, elencando tutti i server nella stringa di connessione,
separati da virgole.
YAML
doctrine_mongodb:
# ...
connections:
default:
server: mongodb://mongodb-01:27017,mongodb-02:27017,mongodb-03:27017

Dove mongodb-01, mongodb-02 e mongodb-03 sono i nomi di host delle macchine. Se si preferisce, si possono
usare gli indirizzi IP.
Configurazione predefinita completa
YAML
doctrine_mongodb:
document_managers:
# Prototype
id:
connection:
~
database:
~
logging:
true
auto_mapping:
false
metadata_cache_driver:
type:
~
class:
~
host:
~
port:
~
instance_class:
~
mappings:
# Prototype
name:
mapping:
type:
dir:
prefix:
alias:
is_bundle:
connections:

6.1. Bundle di Symfony SE

true
~
~
~
~
~

617

Symfony2 documentation Documentation, Release 2

# Prototype
id:
server:
~
options:
connect:
~
persist:
~
timeout:
~
replicaSet:
~
username:
~
password:
~
proxy_namespace:
Proxies
proxy_dir:
%kernel.cache_dir%/doctrine/odm/mongodb/Proxies
auto_generate_proxy_classes: false
hydrator_namespace:
Hydrators
hydrator_dir:
%kernel.cache_dir%/doctrine/odm/mongodb/Hydrators
auto_generate_hydrator_classes: false
default_document_manager: ~
default_connection:
~
default_database:
default

Implementare un semplice form di registrazione con MongoDB


Alcuni form hanno campi extra, i cui valori non hanno bisogno di essere memorizzati nella base dati. In questo
esempio, creeremo un form di registrazione con alcuni campi extra (come un campo checkbox accettazione
termini) e inseriremo il form che si occupa effettivamente di memorizzare le informazioni dellaccount. Useremo
MongoDB per memorizzare i dati.
Il semplice modello User

Quindi, in questa guida inizieremo con il modello per un documento User:


// src/Acme/AccountBundle/Document/User.php
namespace Acme\AccountBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Bundle\MongoDBBundle\Validator\Constraints\Unique as MongoDBUnique;
/**
* @MongoDB\Document(collection="users")
* @MongoDBUnique(path="email")
*/
class User
{
/**
* @MongoDB\Id
*/
protected $id;
/**
* @MongoDB\Field(type="string")
* @Assert\NotBlank()
* @Assert\Email()
*/
protected $email;
/**
* @MongoDB\Field(type="string")
* @Assert\NotBlank()

618

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

*/
protected $password;
public function getId()
{
return $this->id;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
}
public function getPassword()
{
return $this->password;
}
// una forma semplice e stupida di crittazione (da non copiare!)
public function setPassword($password)
{
$this->password = sha1($password);
}
}

Il documento User contiene tre campi, di cui due (email e password) vanno mostrati sul form. La propriet email
deve essere univoca nella base dati, quindi abbiamo aggiunto la validazione in cima alla classe.
Note: Se si vuole integrare questo User con il sistema di sicurezza, occorre implementare UserInterface, del
componente della sicurezza.

Creare un form per il modello

Quindi, creare il form per il modello User:


// src/Acme/AccountBundle/Form/Type/UserType.php
namespace Acme\AccountBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilder;
class UserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add(email, email);
$builder->add(password, repeated, array(
first_name => password,
second_name => confirm,
type => password
));
}
public function getDefaultOptions(array $options)

6.1. Bundle di Symfony SE

619

Symfony2 documentation Documentation, Release 2

{
return array(data_class => Acme\AccountBundle\Document\User);
}
public function getName()
{
return user;
}
}

Abbiamo solo aggiunto due campi: email e password (ripetuta, per conferma). Lopzione data_class dice al
form il nome della classe dei dati (cio il documento User).
Tip: Per approfondire il componente dei form, leggere la documentazione.

Inserire il form User in un form di registrazione

Il form che useremo per la pagina di regstrazione non lo stesso form usato per modificare lutente (cio
UserType). Il form di registrazione conterr campi in pi, come accettazione termini, i cui valori non saranno
memorizzati nella base dati.
In altre parole, creare un secondo form per la registrazione, in cui inserire il form User e aggiungere i campi extra
necessari. Iniziare creando una semplice classe, che rappresenta la registrazione:
// src/Acme/AccountBundle/Form/Model/Registration.php
namespace Acme\AccountBundle\Form\Model;
use Symfony\Component\Validator\Constraints as Assert;
use Acme\AccountBundle\Document\User;
class Registration
{
/**
* @Assert\Type(type="Acme\AccountBundle\Document\User")
*/
protected $user;
/**
* @Assert\NotBlank()
* @Assert\True()
*/
protected $termsAccepted;
public function setUser(User $user)
{
$this->user = $user;
}
public function getUser()
{
return $this->user;
}
public function getTermsAccepted()
{
return $this->termsAccepted;
}
public function setTermsAccepted($termsAccepted)

620

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

{
$this->termsAccepted = (boolean)$termsAccepted;
}
}

Quindi, creare il form per il modello Registration:


// src/Acme/AccountBundle/Form/Type/RegistrationType.php
namespace Acme\AccountBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilder;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add(user, new UserType());
$builder->add(terms, checkbox, array(property_path => termsAccepted));
}
public function getName()
{
return registration;
}
}

Non occorre usare un metodo speciale per inserire il form UserType. Un form un campo, quindi lo si pu
aggiungere come ogni altro campo, aspettandosi che la corrispondente propriet user conterr unistanza della
classe UserType.
Gestire linvio del form

Quindi, occorre un controllore che gestisca il form. Iniziare creando un semplice controllore, per mostrare il form
di registrazione:
// src/Acme/AccountBundle/Controller/AccountController.php
namespace Acme\AccountBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Acme\AccountBundle\Form\Type\RegistrationType;
use Acme\AccountBundle\Form\Model\Registration;
class AccountController extends Controller
{
public function registerAction()
{
$form = $this->createForm(new RegistrationType(), new Registration());

return $this->render(AcmeAccountBundle:Account:register.html.twig, array(form => $form


}
}

e il suo template:
{# src/Acme/AccountBundle/Resources/views/Account/register.html.twig #}
<form action="{{ path(create)}}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}

6.1. Bundle di Symfony SE

621

Symfony2 documentation Documentation, Release 2

<input type="submit" />


</form>

Infinre, creaee til controllore che gestisce linvio del form. Questo eseguir la validazione e salver i dati in
MongoDB:
public function createAction()
{
$dm = $this->get(doctrine.odm.mongodb.default_document_manager);
$form = $this->createForm(new RegistrationType(), new Registration());
$form->bindRequest($this->getRequest());
if ($form->isValid()) {
$registration = $form->getData();
$dm->persist($registration->getUser());
$dm->flush();
return $this->redirect(...);
}

return $this->render(AcmeAccountBundle:Account:register.html.twig, array(form => $form->cr


}

Ecco fatto! Il form ora valida e consente di salvare loggetto User in MongoDB.
LObject Document Mapper (ODM) MongoDB assomiglia allORM di Doctrine2 per filosofia e funzionamento,
In altre parole, come lORDM di Doctrine2, con lODM di Doctrine si ha a che fare solo con oggetti PHP puri,
che sono persistiti in modo trasparente a MongoDB.
Tip: Si pu approfondire lODM MongoDB di Doctrine nella documentazione del progetto.
disponibile un bundle che integra lODM MongoDB di Doctrine in Symfony, facilitandone la configurazione e
lutilizzo.
Note: Questo capitolo assomiglia molto al capitolo sullORM Doctrine2, che parla di come lORM di Doctrine2
possa essere usato per persistere dati in un database relazione (p.e. MySQL). La somiglianza intenzionale: che
si persista in un database relazione con ORM o in MongoDB con ODM, la filosofia quasi la stessa.

Installazione
Per usare lODM MongoDB, occorrono due librerie fornite da Doctrine e un bundle che le integri in Symfony2.
Se si usa la Standard Distribution di Symfony, aggiungere il seguente al file deps del proprio progetto:
[doctrine-mongodb]
git=http://github.com/doctrine/mongodb.git
[doctrine-mongodb-odm]
git=http://github.com/doctrine/mongodb-odm.git
[DoctrineMongoDBBundle]
git=http://github.com/symfony/DoctrineMongoDBBundle.git
target=/bundles/Symfony/Bundle/DoctrineMongoDBBundle

Aggiornare le librerie dei venditori, eseguendo:

622

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

$ php bin/vendors install

Quindi, aggiungere gli spazi dei nomi Doctrine\ODM\MongoDB e Doctrine\MongoDB al file


app/autoload.php, in modo che tali librerie possano essere caricate. Assicurarsi di aggiungerle sopra lo
spazio dei nomi Doctrine (mostrato):
// app/autoload.php
$loader->registerNamespaces(array(
// ...
Doctrine\\ODM\\MongoDB
=> __DIR__./../vendor/doctrine-mongodb-odm/lib,
Doctrine\\MongoDB
=> __DIR__./../vendor/doctrine-mongodb/lib,
Doctrine
=> __DIR__./../vendor/doctrine/lib,
// ...
));

Quindi, registrare la libreria delle annotazioni, aggiungendo il seguente allautoloader (sotto la riga, gi esistente,
AnnotationRegistry::registerFile):

// app/autoload.php
AnnotationRegistry::registerFile(
__DIR__./../vendor/doctrine-mongodb-odm/lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Doctrine
);

Infine, abilitare il bundle nel kernel:


// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Symfony\Bundle\DoctrineMongoDBBundle\DoctrineMongoDBBundle(),
);
// ...
}

Si ora pronti per lavorare.


Configurazione
Per iniziare, sono necessarie alcune configurazioni di base, che impostano il document manager. Il modo pi
semplice abilitare auto_mapping, che attiver lODM MongoDB su tutta la propria applicazione:
# app/config/config.yml
doctrine_mongodb:
connections:
default:
server: mongodb://localhost:27017
options:
connect: true
default_database: test_database
document_managers:
default:
auto_mapping: true

Note: Ovviamente, occorre anche assicurarsi che il server MongoDB stia girando. Per maggiori dettagli, vedere
la guida Quick Start di MongoDB.

6.1. Bundle di Symfony SE

623

Symfony2 documentation Documentation, Release 2

Un semplice esempio: un prodotto


Il modo migliore per capire lODM MongoDB ODM vederlo in azione. In questa sezione, affronteremo tutti i
passi necessari per iniziare a persistere documenti in MongoDB.
Codice con lesempio
Se si vuole seguire lesempio in questo capitolo, creare AcmeStoreBundle, con:
php app/console generate:bundle --namespace=Acme/StoreBundle

Creare una classe documento

Si supponga di dover costruire unapplicazione in cui devono essere mostrati dei prodotti. Senza nemmeno pensare
a Doctrine o a MongoDB, gi si sa di aver bisogno di un oggetto Product, che rappresenti tali prodotti. Creare
questa classe nella cartella Document del proprio AcmeStoreBundle:
// src/Acme/StoreBundle/Document/Product.php
namespace Acme\StoreBundle\Document;
class Product
{
protected $name;
protected $price;
}

La classe, chiamata spesso documento, che significa una classe di base che contiene dati, semplice e aiuta a
soddisfare i requisiti per i prodotti della propria applicazione. Questa classe non pu ancora essere persistita in
MongoDB, solo una semplice classe PHP.
Aggiungere informazioni di mappatura

Doctrine consente di lavorare con MongoDB in un modo molto pi interessante rispetto al semplice recupero di
dati in un array. Invece, Doctrine consente di persistere interi oggetti in MongoDB e di recuperare interi oggetti
da MongoDB. Funziona mappando una classe PHP le sue propriet su elementi di una collezione di MongoDB.
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 su MongoDB.
Questi meta-dati possono essere specificati in diversi formati, inclusi YAML, XML o direttamente dentro la classe
Product, tramite annotazioni:
Annotations
// src/Acme/StoreBundle/Document/Product.php
namespace Acme\StoreBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* @MongoDB\Document
*/
class Product
{
/**
* @MongoDB\Id
*/
protected $id;

624

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

/**
* @MongoDB\String
*/
protected $name;
/**
* @MongoDB\Float
*/
protected $price;
}

YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Product.mongodb.yml
Acme\StoreBundle\Document\Product:
fields:
id:
id: true
name:
type: string
price:
type: float

XML

<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.mongodb.xml -->


<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
<document name="Acme\StoreBundle\Document\Product">
<field fieldName="id" id="true" />
<field fieldName="name" type="string" />
<field fieldName="price" type="float" />
</document>
</doctrine-mongo-mapping>

Doctrine consente di scegliere tra una grande variet di tipi di campo, ognuno con le sue opzioni Per informazioni
sui tipi disponibili, vedere la sezione Riferimento sui tipi di campo di Doctrine.
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 MongoDB\ (p.e.
MongoDB\String), che non mostrato nella documentazione di Doctrine. Occorrer anche includere
listruzione use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;, che importa il
prefisso MongoDB delle annotazioni.
Generare getter e setter

Sebbene ora Doctrine sappia come persistere un oggetto Product in MongoDB, 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:mongodb:generate:documents AcmeStoreBundle

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

6.1. Bundle di Symfony SE

625

Symfony2 documentation Documentation, Release 2

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.

Persistere gli oggetti in MongoDB

Ora che il documento Product stato mappato ed completo di getter e setter, si pronti per persistere i dati in
MongoDB. 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\Document\Product;
use Symfony\Component\HttpFoundation\Response;
// ...

5
6
7
8
9
10

public function createAction()


{
$product = new Product();
$product->setName(A Foo Bar);
$product->setPrice(19.99);

11

$dm = $this->get(doctrine.odm.mongodb.document_manager);
$dm->persist($product);
$dm->flush();

12
13
14
15

return new Response(Created product id .$product->getId());

16
17

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-10 In questa sezione, si istanzia e si lavora con loggetto $product, come qualsiasi altro normale
oggetto PHP;
riga 12 Questa riga recupera loggetto gestore di documenti di Doctrine, responsabile della gestione del
processo di persistenza e del recupero di oggetti in MongoDB;
riga 13 Il metodo persist() dice a Doctrine di gestire loggetto $product. Questo non fa (ancora)
eseguire una query su MongoDB.
riga 14 Quando il metodo flush() richiamato, Doctrine cerca tutti gli oggetti che sta gestendo, per
vedere se hanno bisogno di essere persistiti su MongoDB In questo esempio, loggetto $product non
stato ancora persistito, quindi il gestore di documenti esegue una query su MongoDB e crea una nuova voce.
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 loperazione pi efficiente possibile.
Quando si creano o aggiornano oggetti, il flusso sempre lo stesso. Nella prossima sezione, si vedr come
Doctrine sia abbastanza intelligente da aggiornare le voci che gi esistono in MongoDB.
Tip: Doctrine fornisce una libreria che consente di caricare dati di test nel proprio progetto (le cosiddette fixture). Per informazioni, vedere DoctrineFixturesBundle.

626

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

Recuperare oggetti da MongoDB

Recuperare un oggetto da MongoDB 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->get(doctrine.odm.mongodb.document_manager)
->getRepository(AcmeStoreBundle:Product)
->find($id);
if (!$product) {
throw $this->createNotFoundException(No product found for 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 documento tramite:
$repository = $this->get(doctrine.odm.mongodb.document_manager)
->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 i propri documenti rimarranno sotto lo spazio dei nomi Document del proprio bundle.
Una volta ottenuto il proprio repository, si avr accesso a tanti metodi utili:
// cerca per chiave primaria (di solito "id")
$product = $repository->find($id);
// nomi di metodi dinamici per cercare in base al valore di una colonna
$product = $repository->findOneById($id);
$product = $repository->findOneByName(foo);
// 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 => foo, price => 19.99));
// cerca tutti i prodotti in base al nome, ordinati per prezzo
$product = $repository->findBy(
array(name => foo),
array(price, ASC)
);

6.1. Bundle di Symfony SE

627

Symfony2 documentation Documentation, Release 2

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)
{
$dm = $this->get(doctrine.odm.mongodb.document_manager);
$product = $dm->getRepository(AcmeStoreBundle:Product)->find($id);
if (!$product) {
throw $this->createNotFoundException(No product found for id .$id);
}
$product->setName(New product name!);
$dm->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 documenti
Si noti che non necessario richiamare $dm->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 dei
documenti:
$dm->remove($product);
$dm->flush();

Come ci si potrebbe aspettare, il metodo remove() rende noto a Doctrine che si vorrebbe rimuovere la data
entit dal database. Tuttavia, loperazione di cancellazione non viene realmente eseguita finch non si richiama il
metodo flush().
Cercare gli oggetti
Come gi visto, la classe repository consente di cercare uno o pi oggetti, in base a diversi parametri. Quando
ci sufficiente, il modo pi facile per recuperare documenti. Ovviamente, si possono anche creare query pi
complesse.
Usare query builder

lODM di Doctrine ha un oggetto Builder, che consente di costruire una query che restituisce esattamente i
documenti desiderati. Se si usa un IDE, si pu anche trarre vantaggio dallauto-completamento durante la scrittura
dei nomi dei metodi. Da dentro un controllore:
$products = $this->get(doctrine.odm.mongodb.document_manager)
->createQueryBuilder(AcmeStoreBundle:Product)
->field(name)->equals(foo)
->limit(10)

628

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

->sort(price, ASC)
->getQuery()
->execute()

In questo caso, saranno restituiti 10 prodotti con nome foo, ordinati da quello con il prezzo pi basso a quello
con il prezzo pi alto.
Loggetto QueryBuilder contiene tutti i metodi necessari per costruire la propria query. Per maggiori informazioni su query builder, consultare la documentazione di Doctrine Query Builder. Per la lista delle condizioni
disponibili per una query, vedere la documentazione Operatori condizionali.
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 cone 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/Document/Product.php
namespace Acme\StoreBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* @MongoDB\Document(repositoryClass="Acme\StoreBundle\Repository\ProductRepository")
*/
class Product
{
//...
}

YAML
# src/Acme/StoreBundle/Resources/config/doctrine/Product.mongodb.yml
Acme\StoreBundle\Document\Product:
repositoryClass: Acme\StoreBundle\Repository\ProductRepository
# ...

XML

<!-- src/Acme/StoreBundle/Resources/config/doctrine/Product.mongodb.xml -->


<!-- ... -->
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
<document name="Acme\StoreBundle\Document\Product"
repository-class="Acme\StoreBundle\Repository\ProductRepository">
<!-- ... -->
</document>
</doctrine-mong-mapping>

Doctrine pu generare la classe repository per noi, eseguendo:


php app/console doctrine:mongodb:generate:repositories AcmeStoreBundle

Quindi, aggiungere un nuovo metodo, chiamato findAllOrderedByName(), alla classe repository appena
generata. Questo metodo cercher tutti i documenti Product, ordinati alfabeticamente.

6.1. Bundle di Symfony SE

629

Symfony2 documentation Documentation, Release 2

// src/Acme/StoreBundle/Repository/ProductRepository.php
namespace Acme\StoreBundle\Repository;
use Doctrine\ODM\MongoDB\DocumentRepository;
class ProductRepository extends DocumentRepository
{
public function findAllOrderedByName()
{
return $this->createQueryBuilder()
->sort(name, ASC)
->getQuery()
->execute();
}
}

Si pu usare il metodo appena creato proprio come i metodi predefiniti del repository:
$product = $this->get(doctrine.odm.mongodb.document_manager)
->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().

Estensioni Doctrine: Timestampable, Sluggable, ecc.


Doctrine alquanto flessibile e diverse estensioni di terze parti sono disponibili, consentendo di eseguire facilmente compiti comuni e ripetitivi sui propri documenti. Sono inclusi Sluggable, Timestampable, Loggable, Translatable e Tree.
Per maggiori informazioni su come trovare e usare tali estensioni, vedere la ricetta usare le estensioni comuni di
Doctrine.
Riferimento sui tipi di campo di Doctrine
Doctrine ha un gran numero di tipi di campo a disposizione. Ognuno di questi mappa un tipo di dato PHP su uno
specifico tipo MongoDB. I seguenti sono solo alucni dei tipi supportati in Doctrine:
string
int
float
date
timestamp
boolean
file
Per maggiori informazioni, vedere Documentazione sulla mappatura dei tipi.
Comandi da console
Lintegrazione con lODM Doctrine2 offre diversi comandi da console, sotto lo spazio dei nomi
doctrine:mongodb. Per vedere la lista dei comandi, si pu eseguire la console senza parametri:

630

Chapter 6. Bundle

Symfony2 documentation Documentation, Release 2

php app/console

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

Note: Per poter caricare fixture in MongoDB, occorre avere il bundle DoctrineFixturesBundle installato.
Per sapere come fare, leggere la voce DoctrineFixturesBundle della documentazione.

Configurazione
Per informazioni dettagliate sulle opzioni di configurazione disaponibili per luso dellODM di Doctrine, vedere
la sezione Riferimenti MongoDB.
Registrare ascoltatori e sottoscrittori di eventi

Doctrine consente di registrare ascoltatori e sottoscrittori di eventi, che sono notificati quando i vari eventi accadono allinterno dellODM di Doctrine. Per ulteriori informazioni, vedere la Documentazione sugli eventi di
Doctrine.
In Symfony, si possono registrare ascoltatori o sottoscrittori, creando un servizio e taggandolo uno specifico tag.
ascoltatore: Usare il tag doctrine.odm.mongodb.<connection>_event_listener, dove il
nome <connection> sostituito dal nome della propria connessione (solitamente, default). Inoltre,
assicurarsi di aggiungere la chiave event al tag, specificando quale evento ascoltare. Ipotizzando che la
connessione si chiami default:
YAML
services:
my_doctrine_listener:
class:
Acme\HelloBundle\Listener\MyDoctrineListener
# ...
tags:
- { name: doctrine.odm.mongodb.default_event_listener, event: postPersist }

XML
<service id="my_doctrine_listener" class="Acme\HelloBundle\Listener\MyDoctrineListener">
<!-- ... -->
<tag name="doctrine.odm.mongodb.default_event_listener" event="postPersist" />
</service>.

PHP
$definition = new Definition(Acme\HelloBundle\Listener\MyDoctrineListener);
// ...
$definition->addTag(doctrine.odm.mongodb.default_event_listener);
$container->setDefinition(my_doctrine_listener, $definition);

sottoscrittore: Usare il tag doctrine.odm.mongodb.<connection>_event_subscriber.


Non occorre nessuna altra chiave nel tag.
Riepilogo
Con Doctrine, ci si pu concentrare sui propri oggetti e su come siano utili nella propria applicazione e preoccuparsi della persistenza su MongoDB in un secondo momento. Questo perch Doctrine consente di usare qualsiasi
6.1. Bundle di Symfony SE

631

Symfony2 documentation Documentation, Release 2

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 collezione di MongoDB.
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.
Imparare di pi dal ricettario
Implementare un semplice form di registrazione con MongoDB
SensioFrameworkExtraBundle
SensioGeneratorBundle
JMSSecurityExtraBundle
DoctrineFixturesBundle
DoctrineMigrationsBundle
DoctrineMongoDBBundle
SensioFrameworkExtraBundle
SensioGeneratorBundle
JMSSecurityExtraBundle
DoctrineFixturesBundle
DoctrineMigrationsBundle
DoctrineMongoDBBundle

632

Chapter 6. Bundle

CHAPTER

SEVEN

CONTRIBUTI
Contribuire a Symfony2:

7.1 Contribuire
7.1.1 Contribuire al codice
Segnalare un bug
Se doveste incontrare un bug in Symfony2, vi chiediamo di riportarlo. Ci aiuta a rendere migliore Symfony2.
Caution: Se pensate di aver trovato un problema di sicurezza, per favore, seguite invece la procedura.
Prima di inviare un bug:
Ricontrollare la documentazione ufficiale per verificare che non si stia facendo un uso scorretto del framework;
Chidere assistenza alla lista degli utenti , al forum o al canale IRC #symfony, se non si sicuri che sia
effettivamente un bug.
Se si hanno problemi a identificare effettivamente il bug, segnalarlo utilizzando il bug tracker ufficiale seguendo
alcune regole:
Utilizzare il campo titolo per descrivere chiaramente la problematica;
Descrivere gli step necessari per riprodurre il bug anche con dei piccoli esempi di codice (fornire il test
unitario per replicare il bug il massimo)
Fornire il maggior numero di dettagli possibile del proprio ambiente (OS, versione PHP versione di Symfony, extensioni abilitate)
(optional) Eventuali patch allegate.
Inviare una patch
Una patch il modo migliore per rimediare a un bug e per proporre dei miglioramenti a Symfony2
Lista di controllo

Lo scopo della lista di controllo assicurare che i contributi possano essere valutati senza il bisogno di domande
e risposte continue e assicurare che i contributi possano essere inclusi in Symfony2 il pi rapidamente possibile.
Le richieste di pull vanno prefissate con il nome del componente o del bundle a cui si riferiscono.

633

Symfony2 documentation Documentation, Release 2

[Componente] breve titolo descrittivo.

Un esempio potrebbe essere simile a questo:


[Form] Aggiunta del tipo di campo selectbox.

Tip: Si prega di aggiungere [WIP] al titolo, se la proposta non ancora completa oppure se i test sono
incompleti o non passano.
Tutte le richieste di pull devono includere il seguente template nella descrizione della richiesta:
Bug fix: [yes|no]
Feature addition: [yes|no]
Backwards compatibility break: [yes|no]
Symfony2 tests pass: [yes|no]
Fixes the following tickets: [lista separata da virgole di ticket risolti]
Todo: [lista di todo in corso]

Un esempio di proposta potrebbe essere il seguente:


Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: Todo: -

Grazie per includere il template completo nelle vostre proposte!


Tip: Tutte le aggiunte di caratteristiche vanno inviate al ramo master, mentre tutti i bug risolti vanno inviati al
pi vecchio ramo ancora attivo. Inoltre, le proposte non devono, di norma, infrangere la retro-compatibilit.

Tip: Per fare in modo che il proprio ramo sia automaticamente testato, si pu aggiungere il proprio fork a travisci.org. Basta entrare con laccount usato su github.com e e abilitare un singolo switch, per i test automatici. Nella
propria richiesta di pull, invece di specificare Symfony2 tests pass: [yes|no], si pu collegare licona di stato di
travis-ci.org. Per maggiori dettagli, vedere travis-ci.org Getting Started Guide.

Configurazione iniziale

Prima di lavorare su Symfony2, configurare il proprio ambiente con il seguente software:


Git;
PHP versione 5.3.2 o superiore;
PHPUnit 3.6.4 o superiore.
Impostare il informazioni utente con il nome e lindirizzo email:
$ git config --global user.name "Il mio nome"
$ git config --global user.email mia_emil@example.com

Tip: Raccomandiamo caldamente a chi fosse nuovo di Git la lettura delleccellente libro gratuito ProGit

Tip: Utenti Windows: installando Git, linstallazione chieder cosa fare con i fine riga e suggerit di sostituire
Lf con CRLF. Questa impostazione sbagliata, se si vuole contribuire a Symfony! La scelta migliore il metodo

634

Chapter 7. Contributi

Symfony2 documentation Documentation, Release 2

as-is, perch git converitr i fine riga in quelli del repository. Se Git gi stato installato, si pu verificare il
valore dellimpostazione, scrivendo:
$ git config core.autocrlf

Questo comando resituir false, input o true, dove true e false sono i valori sbagliati. Si pu modificare
il valore, scrivendo:
$ git config --global core.autocrlf input

Sostituire global con local se si vuole impostare solo per il repository attivo
Recuperare il codice sorgente di Symfony2:
Creare un account su GitHub ed eseguire lautenticazione;
Forkare il repository di Symfony2: cliccare sul bottone fork;
Dopo aver completato il fork, clonare il proprio fork localmente (questo creer una cartella symfony)
$ git clone git@github.com:USERNAME/symfony.git

Aggiungere il repository di upstream come remote:


$ cd symfony
$ git remote add upstream git://github.com/symfony/symfony.git

Ora che Symfony2 installato, verificate che tutti i test unitari passino per il vostro ambiente come spiegato nel
capitolo document.
Lavorare su una patch

Ogni volta che si desidera lavorare su una patch per un bug o per un miglioramento, necessario creare uno
specifico ramo.
Il ramo deve essere basato sul ramo master se si vuole aggiungere una nuova funzionalit. Ma se si vuole fissare
un bug, utilizzare le vecchie versioni ma mantenute versioni Symfony nelle quali appare il bug (come 2.0).
Creare il ramo dellargomento con il seguente comando:
$ git checkout -b NOME_RAMO master

Oppure, se si vuole fornire il fix di un bug per il ramo 2.0, occorre prima tracciare localmente il ramo remoto 2.0:
$ git checkout -t origin/2.0

Si pu quindi creare un nuovo ramo dal 2.0, per lavorare sul fix del bug:
$ git checkout -b NOME_RAMO 2.0

Tip: Usare un nome descrittivo per il ramo (ticket_XXX dove XXX il numero del ticket una buona convezione
per il fix del bug)
Il comando sopra scambia automaticamente il codice con il ramo appena creato (per verificare in quale ramo ci si
trovi eseguire il comando git branch)
possibile lavorare sul codice quanto si vuole e committare tanto quanto si vuole; ma bisogna tenere a mente le
seguenti indicazioni:
Seguire gli standards del codice (utilizzare git diff check per controllare i spazi alla fine);
Aggiungere test unitari per provare che il bug stato fissato per mostrare che la funzionalit effettivamente
funzionante;

7.1. Contribuire

635

Symfony2 documentation Documentation, Release 2

Fare commit separati e atomici (utilizzare le funzionalit di git rebase per ottenere uno storico chiaro e
pulito);
Scrivere buoni messaggi di commit.
Inviare una patch

Prima di inviare una patch, aggiornare il proprio ramo (necessario se passa del tempo tra il checkout e il commit
delle nuove funzionalit)
$
$
$
$
$

git
git
git
git
git

checkout master
fetch upstream
merge upstream/master
checkout NOME_RAMO
rebase master

Tip: Sostituire master con 2.0 se si sta lavorando sul fix di un bug
Quando si esegue il comando rebase, potrebbe essere necessario risolvere conflitti dovuti allunione del codice.
Il comando git status metter in mostra i file non ancora uniti (unmerged ). Risolvere tutti i conflitti e
continuare con il rebase
$ git add ... # aggiunge file risolti
$ git rebase --continue

Verificare che tutti i test stiano ancora passando e inviare gli sviluppi nel ramo remoto.
$ git push origin NOME_RAMO

A questo punto possibile discutere della patch nella lista dev o effettuare direttamente una richiesta di pull (deve
essere eseguita nel repository symfony/symfony). Per facilitare il lavoro del team di sviluppo principale,
includere sempre nella richiesta di pull un messaggio con i componenti modificati, come di seguito:
[Yaml] pippo pluto
[Form] [Validator] [FrameworkBundle] pippo pluto

Tip: Si faccia attenzione a puntare la richiesta di pull a symfony:2.0, se si vuole che il team faccia il pull del
fix di un bug sul ramo 2.0.
Se si decide di inviare unemail alla lista, non dimenticare di inserire lURL del ramo
(https://github.com/USERNAME/symfony.git NOME_RAMO) oppure lURL della richiesta di
pull.
Dipendentemente dal riscontro della lista o attraverso la richiesta di pull su Github, potrebbe essere necessario
rielaborare la patch. Prima di re-inserire la path, eseguire il rebase con il ramo master, ma non unire attraverso il
merge; e forzare il push nellorigin:
$ git rebase -f upstream/master
$ git push -f origin NOME_RAMO

Note: Tutte le patch che si rilasciano devono essere sotto licenza MIT a meno che non sia esplicitato diversamente
nel codice.
Tutti i bug risolti uniti nei rami di manutenzione sono anche uniti nei pi recenti rami. Per esempio se si invia una
patch per il ramo 2.0, la patch sar applicata dal team di sviluppo principale nel ramo master.
$ git rebase -i head~3
$ git push -f origin NOME_RAMO

636

Chapter 7. Contributi

Symfony2 documentation Documentation, Release 2

Il numero 3 deve essere uguale al numero di commit nel proprio ramo. Dopo aver scritto questo comando, si aprir
un programma di modifica, con una lista di commit:
pick 1a31be6 primo commit
pick 7fc64b4 secondo commit
pick 7d33018 terzo commit

Per unificare tutti i commit nel primo, rimuovere la parola pick prima del secondo e dellultimo commit e
sostituirla con la parola squash, o anche solo s. Quando si salva, git inizier il rebase e, in caso di successo,
chieder di modificare il messaggio di commit, che come predefinito una lista di messaggi di commit di tutti i
commit. Dopo aver finito, eseguire il push.
Note: Tutte le patch da inviare devono essere rilasciate sotto licenza MIT, a meno che non sia specificato diversamente nel codice.
Tutti i merge di fix di bug nei rami di manutenzione subiscono merge anche nei rami pi recente, regolarmente.
Per esempio, se si propone una patch per il ramo 2.0, la patch sar applicata dal team anche al ramo master.
Segnalazione di un problema di sicurezza
Si trovato un problema di sicurezza in Symfony2? Non utilizzare la lista o il bug tracker. Tutte le questioni di
sicurezza devono essere inviate a security [at] symfony-project.com. Le email inviate a questo indirizzo verranno
inoltrate al team di sviluppo principale di Symfony .
Per ogni reposrt, prima si cercher di confermare la vulnerabilit. Quando confermata, il team di sviluppo lavorer
ad una soluzione seguendo questi passi:
1. Inviare un riconoscimento al segnalatore;
2. Lavorare su una patch;
3. Scrivere un post che descriva la vulnerabilit, i possibili exploit e come aggiornare le applicazioni afflitte;
4. Applicare la patch a tutte le versioni di Symfony in manutenzione;
5. Pubblicare il post sul blog ufficiale di Symfony.
Note: Nel periodo in cui si lavora alla risoluzione della patch, si prega di non rivelare pubblicamente la problematica.

Eseguire i test di Symfony2


Prima di inviare una patch, occorre eseguire tutti i test di Symfony2, per assicurarsi di non aver rotto nulla.
PHPUnit

Per eseguire i test di Symfony2, installare prima PHPUnit 3.6.4 o successivi.


$
$
$
$

pear
pear
pear
pear

channel-discover pear.phpunit.de
channel-discover components.ez.no
channel-discover pear.symfony-project.com
install phpunit/PHPUnit

Dipendenze (opzionali)

Per eseguire tutti i test, inclusi quelli che hanno dipendenze esterne, Symfony2 deve poterle scaricare. Per impostazione predefinita, sono auto-caricati dalla cartella vendor/ (vedere autoload.php.dist).

7.1. Contribuire

637

Symfony2 documentation Documentation, Release 2

I test necessitano delle seguenti librerie di terze parti:


Doctrine
Swiftmailer
Twig
Monolog
Per installarle tutte, eseguire lo script vendors:
$ php vendors.php install

Note: Si noti che lo script ha bisogno di tempo per terminare.


Dopo linstallazione, si possono aggiornare i venditori alle loro ultime versioni, con il comando seguente:
$ php vendors.php update

Esecuzione

Prima di tutto, aggiornare i venditori (vedere sopra).


Quindi, eseguire i test dalla cartella radice di Symfony2, con il comando seguente:
$ phpunit

Loutput dovrebbe mostrare OK. Altrimenti, occorre appurare quello che si verificato e se i test sono rotti per
colpa di una propria modifica.
Tip: Eseguire i test prima di applicare le proprie modifiche, per assicurarsi che girino correttamente con la propria
configurazione.

Copertura del codice

Se si aggiunge una nuova caratteristica, occorre anche verificare la copertura del codice, usando lopzione
coverage-html:
$ phpunit --coverage-html=cov/

Verificare la copertura del codice, aprendo la pagina generata cov/index.html in un browser.


Tip: La copertura del codice funziona solo con XDebug abilitato e tutte le dipendenze installate.

Standard del codice


Contribuendo al codice di Symfony2, bisogna seguire i suoi standard. Per farla breve, ecco una regola doro:
imitare il codice esistente di Symfony2. La maggior parte dei bundle e delle librerie open source usati da
Symfony2 segue le stesse linee guida.
Ricordare che il vantaggio principale degli standard che ogni pezzo di codice sembra familiare, non che questo
o quello siano pi o meno leggibili.
Poich unimmagine (o un po di codice) vale pi di mille parole, ecco un breve esempio contenente la maggior
parte delle caratteristiche descritte sotto:

638

Chapter 7. Contributi

Symfony2 documentation Documentation, Release 2

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Acme;
class Foo
{
const SOME_CONST = 42;
private $foo;
/**
* @param string $dummy Some argument description
*/
public function __construct($dummy)
{
$this->foo = $this->transform($dummy);
}
/**
* @param string $dummy Some argument description
* @return string|null Transformed input
*/
private function transform($dummy)
{
if (true === $dummy) {
return;
}
if (string === $dummy) {
$dummy = substr($dummy, 0, 5);
}
return $dummy;
}
}

Struttura

Non usare mai gli short tag (<?);


Non terminare i file delle classi con il tag di chiusura ?>;
Indentare con quattro spazi (mai con le tabulazioni);
Usare il carattere linefeed (0x0A) per terminare le righe;
Aggiungere un singolo spazio dopo ogni virgola delimitatrice;
Non mettere spazi dopo una parentesi chiusa e prima di una aperta;
Aggiungere un singolo spazio intorno agli operatori (==, &&, ...);
Aggiungere un singolo spazio prima di aprire le parentesi di una struttura di controllo (if, else, for, while,
...);

7.1. Contribuire

639

Symfony2 documentation Documentation, Release 2

Aggiungere una riga vuota prima delle istruzioni return, a meno che non siano soli dentro una struttura di
controllo (come un if );
Non aggiungere spazi finali in fondo alle righe;
Usare le parentesi graffe per indicare una struttura di controllo, indipendentemente dal numero di istruzioni
contenute;
Inserire le parentesi graffe su una nuova riga per classi, metodi e funzioni;
Separare le istruzioni condizionali (if, else, ...) e la parentesi graffa di apertura con un singolo spazio e senza
nuove righe;
Dichiarare esplicitamente la visibilit di classi, metodi e propriet (non usare var);
Usare le costanti native di PHP in minuscolo: false, true e null. Lo stesso per array();
Usare stringhe maiuscole per le costanti, con parole separate da trattini bassi;
Definire una classe per file (non si applica a classi private di helper che non devono essere istanziate
dallesterno e quindi esulano dallo standard PSR-0);
Dichiarare le propriet di una classe prima dei metodi;
Dichiarare prima i metodi pubblici, poi quelli protetti e infine quelli privati;
Convenzioni sui nomi

Usare camelCase, non i trattini bassi, per nomi di variabili, di funzioni e di metodi;
Usare i trattini bassi per nomi di opzioni e parametri;
Usare gli spazi dei nomi per tutte le classi;
Aggiungere il suffisso Interface alle interfacce;
Usare caratteri alfanumerici e trattini bassi per i nomi di file;
Non dimenticare di dare unocchiata al documento pi prolisso sulle convenzioni, per considerazioni
pi soggettive sulla nomenclatura.
Documentazione

Aggiungere blocchi PHPDoc per ogni classe, metodo e funzione;


Omettere il tag @return, se il metodo non restituisce nulla;
Le annotazioni @package e @subpackage non sono usate.
Licenza

Symfony rilasciato sotto licenza MIT e il blocco della licenza deve essere presente in cima a ogni file PHP,
prima dello spazio dei nomi.
Convenzioni
La documentazione Standard del codice descrive gli standard del codice per i progetti Symfony2 e per i bundle
interni e di terze parti. Tale documento descrive gli standard e le convenzioni utilizzate nel nucleo del framework
per renderlo pi coerente e prevedibile.

640

Chapter 7. Contributi

Symfony2 documentation Documentation, Release 2

Nomi dei metodi

Quando un oggetto ha molte relazioni principali correlate a cose (oggetti, parametri, ...) i nomi dei metodi
sono normalizzati in:
get()
set()
has()
all()
replace()
remove()
clear()
isEmpty()
add()
register()
count()
keys()
Lutilizzo di questi metodi sono permessi solo quando chiaro che si tratti di una relazione principale:
un CookieJar ha molti oggetti Cookie;
un servizio Container ha molti servizi e molti parametri (come servizi nelle relazioni principali, quindi
si usa la convenzione);
una Console di Input ha molti argomenti e molte opzioni. Non ce una relazione principaleThere is no
main e quindi questa convezione non applicata
Per le relazioni per le quali non si pu applicare la naming convention bisogna invece seguire i seguenti metodi
(dove XXX il nome della cosa relazionata ):
Relazione principale
get()
set()
n/d
has()
all()
replace()
remove()
clear()
isEmpty()
add()
register()
count()
keys()

Altre relazioni
getXXX()
setXXX()
replaceXXX()
hasXXX()
getXXXs()
setXXXs()
removeXXX()
clearXXX()
isEmptyXXX()
addXXX()
registerXXX()
countXXX()
n/d

Note: Mentre setXXX e replaceXXX sono molto simili, ce una differenza: setXXX pu sostituire o
aggiungere nuovi elemento alla relazione. replaceXXX daltra parte espressamente limitata ad aggiungere nuove
elementi e inoltre dovrebbe lanciare eccezioni in quei casi.

Licenza di Symfony2
Symfony2 distribuito sotto licenza MIT.
Secondo Wikipedia:
7.1. Contribuire

641

Symfony2 documentation Documentation, Release 2

una licenza permissiva, cio permette il riutilizzo nel software proprietario sotto la condizione
che la licenza sia distribuita con tale software. anche una licenza GPL-compatibile, cio la GPL
permette di combinare e ridistribuire tale software con altro che usa la Licenza MIT.
La licenza

Copyright (c) 2004-2011 Fabien Potencier


Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the Software), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

7.1.2 Contribuire alla documentazione


Contribuire alla documentazione
La documentazione importante tanto quanto il codice. E segue esattamente gli stessi principi: DRY, tests,
facilit di manutenzione, estensibilit, ottimizzazione, e refactoring solo per nominarne alcuni. E certamente la
documentazione ha bug, errori di battitura, difficolt di lettura dei tutorial, e molto altro.
Contribuire

Prima di contribuire, necessario famigliarizzare con il linguaggio di markup usato per la documentazione.
La documentazione di Symfony 2 ospitata da GitHub:
https://github.com/symfony/symfony-docs

Se si vuole inviare una patch, fare un fork del repository ufficiale su GitHub e fare un clone:
git://github.com/symfony/symfony-docs.git

A meno di non documentare una caratteristica aggiunta in Symfony 2.1, le modifiche vanno basate sul ramo 2.0,
non sul ramo master. Per poterlo fare, eseguire un checkout del ramo 2.0 prima del prossimo passo:
$ git checkout 2.0

Quindi, creare un ramo dedicato per le proprie modifiche (per questioni organizzative):
$ git checkout -b miglioramenti_di_pippo_e_pluto

Si possono ora eseguire le proprie modifiche in tale ramo. Quando si ha finito, fare il push di quest
ramo nel proprio fork su GitHub e richiedere un pull. La richiesta di pull sar tra il proprio ramo
miglioramenti_di_pippo_e_pluto e il ramo master di symfony-docs.

642

Chapter 7. Contributi

Symfony2 documentation Documentation, Release 2

Se le proprie modifiche sono basate sul ramo 2.0, occorre seguire il collegamento di modifica del commit e
cambiare il ramo base in @2.0:

GitHub spiega largomento in modo dettagliato, su richieste di pull.


Note: La documentazione di Symfony2 rilasciata sotto licenza Creative Commons Attribuzione - Condividi
allo stesso modo 3.0 Unported.

Segnalare una problematica

Il modo pi semplice di contribuire segnalando una problematica: un errore di battitura, un errore grammaticale,
un bug nel codice di esempio, e cos via
Passi:
Segnalare un bug attraverso il bug Tracker;
(opzionale) Inviare una patch.
Traduzione

Leggere la documentazione apposita.


Formato della documentazione
La documentazione di Symfony2 utilizza reStructuredText che utilizza come linguaggio di markup Sphinx per la
generazione delloutput (HTML, PDF, ...).
reStructuredText

reStructuredText is an easy-to-read, what-you-see-is-what-you-get plaintext markup syntax and parser system.


Si pu imparare di pi su questa sintassi leggendo documents si Symfony2 oppure leggendo la reStructuredText
Primer nel sito web di Sphinx.
Se si ha dimestichezza con Markdown, bisogna fare attenzione alle cose simili, ma differenti:
Le liste cominciano allinizio della linea (non hanno bisogno di indentazione)
I blocchi di codice utilizzano le doppie apici (come queste).
Sphinx

Sphinx un sistema di compilazione che aggiunge alcuni piacevoli strumenti per creare documentazione da documenti reStructuredText. Come tale, essa aggiunge nuove direttive e interpreta ruoli di testo definiti nello standard
reST markup.

7.1. Contribuire

643

Symfony2 documentation Documentation, Release 2

Colorazione della sintassi Tutti i blocchi di codice utilizzano PHP come linguaggio predefinito. possibile
cambiarlo con la direttiva code-block:
.. code-block:: yaml
{ foo: bar, bar: { foo: bar, bar: baz } }

Se il proprio codice PHP comincia con <?php, allora si avr bisogno di utilizzare html+php come pseudolinguaggio:
.. code-block:: html+php
<?php echo $this->foobar(); ?>

Note: La lista dei linguaggi supportati disponibile nel sito di Pygments.

Blocchi di configurazione Ogni volta che si mostra una configurazione, per mostrarla in tutti i formati supportati
,bisogna utilizzare la direttiva configuration-block (PHP, YAML e XML):
.. configuration-block::
.. code-block:: yaml
# Configurazione in YAML
.. code-block:: xml
<!-- Configurazione in XML //-->
.. code-block:: php
// Configuration in PHP

Il precedente snippet reST mostra un blocco come di seguito:


YAML
# Configurazione in YAML

XML
<!-- Configurazione in XML //-->

PHP
// Configuration in PHP

Ecco la lista dei formati attualmente supportati:


Formato markup
html
xml
php
yaml
jinja
html+jinja
jinja+html
php+html
html+php
ini
php-annotations

644

Mostrato
HTML
XML
PHP
YAML
Twig
Twig
Twig
PHP
PHP
INI
Annotazioni

Chapter 7. Contributi

Symfony2 documentation Documentation, Release 2

Test della documentazione Per fare un test della documentazione, prima di un commit:
Installare Sphinx;
Eseguire la preparazione rapida di Sphinx;
Installare lestensione configuration-block di Sphinx (vedere sotto);
Eseguire make html e controllare lHTML generato nella cartella build.
Installare lestensione configuration-block di Sphinx
Scaricare lestensione dal repository configuration-block
Copiare il file configurationblock.py nella cartella _exts della propria cartella dei sorgenti (in
cui si trova conf.py)
Aggiungere le righe seguenti al file conf.py:
# ...
sys.path.append(os.path.abspath(_exts))
# ...
# add configurationblock to the list of extensions
extensions = [configurationblock]

Traduzioni
La documentazione di Symfony2 scritta originariamente in inglese e molte persone sono coinvolte nel processo
di traduzione.
Contribuire

Prima di tutto, bisogna diventare familiari con il linguaggio di markup usato dalla documentazione
Successivamente, iscriversi alla lista Symfony docs, per poter collaborare.
Infine, trovare il repository master per il linguaggio per il quale si vuole contribuire. Di seguito la lista dei
principali repository master:
Inglese: http://github.com/symfony/symfony-docs
Francese: https://github.com/gscorpio/symfony-docs-fr
Italiano: https://github.com/garak/symfony-docs-it
Giapponese: https://github.com/symfony-japan/symfony-docs-ja
Polacco: http://github.com/ampluso/symfony-docs-pl
Rumeno: http://github.com/sebio/symfony-docs-ro
Russo: http://github.com/avalanche123/symfony-docs-ru
Spagnolo: https://github.com/gitnacho/symfony-docs-es
Note: Se si vuole contribuire nella truduzione di un nuovo linguaggio, leggere la sezione dedicata.

Far parte del team di traduzione

Se si vuole aiutare nella traduzione di alcuni documenti nella propria lingua o risolvere dei bug, questo il semplice
processo da seguire per far parte del team:

7.1. Contribuire

645

Symfony2 documentation Documentation, Release 2

Presentarsi sulla lista Symfony docs;


(opzionale) Chiedere su quali documenti si puo lavorare;
Forkare il repository master della propria lingua (cliccare il bottone Fork nella pagina di Github);
Tradurre qualche documento;
Fare una richiesta di pull (cliccare sul bottone Pull Request della propria pagina di Github);
Il team manager accetta le modifiche e ne fa il merge nel repository principale;
La documentazione sul sito aggiornata ogni notte dal repository ufficiale.
Aggiungere una nuova lingua

Questa sezione fornisce alcune guide per iniziare la traduzione di Symfony2 per una nuova lingua.
Iniziare la trduzione in una nuova lingua comporta molto lavoro, necessario parlarne sulla lista Symfony docs e
trovare altre persone che diano supporto.
Quando il team pronto, nominare un manager; Questultimo sar il responsabile del repository master.
Creare il repository e copiarci i documenti in lingua inglese.
Il team a questo punto pu iniziare il processo di traduzione.
Quando il team pensa che il repository sia in uno stato coerente e stabile ( tutto tradotto, oppure i documenti non
tradotti saranno rimossi) il tema manager pu fare richiesta che il repository sia aggiunto alla lista di quelli master
ufficiali, inviando unemail a Fabien (fabien.potencier at symfony.com).
Manutenzione

La traduzione non finisce quanto tutto stato tradotto. La documentazione evolve continuamente (aggiunta di
nuovi documenti, bug risolti, paragrafi riorganizzati). Il team deve seguire continuamente la documentazione in
inglese e applicare i cambiamenti alla propria versione quanto prima.
Caution: I linguaggi non correttamente manutenuti sono rimossi dalla lista di quelli ufficiali, poich la
documentazione non aggiornata pericolosa

Licenza della documentazione di Symfony2


La documentazione di Symfony2 rilasciata sotto licenza Creative Commons Attribuzione - Condividi allo stesso
modo 3.0 Unported.
Si liberi:
di condividere copiare, distribuire e trasmettere lopera;
di modificare adattare lopera.
Alle seguenti condizioni:
Attribuzione Si deve attribuire la paternit dellopera nei modi indicati dallautore o da chi ha dato lopera
in licenza e in modo tale da non suggerire che essi avallino voi o il modo in cui voi usate lopera;
Condividi allo stesso modo Se si altera o trasforma questopera, o se la si usa per crearne unaltra, si pu
distribuire lopera risultante solo con una licenza identica o equivalente a questa
Prendendo atto che:
Rinuncia possibile rinunciare a qualunque delle condizioni sopra descritte se si ottiene lautorizzazione
dal detentore dei diritti;

646

Chapter 7. Contributi

Symfony2 documentation Documentation, Release 2

Pubblico dominio Nel caso in cui lopera o qualunque delle sue componenti siano nel pubblico dominio
secondo la legge vigente, tale condizione non in alcun modo modificata dalla licenza;
Altri diritti La licenza non ha effetto in nessun modo sui seguenti diritti:
Le eccezioni, libere utilizzazioni e le altre utilizzazioni consentite dalla legge sul diritto dautore;
I diritti morali dellautore;
Diritti che altre persone possono avere sia sullopera stessa che su come lopera viene utilizzata, come
il diritto allimmagine o alla tutela dei dati personali.
Nota Ogni volta che si usa o distribuisce questopera, si deve farlo secondo i termini di questa licenza,
che va comunicata con chiarezza.
Questo un riassunto in linguaggio accessibile a tutti del Codice Legale (la licenza integrale).

7.1.3 Comunit
Incontri su IRC
Lo scopo di questi incontri discutere argomenti in tempo reale con molti degli sviluppatori di Symfony2.
Chiunque pu proporre argomenti sulla lista symfony-dev fino a 24 ore prima dellincontro, idealmente includendo
informazioni preparate e rilevanti, tramite un URL. 24 ore prima dellincontro, sar inviato un collegamento a un
doodle, con inclusa una lista di tutti gli argomenti proposti. Chiunque pu votare sugli argomenti, finch non inizia
lincontro, per definire lordine del giorno. Ogni argomento avr un tempo limite di 15 minuti e lintero incontro
durer unora, lasciando spazio per 4 argomenti.
Caution: Notare che lo scopo dellincontro non tanto quello di trovare soluzioni definitive, quanto piuttosto
assicurare che ci sia una comprensione comune del problema e spostare la discussione in avanti, con modalit
difficili da ottenere con strumenti che non siano in tempo reale.
Gli incontro si tengono ogni gioved alle 17:00 CET (+01:00), sul canale #symfony-dev del server IRC Freenode.
I log di IRC saranno in seguito pubblicati sul wiki, includendo un breve sommario di ogni argomento. Saranno
creati ticket per ogni task o problema identificato durante lincontro e incluso nel sommario.
Alcuni semplici linee guida per la partecipazione:
Si pu cambiare voto fino allinizio dellincontro, cliccando su Edit an entry;
Il doodle sar chiuso alla votazione allinizio dellincontro;
Lagenda definita dagli argomenti pi votati nel doodle, oppure dagli argomenti proposti prima, in caso di
pareggio;
Allinizio dellincontro, una persona si identificher come moderatore;
Il moderatore essenzialmente responsabile di assicurare il tempo limite di 15 minuti e che i task siano
chiaramente identificati;
Di solito il moderatore gestir anche la scrittura del sommario e la scrittura dei ticket, a meno che qualcun
altro non se ne prenda carico;
Chiunque pu unirsi ed esplicitamente invitato a partecipare;
Sarebbe ideale familiarizzare con largomento proposto, prima dellincontro;
Quando inizia un nuovo argomento, il proponente invitato a tenere una breve introduzione;
Chiunque pu quindi commentare, se lo ritiene opportuno;
A seconda del numero di partecipanti, ci si dovrebbe astenere dal propugnare troppo a lungo le proprie
opinioni;

7.1. Contribuire

647

Symfony2 documentation Documentation, Release 2

Tenere a mente che i log di IRC saranno successivamente pubblicati, quindi tutti avranno lopportunit di
rivedere i commenti pi di una volta;
I partecipanti sono incoraggiati ad alzare la mano per prendere la parola sui task definiti durante lincontro.
Ecco un esempio di doodle.
Altre risorse
Per poter seguire quello che succede nella comunit, si possono trovare utili le seguenti risorse:
Elenco di pull request aperte
Elenco di commit recenti
Elenco di bug e miglioramenti aperti
Elenco di bundle open source
Codice:
Bug |
Patch |
Sicurezza |
Test |
Standard del codice |
Convenzioni del codice |
Licenza
Documentazione:
Panoramica |
Formato |
Traduzioni |
Licenza
Comunit:
Incontri su IRC |
Altre risorse
Codice:
Bug |
Patch |
Sicurezza |
Test |
Standard del codice |
Convenzioni del codice |
Licenza
Documentazione:
Panoramica |
Formato |
Traduzioni |

648

Chapter 7. Contributi

Symfony2 documentation Documentation, Release 2

Licenza
Comunit:
Incontri su IRC |
Altre risorse

7.1. Contribuire

649

Symfony2 documentation Documentation, Release 2

650

Chapter 7. Contributi

INDEX

A
Ambienti, 314
Cartella cache, 318
Configurazione, 56
Creare un nuovo ambiente, 317
Eseguire ambienti diversi, 315
File di configurzione, 314
Introduzione, 55
Parametri esterni, 319
Assetic
Riferimento configurazione, 475
Autoloader
Configurazione, 435

B
Bundle
Best Practice, 341
Configurazione dellestensione, 347
Convenzioni di nomenclatura, 341
Ereditariet, 345, 346
Estensione, 348

C
Cache, 193, 195
Configurazione, 202
ESI, 203
Gateway, 194
Get condizionale, 201
Header Cache-Control, 197, 199
Header Etag, 200
Header Expires, 199
Header Last-Modified, 200
HTTP, 196
Invalidazione, 205
Metodi sicuri, 197
Proxy, 194
Reverse Proxy, 194
Scadenza HTTP, 198
Tipi, 194
Twig, 85
Validazione, 199
Varnish, 407
Vary, 202
CLI
ODM Doctrine, 630

ORM Doctrine, 120


Componenti
Routing, 458
Componenti di Symfony2, 30
Configuration
Convenzione, 354
Configurazione
Autoloader, 435
Cache, 202
Doctrine DBAL, 479
Modalit debug, 316
ODM MongoDB Doctrine, 631
PHPUnit, 130
Semantica, 347
Test, 129
Validazione, 134
Console
CLI, 436
Contenitore di servizi, 218
Configurare i servizi, 219
Configurazione avanzata, 230
Configurazione delle estensioni, 223
Cos un servizio?, 218
Cos?, 218
imports, 222
Referenziare i servizi, 225
Tag, 335
Controllore, 57
Accedere ai servizi, 64
Attivit comuni, 62
Ciclo di vita richiesta-controllore-risposta, 58
Classe base Controller, 62
Come servizi, 256
Formato dei nomi delle stringhe, 78
Gestire gli errori, 64
Inoltro, 63
La sessione, 65
Oggetto Request, 66
Oggetto Response, 66
Pagine 404, 64
Parametri del controllore, 60
Rendere i template, 64
Rinvio, 62
Rotte e controllori, 59
Semplice esempio, 58
Convenzione
651

Symfony2 documentation Documentation, Release 2

Configuration, 354
Convenzioni di nomenclatura
Bundle, 341
Convenzioni sui nomi
Distributore di eventi, 239
Creazione di pagine, 44
Esempio, 45
CSS Selector, 441

choice, 493, 508


collection, 496
country, 500
csrf, 502
date, 503
datetime, 505
email, 507
field, 513
file, 511
form, 514
hidden, 514
integer, 515
language, 516
locale, 518
money, 520
number, 522
password, 523
percent, 525
radio, 526
repeated, 527
search, 529
text, 530
textarea, 531
time, 532
timezone, 533
url, 535
Creare classi form, 155
Creare un form in un controllore, 144
Creazione di un form semplice, 143
Doctrine, 156
Ereditariet dei frammenti di template, 160
Eventi, 302
Gestione dellinvio del form, 146
Gruppi di validatori, 148
Incorporare form, 157
Indovinare il tipo di campo, 151, 152
Nomi per i frammenti di form, 160
Opzioni dei tipi di campo, 151
Personalizzare i campi, 159
Personalizzare la resa dei form, 286
Protezione CSRF, 163
Rendere manualmente ciascun campo, 153
Rendere un form in un template, 152
Riferimenti sui tipi, 490
Riferimento delle funzioni per i form in Twig, 538
Temi, 159
Temi globali, 161
Tipi di campo predefiniti, 149
Tipo di campo personalizzato, 308
Unire una collezione di form, 304
Validazione, 147
Visualizzazione di base nel template, 145

D
DBAL
Doctrine, 282
Debug, 418
Dependency Injection Container, 218
Dependency Injection, Estensione, 348
Dispatcher di eventi, 419, 421
Distributore di eventi, 239
Ascoltatori, 240
Bloccare il flusso degli eventi, 244
Convenzioni sui nomi, 239
Creare e distribuire un evento, 241
Eventi, 239
Sotto-classi evento, 239
Sottoscrittori, 243
Doctrine, 100
Aggiungere meta-dati di mappatura, 102
Comandi console ODM, 630
Comandi da console ORM, 120
Configurazione DBAL, 479
Configurazione ODM MongoDB, 631
DBAL, 282
Form, 156
Generare entit da una base dati esistente, 279
Riferimento configurazione ORM, 476
DomCrawler, 443

E
Email, 355
Gmail, 357
ESI, 203
Evento
Kernel, 237
kernel.controller, 237
kernel.exception, 238
kernel.request, 237
kernel.response, 238
kernel.view, 237

F
Finder, 447
Fogli di stile
Includere fogli di stile, 93
Fondamenti di Symfony2, 23
Richieste e risposte, 26
Form, 143
Campi
birthday, 490
checkbox, 492
652

H
Header HTTP
Cache-Control, 197, 199
Etag, 200
Expires, 199
Index

Symfony2 documentation Documentation, Release 2

Last-Modified, 200
Vary, 202
HTTP, 451
304, 201
Paradigma richiesta-risposta, 24
HttpFoundation, 451

I
Installazione, 41
Interno, 234
Gestione della richiesta, 236
Kernel, 235
Richieste interne, 236
Risoluzione dei controllori, 235

J
Javascript
Includere Javascript, 93

K
Kernel
Evento, 237

L
Layout, 409
Locale, 456
Log, 412

M
Monolog
Riferimento configurazione, 488

P
PHPUnit
Configurazione, 130
Prestazioni
Autoloader, 233
Cache bytecode, 233
File di avvio, 233
Process, 457
Profilazione
Raccoglitore di dati, 423
Profiler, 244
Usare il servizio del profiler, 245
Visualizzare, 246
Visualizzazione, 245

R
Requisiti, 588
Richiesta
Aggiungere un formato di richiesta e un tipo mime,
421
Riferimento configurazione
Assetic, 475
Framework, 471
Monolog, 488
ORM Doctrine, 476

Index

Swiftmailer, 484
Twig, 487
WebProfiler, 489
Rotte, 67
Basi, 67
Controllori, 78
Creazione di rotte, 69
Debug, 81
Esempio avanzato, 77
Generare URL in un template, 82
Generazione di URL, 81
Importare risorse per le rotte, 79
Parametro _format, 77
Requisiti, 73
Requisiti dello schema, 257
Requisiti di metodi, 76
Segnaposti, 70
Sotto il cofano, 68
URL assoluti, 82
Routing, 458
Allow / in route parameter, 258

S
Servizi web
SOAP, 425
Session
Database Storage, 338
Sessione, 65
Sicurezza, 165
Access Control List (ACL), 371
Concetti avanzati su ACL, 374
Fornitore di autenticazione personalizzato, 398
Fornitore di entit, 387
Fornitore di utenti, 387
Fornitore utenti, 394
Puntare il percorso di rinvio, 406
Riferimento configurazione, 481
Sicurezza, Votanti, 368
single
Template
Sovrascrivere template, 96
Sovrascrivere template di eccezioni, 96
single Sessione
Messaggi flash, 65
Slot, 410
Struttura delle cartelle, 50

T
Template, 83
Collegare le pagine, 91
Collegare le risorse, 93
Convenzioni dei nomi, 88
Cos un template?, 83
Ereditariet, 85
Escape delloutput, 97
Formati, 98
Helper, 89, 411
Il servizio templating, 95
653

Symfony2 documentation Documentation, Release 2

Include, 411
Includere altri template, 89
Includere fogli di stile e Javascript, 93
Inserire azioni, 90
Inserire pagine, 411
Layout, 409
Lo schema di ereditariet a tre livelli, 97
Posizioni dei file, 88
Slot, 410
Variabili globali, 408
Template PHP, 408
Template Tag e helper, 89
Test, 121, 232, 361
Asserzioni, 124
Autenticazione HTTP, 361
Client, 124
Configurazione, 129
Crawler, 126
Doctrine, 363
Profilazione, 362
Test funzionali, 122
Test unitari, 121
Traduzioni, 206
Cataloghi di messaggi, 209
Configurazione, 207
Creazione delle traduzioni, 210
Domini dei messaggi, 212
Fallback e locale predefinito, 213
Locale dellutente, 213
Nei template, 216
Pluralizzazione, 214
Sedi per le traduzioni e convenzioni sui nomi, 210
Segnaposto per i messaggi, 208
Traduzioni di base, 207
Twig
Cache, 85
Introduzione, 84
Riferimento configurazione, 487

V
Validazione, 131
Configurazione, 134
Configurazione dei vincoli, 136
Obiettivi dei vincoli, 138
Usare il validatore, 132
Validazione con i form, 133
Valori grezzi di validazione, 142
Vincoli, 134
Vincoli personalizzati, 312
Vincoli sui getter, 139
Vincoli sulle propriet, 138
Varnish
Configurazione, 407
Invalidare, 407

654

Index

You might also like