Neste artigo, tentaremos lidar com um componente chamado Config , que ajuda a carregar e processar vários dados, independentemente da fonte.
A seguir, é apresentada uma tradução do artigo Visão geral dos componentes do Symfony2: Config . O original foi publicado em 2014 e indica a segunda versão do Symfony, mas as informações são relevantes para a última versão do momento, no momento.
Vamos imaginar que queremos criar um gerador de blog que terá vários parâmetros, como título ( título ), descrição ( descrição ), número de postagens na página principal ( posts_main_page ), ícones de mídia social ( social ) e presença ou ausência de feeds RSS
( rss ) . Para esses fins, descreveremos o arquivo de configuração no formato YAML
:
blog: title: description: ... rss: true posts_main_page: 2 social: twitter: url: http://twitter.com/raulfraile icon: twitter.png sensiolabs_connect: url: https://connect.sensiolabs.com/profile/raulfraile icon: sensiolabs_connect.png
Agora tentaremos analisar nosso arquivo, verificar a disponibilidade dos campos obrigatórios e definir valores padrão, se necessário. Verificaremos todos os dados obtidos quanto à conformidade com as regras estabelecidas, por exemplo, rss pode conter apenas um valor booleano e posts_main_page deve conter um valor inteiro no intervalo de 1 a 10. Teremos de repetir esses procedimentos sempre que o arquivo for acessado, a menos que o sistema de cache seja usado . Além disso, esse mecanismo complica o uso de arquivos de outros formatos, como INI
, XML
ou JSON
.
Para simplificar as ações acima, usaremos o componente Config . O componente é simples, bem testado e flexível o suficiente para uso em diferentes projetos.
Arquitetura
Dividiremos o projeto em duas partes principais:
- Definição da estrutura hierárquica dos parâmetros
O componente permite determinar o formato da fonte de configuração, que pode ser qualquer coisa, desde um simples arquivo INI
até algo mais exótico. A classe TreeBuilder ajudará a determinar os tipos de parâmetros, torná-los obrigatórios / opcionais e definir o valor padrão. - Detecção, carregamento e processamento.
Após a especificação do formato de origem, ele deve ser encontrado, carregado e processado. Finalmente, o componente retornará uma matriz simples com valores verificados ou lançará uma exceção por erro.
Exemplo
Vamos voltar ao nosso exemplo. E assim, queremos criar um sistema flexível de geração de blogs. Primeiro, definimos uma estrutura hierárquica (árvore), e uma instância da classe TreeBuilder nos ajudará com isso, o que fornece uma interface semelhante à sintaxe DSL
.
<?php namespace RaulFraile\Config; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Builder\TreeBuilder; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('blog'); $rootNode ->children() ->scalarNode('title') ->isRequired() ->end() ->scalarNode('description') ->defaultValue('') ->end() ->booleanNode('rss') ->defaultValue(false) ->end() ->integerNode('posts_main_page') ->min(1) ->max(10) ->defaultValue(5) ->end() ->arrayNode('social') ->arrayPrototype() ->children() ->scalarNode('url')->end() ->scalarNode('icon')->end() ->end() ->end() ->end() ->end() ; return $treeBuilder; } }
Não se preocupe se você vir uma estrutura semelhante de código PHP pela primeira vez, a sintaxe DSL
sempre parece um pouco estranha. No exemplo acima, determinamos o nó raiz do blog e construímos a estrutura da árvore de configuração, cujos ramos são os parâmetros que precisamos e as regras para seus valores. Por exemplo, o título é indicado como um parâmetro obrigatório de um tipo escalar, a descrição como um parâmetro opcional, que está vazio por padrão. Em rss , esperamos um valor booleano que é false
por padrão, e posts_main_page deve conter um valor inteiro no intervalo de 1 a 10, sendo 5 o padrão .
Bem, nós definimos a estrutura, agora vamos ao carregamento e processamento. Por condição, a fonte pode ser qualquer, para começar, precisamos convertê-la em uma matriz regular para verificar e processar todos os valores usando nossa estrutura de configuração. Para cada formato de origem, precisamos de uma classe separada; portanto, se vamos usar os formatos de arquivo YAML
e XML
, precisamos criar duas classes. O exemplo abaixo mostra por simplicidade apenas a classe de formato YAML
:
<?php namespace RaulFraile\Config; use Symfony\Component\Config\Loader\FileLoader; use Symfony\Component\Yaml\Yaml; class YamlConfigLoader extends FileLoader { public function load($resource, $type = null) { $configValues = Yaml::parse(file_get_contents($resource)); return $configValues; } public function supports($resource, $type = null) { return is_string($resource) && 'yml' === pathinfo( $resource, PATHINFO_EXTENSION ); } }
Como você pode ver, tudo é muito simples. O método YamlConfigLoader::supports
é usado pela classe LoaderResolver
para verificar a fonte de configuração. O método YamlConfigLoader::load
converte um arquivo YAML
em uma matriz de dados usando outro componente Symfony YAML .
Em conclusão, combinamos a estrutura de configuração e o carregador ao processar a fonte para obter os valores necessários:
<?php use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderResolver; use Symfony\Component\Config\Loader\DelegatingLoader; use Symfony\Component\Config\Definition\Processor; use RaulFraile\Config\YamlConfigLoader; use RaulFraile\Config\Configuration; include_once __DIR__. '/vendor/autoload.php';
Vamos analisar esse código. Primeiro, definimos uma matriz de diretórios onde os arquivos de configuração podem ser localizados e o colocamos como um parâmetro no objeto config.yml
que procura o arquivo config.yml
no diretório especificado. Em seguida, crie um objeto YamlConfigLoader
que retorna uma matriz com valores e ele já é processado por nossa estrutura de configuração.
Como resultado, obtemos a seguinte matriz:
array(5) { 'title' => string(7) "My blog" 'description' => string(24) "This is just a test blog" 'rss' => bool(true) 'posts_main_page' => int(2) 'social' => array(2) { 'twitter' => array(2) { 'url' => string(29) "http://twitter.com/raulfraile" 'icon' => string(11) "twitter.png" } 'sensiolabs_connect' => array(2) { 'url' => string(49) "https://connect.sensiolabs.com/profile/raulfraile" 'icon' => string(22) "sensiolabs_connect.png" } } }
Se tentarmos alterar o config.yml
excluindo os campos rss e post_main_page , obteremos os valores padrão:
array(5) { ... 'rss' => bool(false) 'posts_main_page' => int(5)
Armazenamento em cache
O processamento de grandes arquivos de configuração pode ser uma tarefa demorada. O componente descrito possui um mecanismo de armazenamento em cache simples, com base na verificação da data de alteração do arquivo de configuração.
Para habilitar o cache, algumas linhas de código são suficientes:
<?php use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Definition\Processor; use RaulFraile\Config\YamlConfigLoader; use RaulFraile\Config\Configuration; include_once __DIR__. '/vendor/autoload.php'; $cachePath = __DIR__.'/cache/config.php'; $configFile = 'config.yml';
Uma instância da classe ConfigCache
verifica a existência de um cache de arquivo e, se disponível, compara a data em que o arquivo de configuração foi modificado. Quando criamos um cache de arquivos, também salvamos uma lista de objetos usados para comparação posterior.
Carregamento múltiplo
Para adicionar outro formato de configuração, basta definir uma classe que será responsável por um formato específico. No exemplo abaixo, adicionamos suporte para a configuração XML
e um manipulador correspondente. A classe LoaderResolver
nos ajudará a combinar diferentes formatos em um pool comum, e a classe DelegatingLoader
carregará o arquivo necessário mediante solicitação.
<?php namespace RaulFraile\Config; use Symfony\Component\Config\Loader\FileLoader; class XmlConfigLoader extends FileLoader { public function load($resource, $type = null) {
$loaderResolver = new LoaderResolver(array( new YamlConfigLoader($locator), new XmlConfigLoader($locator) )); $delegatingLoader = new DelegatingLoader($loaderResolver); $configValues = $delegatingLoader->load($locator->locate('config.xml'));
Entre outras coisas, o componente tem a funcionalidade de gerar informações de referência para sua documentação.
<?php ... use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; $dumper = new YamlReferenceDumper(); echo $dumper->dump($configuration);
Ele produzirá:
blog: title: ~ # Required description: '' rss: false posts_main_page: 5 social: url: ~ icon: ~
Sumário
Talvez você descubra que tudo isso é muito complicado e confuso, e você pode fazer isso com algumas funções. Talvez, mas esse seja o "preço" para uma boa estrutura de POO. Por outro lado, esse componente oferece várias vantagens:
- ~ 80% de cobertura do componente com testes e suporte ativo.
- Adicionar novos formatos de configuração é realmente fácil. Basta definir um manipulador que converta os dados de origem em uma matriz regular. Uma estrutura semelhante será usada para outros formatos. A extensão de qualquer parte do componente é implementada adicionando a interface necessária.
- O armazenamento em cache funciona imediatamente, com configurações flexíveis para ambientes de desenvolvimento e produção.
- Verificação interna de parâmetros e seus valores.
- Geração de informações em segundo plano.