Visão geral do componente Symfony: Config

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:


  1. 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.
  2. 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'; //    $directories = array(__DIR__.'/config'); $locator = new FileLocator($directories); //       $loader = new YamlConfigLoader($locator); $configValues = $loader->load($locator->locate('config.yml')); //   $processor = new Processor(); $configuration = new Configuration(); try { $processedConfiguration = $processor->processConfiguration( $configuration, $configValues ); //   var_dump($processedConfiguration); } catch (Exception $e) { echo $e->getMessage() . PHP_EOL; } 

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'; //    /    $cache = new ConfigCache($cachePath, true); if (!$cache->isFresh()) { $directories = array(__DIR__.'/config'); $locator = new FileLocator($directories); $loader = new YamlConfigLoader($locator); $configFilePath = $locator->locate($configFile); $configValues = $loader->load($configFilePath); $resource = new FileResource($configFilePath); $processor = new Processor(); $configuration = new Configuration(); try { $processedConfiguration = $processor->processConfiguration( $configuration, $configValues ); //     $cache->write(serialize($processedConfiguration), array($resource)); } catch (Exception $e) { echo $e->getMessage() . PHP_EOL; } } 

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) { //  xml return $configValues; } public function supports($resource, $type = null) { return is_string($resource) && 'xml' === pathinfo( $resource, PATHINFO_EXTENSION ); } } 

 $loaderResolver = new LoaderResolver(array( new YamlConfigLoader($locator), new XmlConfigLoader($locator) )); $delegatingLoader = new DelegatingLoader($loaderResolver); $configValues = $delegatingLoader->load($locator->locate('config.xml')); 

Geração de informações em segundo plano


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.

Source: https://habr.com/ru/post/pt413413/


All Articles