Symfony component overview: Config

In this article we will try to deal with the component called Config , which helps to load and process various data regardless of the source.


The following is a translation of the article Symfony2 components overview: Config . The original was published in 2014 and it indicates the second version of symfony, but the information is relevant for the latest, at the moment, fourth version.


Let's imagine that we want to create a blog generator that will take several parameters such as title ( title ), description ( description ), number of posts on the main page ( posts_main_page ), social network icons ( social ) and the presence or absence of RSS feeds ( rss ) . For these purposes, we describe the configuration file in YAML format:


blog: title: My blog description: This is just a test blog 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 

Now we will try to parse our file, check for the presence of required fields, set default values ​​if necessary. We check all received data for compliance with the established rules, for example, rss can only contain a boolean value, and the posts_main_page should contain an integer value in the range from 1 to 10. We will have to repeat these procedures each time the file is accessed, if, of course, the caching system is not used . In addition, a similar mechanism complicates the use of files of other formats like INI, XML or JSON.


To simplify the above actions, we will use the Config component , so you do not have to spend time writing most of the code. The component is simple, well tested and flexible enough for use in different projects.


Architecture


The architecture is divided into two main parts:


  1. Determination of the hierarchical structure of parameters.
    The component allows you to determine the format of the configuration source, which can be anything from a simple INI file to something more esoteric, such as a special protocol configuration message. Using the TreeBuilder class, we can define value types, make them mandatory / optional, and set a default value.
  2. Detection, loading and processing.
    After the source format is specified, it must be found, loaded and processed. Finally, the component will return a simple array with verified values ​​or throw an exception on error.

Example


Let's go back to our example. And so, we want to create a flexible blog generation system. To begin with, we will define a hierarchical structure (tree), and an instance of the TreeBuilder class, which provides a DSL syntax with a similar interface, will help us in this.


 <?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; } } 

Impressive ?! But don't worry too much, if you see a similar structure of PHP code for the first time, the DSL syntax in it always looks a bit strange. In the example above, we defined the root node of the blog and built from it the structure of the configuration tree, the branches of which are the parameters and rules we need for their values. For example, title is designated as a mandatory parameter of a scalar type, description as an optional parameter, which is empty by default, in rss we expect a boolean value, which defaults to false , and posts_main_page must contain an integer value in the range from 1 to 10, while 5 by default .


Ok, we have defined the structure, now let's do the loading and processing. By condition, the source can be any, for this we need to convert it into a regular array in order to further check and process all values ​​using our configuration structure. For each source format, we need a separate class, so if we are going to use the YAML and XML file formats, we need to create two classes. In the example below, for simplicity, only the YAML format class is presented:


 <?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 ); } } 

As you can see everything is very simple. The supports method is used by the LoaderResolver class to verify the configuration source. The load method converts a YAML file to an array of data using another Symfony component of YAML .


Finally, we combine the configuration structure and the loader while processing the source to obtain the necessary values:


 <?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; } 

Let's break this code down. First, we define an array of directories where configuration files can be located, and place it as a parameter to the FileLocator object that searches for the config.yml file in the specified directory. Then, we create a YamlConfigLoader object, which returns an array with values, and it is already processed by our configuration structure.


As a result, we get the following array:


 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" } } } 

If we try to change the config.yml by removing the rss and post_main_page fields, we get the default values:


 array(5) { ... 'rss' => bool(false) 'posts_main_page' => int(5) 

Caching


Processing large configuration files can be a resource-intensive task. The described component has a simple caching mechanism based on checking the date of the configuration file change.


A few lines of code are enough to enable the cache:


 <?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; } } 

An instance of the ConfigCache class checks the existence of a file cache and, if available, compares the date of the configuration file change. When we create a file cache, we also save a list of used objects for further matching.


Multiple loading


To add another configuration format, it is enough to define a class that will be responsible for a specific format. In the example below, we added XML configuration support and the corresponding handler. The LoaderResolver class will help us to combine different formats into a common pool, and the DelegatingLoader class will load the required file on demand.


 <?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')); 

Reference generation


Among other things, the component has the functionality to generate reference information for your documentation.


 <?php ... use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; $dumper = new YamlReferenceDumper(); echo $dumper->dump($configuration); 

It will be displayed:


 blog: title: ~ # Required description: '' rss: false posts_main_page: 5 social: url: ~ icon: ~ 

Total


You may think that all this is too complicated and confusing, and you would have managed a couple of functions. Perhaps, but such is the “price” for a good OOP structure. On the other hand, this component offers you several advantages:


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


All Articles