Table of Contents
Questions & Feedback
Found a typo or an error?
Want to improve this document? Edit it.
Need support or have a technical question?
Post to the user mailing-list.
Master Symfony2 fundamentals
Symfony hosting done right
Discover the SensioLabs Support
Come esporre una configurazione semantica per un bundle
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. Ciascuno 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 l'integrazione
con i form, che implica la definizione di alcuni servizi, così come anche
l'integrazione di altri componenti correlati:
- YAML
1 2 3
framework: # ... form: true
- XML
1 2 3
<framework:config> <framework:form /> </framework:config>
- PHP
1 2 3 4 5
$container->loadFromExtension('framework', array( // ... 'form' => true, // ... ));
Quando si crea un bundle, si hanno due scelte sulla gestione della configurazione:
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.Esporre una configurazione semantica (avanzato):
Questo è il modo usato per la configurazione dei bundle del nucleo (come descritto sopra). L'idea di base è che, invece di far sovrascrivere all'utente i singoli parametri, lasciare che ne configuri alcune opzioni create specificatamente. Lo sviluppatore del bundle deve quindi analizzare tale configurazione e caricare i servizi all'interno di una classe "Extension". Con questo metodo, non si avrà bisogno di importare alcuna risorsa di configurazione dall'appplicazione principale: la classe Extension può gestire tutto.
La seconda opzione, di cui parleremo, è molto più flessibile, 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, qualora fosse necessario.
Il secondo metodo ha diversi vantaggi:
- Molto più potente che definire semplici parametri: un valore specifico di un'opzione può scatenare la creazioni di molte definizioni di servizi;
- Possibilità di avere una gerarchia di configurazioni
- Fusione intelligente quando diversi file di configurazione (p.e.
config_dev.ymleconfig.yml) sovrascrivono le proprie configurazioni a vicenda; - Validazione della configurazione (se si usa una classe di configurazione);
- auto-completamento nell'IDE quando si crea un XSD e lo sviluppatore usa XML.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
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
1 2
# app/config/config.yml acme_hello: ~
- XML
1 2 3 4 5 6 7 8 9 10 11 12
<!-- 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/schema/hello-1.0.xsd"> <acme_hello:config /> <!-- ... --> </container>
- PHP
1 2
// 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 l'utente 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 dell'array $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() dell'estensione (Symfony2 converte automaticamente
XML e YAML in array).
Si prenda la seguente configurazione:
- YAML
1 2 3 4
# app/config/config.yml acme_hello: pippo: valoreDiPippo pluto: valoreDiPluto
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13
<!-- 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/schema/hello-1.0.xsd"> <acme_hello:config pippo="valoreDiPippo"> <acme_hello:pluto>valoreDiPluto</acme_hello:pluto> </acme_hello:config> </container>
- PHP
1 2 3 4 5
// app/config/config.php $container->loadFromExtension('acme_hello', array( 'pippo' => 'valoreDiPippo', 'pluto' => 'valoreDiPluto', ));
L'array passato al metodo load() sarà simile a questo:
1 2 3 4 5 6 | array(
array(
'pippo' => 'valoreDiPippo',
'pluto' => 'valoreDiPluto',
)
)
|
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, l'array in uscita sarà simile a questo:
1 2 3 4 5 6 7 8 9 10 | array(
array(
'pippo' => 'valoreDiPippo',
'pluto' => 'valoreDiPluto',
),
array(
'pippo' => 'valoreDevDiPippo',
'baz' => 'nuovaVoceDiConfig',
),
)
|
L'ordine 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:
1 2 3 4 5 6 7 8 9 | public function load(array $configs, ContainerBuilder $container)
{
$config = array();
foreach ($configs as $subConfig) {
$config = array_merge($config, $subConfig);
}
// usare ora l'array $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:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 un'opzione
enabled è impostata a true:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 dall'utente 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <!-- 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/services/services-1.0.xsd">
<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
all'utente di definire questa opzione type sotto una chiave di nome mio_tipo.
Aggiungere al metodo load() il codice seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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');
if (!isset($config['mio_tipo'])) {
throw new \InvalidArgumentException(
'The "mio_tipo" option must be set'
);
}
$container->setParameter(
'acme_hello.my_service_type',
$config['mio_tipo']
);
}
|
L'utente ora è in grado di configurare effettivamente il servizio, specificando il
valore di configurazione mio_tipo:
- YAML
1 2 3 4
# app/config/config.yml acme_hello: mio_tipo: pippo # ...
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13
<!-- 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/schema/hello-1.0.xsd"> <acme_hello:config mio_tipo="pippo"> <!-- ... --> </acme_hello:config> </container>
- PHP
1 2 3 4 5
// app/config/config.php $container->loadFromExtension('acme_hello', array( 'mio_tipo' => 'pippo', ..., ));
Parametri globali¶
Quando si configura il contenitore, si hanno a disposizione i seguenti parametri globali:
kernel.namekernel.environmentkernel.debugkernel.root_dirkernel.cache_dirkernel.logs_dirkernel.bundle_dirskernel.bundleskernel.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.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // src/Acme/HelloBundle/DependencyInjection/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('pluto')->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 un'opzione che non sia mio_tipo, l'utente sarà avvisato con un'eccezione
del passaggio di un'opzione non supportata:
1 2 3 4 5 6 7 8 | public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// ...
}
|
Il metodo processConfiguration() usa l'albero 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.
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
__construct(). Se si ha qualcosa di diverso, la propria classe
Extension dovrà sovrascrivere il metodo
Extension::getConfiguration()
e restituire un'istanza di
Configuration.
Si possono aggiungere commenti ed esempi alla configurazione, usando i metodi
->setInfo() e ->setExample():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 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('pluto')
->setInfo('cosa configura mio_tipo')
->setExample('impostazione di esempio')
->end()
->end()
;
return $treeBuilder;
}
}
|
Il testo apparirà come commenti YAML nell'output del comando
config:dump-reference.
Convenzioni per l'estensione¶
Quando si crea un'estensione, seguire queste semplici convenzioni:
- L'estensione deve trovarsi nel sotto-spazio dei nomi
DependencyInjection; - l'estensione deve avere lo stesso nome del bundle, ma con
Extension(AcmeHelloExtensionperAcmeHelloBundle); - L'estensione 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
build() nel proprio
bundle:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // ...
use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass;
class AcmeHelloBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
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() dell'estensione sarà richiamato solo se l'utente
specifica l'alias 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.





is a trademark of Fabien Potencier. All rights reserved.