.. index::
single: Config; Definindo e processando valores de configuração
Definindo e processando valores de configuração
===============================================
Validando valores de configuração
---------------------------------
Após carregar valores de configuração a partir de diversos tipos de fontes, os
valores e suas estruturas podem ser validados utilizando a parte "Definition" do
Config Component. É comumente esperado que valores de configuração exibam algum
tipo de hierarquia. Além de que, valores devem ser de algum certo tipo, ser
restrito em número, ou ser um item de um dado conjunto de valores. Por exemplo,
a configuração a seguir (em Yaml) demonstra uma hierarquia clara e algumas
regras de validação que devem ser aplicadas a ela (como: "o valor para
``auto_connect`` deve ser um valor booleano"):
.. code-block:: yaml
auto_connect: true
default_connection: mysql
connections:
mysql:
host: localhost
driver: mysql
username: user
password: pass
sqlite:
host: localhost
driver: sqlite
memory: true
username: user
password: pass
Quando se carrega multiplos arquivos de configuração, deve ser possível
fundir e sobrescrever alguns valores. Outros valores não devem ser fundidos
e permanecerem da mesma forma que estão quando encontrados pela primeria vez.
Além de que, alhumas chaves só estão disponíveis quando outra chave tem um
valor específico (na configuração de exmeplo abaixo: a chave ``memory`` só
faz sentido quando ``driver`` for ``sqlite``).
Definindo a hierarquia de valores de configuração utilizando o Treebuilder
--------------------------------------------------------------------------
Todas as regras referentes a configuração de valores podem ser definidas
utilizando o :class:`Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder`.
Uma instância :class:`Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder`
deve ser retornada a partir de uma classe ``Configuration`` customizada, que
implementa o :class:`Symfony\\Component\\Config\\Definition\\ConfigurationInterface`::
namespace Acme\DatabaseConfiguration;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class DatabaseConfiguration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('database');
// ... adiciona definições de nó a raiz da ávore
// ... add node definitions to the root of the tree
return $treeBuilder;
}
}
Adicionando definições de nó a arvore
-------------------------------------
Nós de variável
~~~~~~~~~~~~~~~
Uma árvore contém definições de nós que podem ser estabelecidas de forma semântica.
Isso significa que utilizando identação e notação fluente, é possível refletir a
estrutura real dos valores de configuração::
$rootNode
->children()
->booleanNode('auto_connect')
->defaultTrue()
->end()
->scalarNode('default_connection')
->defaultValue('default')
->end()
->end()
;
O nó raiz em sí é um nó array, e possui filhos, como o nó booleano ``auto_connect``
e o nó escalar ``default_connection``. Em geral: após definir um nó, uma chamada
para ``end()`` leva você um passo a cima na hierarquia.
Tipo de nó
~~~~~~~~~
É possível validar o tipo de um valor fornecido utilizando a definição apropriada
de nó. O tipo nó está disponível para:
* scalar
* boolean
* integer (new in 2.2)
* float (new in 2.2)
* enum (new in 2.1)
* array
* variable (no validation)
e são creiados com ``node($name, $type)`` ou com seu método atalho
``xxxxNode($name)``.
Limitadores de nós numéricos
~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 2.2
Os nós numéricos (float and integer) são novos em 2.2
Os nós numéricos (float and integer) fornecem dois limitadores extras -
:method:`Symfony\\Component\\Config\\Definition\\Builder::min` e
:method:`Symfony\\Component\\Config\\Definition\\Builder::max` -
possibilitando validar o valor::
$rootNode
->children()
->integerNode('positive_value')
->min(0)
->end()
->floatNode('big_value')
->max(5E45)
->end()
->integerNode('value_inside_a_range')
->min(-50)->max(50)
->end()
->end()
;
Nós enum
~~~~~~~~~~
.. versionadded:: 2.1
O nó enum é novo no Symfony 2.1
Nós enum fornecem um limitador para comparar a entrada contra um conjunto de
valores::
$rootNode
->children()
->enumNode('sexo')
->values(array('masculino', 'feminino'))
->end()
->end()
;
Isso irá restringir a opção ``sexo`` a ser ``masculino`` ou ``feminino``.
Nós array
~~~~~~~~~~~
É possível adicionar um nó mais profundo à hierarquia, acrescentando um
nó array. O próprio nó array pode ter um conjunto de variáveis nó pré definidas::
$rootNode
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')->end()
->scalarNode('host')->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->end()
->end()
->end()
;
Ou você pode definir um protótipo para cada nó dentro de um nó array::
$rootNode
->children()
->arrayNode('connections')
->prototype('array')
->children()
->scalarNode('driver')->end()
->scalarNode('host')->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->end()
->end()
->end()
;
Um protótipo pode ser utilizado para adicionar uma definição que pode ser repetida
várias vezes dentro do nó atual. De acordo com a definição do protótipo no exemplo
acima, é possível ter multiplas arrays connection (contendo um ``driver``, ``host``
, etc.).
Opções do nó array
~~~~~~~~~~~~~~~~~~
Antes de definir o filho de um nó array, você pode fornecer opções, como:
``useAttributeAsKey()``
Fornece o nome de um nó filho, do qual o valor deve ser utilizado como a chave no array
resultante.
``requiresAtLeastOneElement()``
Deve haver pelo menos um elemento no array (funciona somente quando ``isRequired()``
também é chamado).
``addDefaultsIfNotSet()``
Se qualquer nós filho tiver valor padrão, use-os caso valores explicitos não forem fornecidos.
Um exemplo disso::
$rootNode
->children()
->arrayNode('parameters')
->isRequired()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('value')->isRequired()->end()
->end()
->end()
->end()
->end()
;
Em YAML, a configuração poderia ser dessa forma:
.. code-block:: yaml
database:
parameters:
param1: { value: param1val }
Em XML, cada nó ``parameters`` teria um atributo ``name`` (junto com
``value``), que seria removido e utilizado como a chave para aquele
elemento no array final. O ``useAttributeAsKey`` É útil para normalizar
a forma como arrays são especificados entre diferentes formatos como XML
e YAML.
Valores padrão e valores obrigatórios
---------------------------
For all node types, it is possible to define default values and replacement
values in case a node
has a certain value:
``defaultValue()``
Define o valor padrão
``isRequired()``
Deve ser definido (mas pode ser vazio)
``cannotBeEmpty()``
Não pode conter um valor vazio
``default*()``
(``null``, ``true``, ``false``), atalho para ``defaultValue()``
``treat*Like()``
(``null``, ``true``, ``false``), fornece um valor substituto no caso do valor ser ``*.``
.. code-block:: php
$rootNode
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('host')
->defaultValue('localhost')
->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->booleanNode('memory')
->defaultFalse()
->end()
->end()
->end()
->arrayNode('settings')
->addDefaultsIfNotSet()
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->defaultValue('value')
->end()
->end()
->end()
->end()
;
Seções Opcionais
-----------------
.. versionadded:: 2.2
Os métodos ``canBeEnabled`` e ``canBeDisabled`` são novos no Symfony 2.2
Se você possui uma seção inteira que é opicional e pode ser enabled/disabled,
você pode se aproveitar da vantagem dos métodos atalho
:method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeEnabled` e
:method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeDisabled`::
$arrayNode
->canBeEnabled()
;
// é equivalente a
$arrayNode
->treatFalseLike(array('enabled' => false))
->treatTrueLike(array('enabled' => true))
->treatNullLike(array('enabled' => true))
->children()
->booleanNode('enabled')
->defaultFalse()
;
O método ``canBeDisabled`` procura pela mesma exceção que a seção habilitaria
por padrão.
Opções de mesclagem
---------------
Opções extras referentes ao processo de mesclagem podem ser fornecidas. Para arrays:
``performNoDeepMerging()``
Quando o valor também é definido em uma segunda array configuração, não
tenta mesclar um array, mas sobrescrevê-lo inteiramente
Para todos os nós:
``cannotBeOverwritten()``
não permite que outros arrays configuração sobrescrevam um valor existente neste nó
Seções de acréscimo
------------------
Se você tiver uma configuração complexa para validar, então a árvore pode
crescer para se tornar extensa e você pode querer divi-la em seções. Você
pode fazer isso criando uma seção, um nó separado e então acrescenta-lo na
árvore principal com ``append()``::
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('database');
$rootNode
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('host')
->defaultValue('localhost')
->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->booleanNode('memory')
->defaultFalse()
->end()
->end()
->append($this->addParametersNode())
->end()
->end()
;
return $treeBuilder;
}
public function addParametersNode()
{
$builder = new TreeBuilder();
$node = $builder->root('parameters');
$node
->isRequired()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('value')->isRequired()->end()
->end()
->end()
;
return $node;
}
Isso também é útil para ajudar a previnir que você se repita se você tiver
seções do config que são repetidas em diferentes locais.
Nomralização
-------------
Quando os arquivos config são processados, eles são primeiramente normalizados,
em seguida mesclados e finalmente a árvore é utilizada para validar o resultado
do array. O processo de normalização é utilizado para remover algumas das
diferenças que resultam de diferentes formatos de configuração, principalmente
as diferenças entre Yaml e XML.
O separador utilizado nas chaves são tipicamente ``_`` em Yaml e ``-`` em XML.
Por exemplo, ``auto_connect`` em Yaml e ``auto-connect``. A normalização
converteria ambos para ``auto_connect``.
.. caution::
A chaves alvo não será alterada se ela estiver misturada, como
``foo-bar_moo`` ou se ela já existir.
Outra diferença entre Yaml e XML é na forma em que os arrays de valores
podem ser representados. Em Yaml você pode ter:
.. code-block:: yaml
twig:
extensions: ['twig.extension.foo', 'twig.extension.bar']
e em XML:
.. code-block:: xml
twig.extension.foo
twig.extension.bar
Esse diferença pode ser removida na normalização através da pluralização
da chave utilizada no XML. Você pode especificar que você quer que uma
chave seja pluralizada dessa forma com ``fixXmlConfig()``::
$rootNode
->fixXmlConfig('extension')
->children()
->arrayNode('extensions')
->prototype('scalar')->end()
->end()
->end()
;
Se for uma pluralização irregular, você pode especificar o plural para
utilizar como um segundo argumento::
$rootNode
->fixXmlConfig('child', 'children')
->children()
->arrayNode('children')
->end()
;
Assim como corrigindo isso, ``fixXmlConfig`` garante que elementos xml individuais
ainda estejam sendo convertidos em array. Então você deve ter:
.. code-block:: xml
default
extra
e às vezes somente:
.. code-block:: xml
default
Por padrão ``connection`` seria um array no primeiro caso e uma string
no segundo tornado-a difícil de validar. Você pode assegurar que ele sempre
será um array com ``fixXmlConfig``.
Você pode controlar ainda mais o processo de normalização. Por exemplo,
Você pode querer permitir que uma string seja definida e utilizada como uma
chave particular ou várias chaves sejam definidas explicitamente. De modo que,
tudo menos ``name`` seja opcional nessa configuração:
.. code-block:: yaml
connection:
name: my_mysql_connection
host: localhost
driver: mysql
username: user
password: pass
você também pode permitir o seguinte:
.. code-block:: yaml
connection: my_mysql_connection
Mudando de um valor string para uma array associativa utilizando ``name`` como chave::
$rootNode
->children()
->arrayNode('connection')
->beforeNormalization()
->ifString()
->then(function($v) { return array('name'=> $v); })
->end()
->children()
->scalarNode('name')->isRequired()
// ...
->end()
->end()
->end()
;
Regras de validação
----------------
Validações mais avançadas podem ser forneceidas utilizado o
:class:`Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder`. Este
builder implementa uma interface fluente para estruturas de controle comuns.
O builder é utilizado para acrescentar regras de validação para definições de nó, como::
$rootNode
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')
->isRequired()
->validate()
->ifNotInArray(array('mysql', 'sqlite', 'mssql'))
->thenInvalid('Invalid database driver "%s"')
->end()
->end()
->end()
->end()
->end()
;
Uma regra de validação sempre tem uma parte "if". Você pode especificar esta
parte das seguintes formas:
- ``ifTrue()``
- ``ifString()``
- ``ifNull()``
- ``ifArray()``
- ``ifInArray()``
- ``ifNotInArray()``
- ``always()``
Uma regra de validação também requer uma parte "then":
- ``then()``
- ``thenEmptyArray()``
- ``thenInvalid()``
- ``thenUnset()``
Normalmente, "then" é um closure. Seu valor de retorno será utilizado como
um novo valor para o nó, ao invés
do valor original do nó.
Processando valores de configuração
-----------------------------------
O :class:`Symfony\\Component\\Config\\Definition\\Processor` utiliza a
árvore como ela foi construida utilizando o :class:`Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder`
para processar multiplos arrays de valores de configuração que deveriam
ser misturados. Se algum valor não for do tipo esperado, for obrigatório
e ainda indefinido, ou não puder ser validado de alguma outra forma, uma
uma exceção será lançada. Caso contrário o resultado é um array de valores
de configuração limpo::
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Config\Definition\Processor;
use Acme\DatabaseConfiguration;
$config1 = Yaml::parse(__DIR__.'/src/Matthias/config/config.yml');
$config2 = Yaml::parse(__DIR__.'/src/Matthias/config/config_extra.yml');
$configs = array($config1, $config2);
$processor = new Processor();
$configuration = new DatabaseConfiguration;
$processedConfiguration = $processor->processConfiguration(
$configuration,
$configs)
;