在本文中,我们将尝试处理一个名为Config的组件,该组件有助于加载和处理各种数据,无论其来源如何。
以下是文章Symfony2组件概述的翻译:Config 。 原始版本于2014年发布,其中指出了Symfony的第二版,但此信息与当前最新的第四版有关。
想象一下,我们想创建一个博客生成器,该生成器将采用几个参数,例如标题( title ),描述( description ),主页上的帖子数( posts_main_page ),社交媒体图标( social )以及是否存在RSS
feed( rss )。 。 为此,我们将以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
现在,我们将尝试解析我们的文件,检查必填字段的可用性,并在必要时设置默认值。 我们将检查所有获得的数据是否符合既定规则,例如, rss只能包含一个布尔值,并且posts_main_page应当包含一个介于1到10之间的整数值。每次访问该文件时,我们都必须重复这些过程,除非使用了缓存系统。 另外,这种机制使其他格式的文件(例如INI
, XML
或JSON
的使用变得复杂。
为了简化上述操作,我们将使用Config组件 。 该组件简单,经过良好测试且足够灵活,可用于不同的项目。
建筑学
我们将项目分为两个主要部分:
- 定义参数的层次结构。
该组件使您可以确定配置源的格式,可以是从简单的INI
文件到更奇特的任何内容。 TreeBuilder类将帮助确定参数的类型,使它们成为必需/可选并设置默认值。 - 检测,加载和处理。
指定源格式后,必须找到,加载和处理它。 最后,该组件将返回一个带有检查值的简单数组,或者在错误时引发异常。
例子
让我们回到我们的例子。 因此,我们想创建一个灵活的博客生成系统。 首先,我们定义一个层次结构(树), TreeBuilder类的实例将为我们提供帮助,它提供了类似于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; } }
不用担心,如果您是第一次看到类似的PHP代码结构,则其中的DSL
语法总是有些奇怪。 在上面的示例中,我们确定了博客的根节点,并从中构建了配置树的结构,其分支是我们需要的参数及其值的规则。 例如, title表示为标量类型的必需参数, description表示为可选参数,默认情况下为空,在rss中,我们期望布尔值默认为false
,并且posts_main_page的整数值应介于1到10之间,默认值为5 。
好了,我们定义了结构,现在让我们开始进行加载和处理。 根据条件,源可以是任何源,首先,我们需要将其转换为常规数组,以便使用我们的配置结构进一步检查和处理所有值。 对于每种源格式,我们需要一个单独的类,因此,如果要使用YAML
和XML
文件格式,则需要创建两个类。 在下面的示例中,为简单起见,仅显示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 ); } }
如您所见,一切都很简单。 LoaderResolver
类使用YamlConfigLoader::supports
方法来验证配置源。 YamlConfigLoader::load
方法使用另一个Symfony YAML组件将YAML
文件转换为数据数组。
总之,在处理源以获得必要的值时,我们将配置结构和加载器结合在一起:
<?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';
让我们分析一下这段代码。 首先,我们定义可以在其中放置配置文件的目录数组,并将其作为参数放置在FileLocator
对象中,该对象在指定目录中查找config.yml
文件。 然后,创建一个YamlConfigLoader
对象,该对象返回带有值的数组,并且该数组已经由我们的配置结构处理。
结果,我们得到以下数组:
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" } } }
如果我们尝试config.yml
删除rss和post_main_page字段来更改config.yml
,则将获得默认值:
array(5) { ... 'rss' => bool(false) 'posts_main_page' => int(5)
快取
处理大型配置文件可能是一项耗时的任务。 所描述的组件具有一个简单的缓存机制,该机制基于检查配置文件的更改日期。
要启用缓存,只需几行代码即可:
<?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';
ConfigCache
类的实例检查文件缓存是否存在,如果可用,则比较修改配置文件的日期。 创建文件缓存时,我们还会保存一个已用对象列表,以供进一步比较。
多次加载
要添加另一种配置格式,只需定义一个负责特定格式的类即可。 在下面的示例中,我们添加了对XML
配置的支持和相应的处理程序。 LoaderResolver
类将帮助我们将不同的格式组合到一个公共池中,而DelegatingLoader
类将根据请求加载所需的文件。
<?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'));
除其他外,该组件具有为您的文档生成参考信息的功能。
<?php ... use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; $dumper = new YamlReferenceDumper(); echo $dumper->dump($configuration);
它将输出:
blog: title: ~ # Required description: '' rss: false posts_main_page: 5 social: url: ~ icon: ~
总结
也许您会发现所有这些都太复杂和令人困惑,并且可以使用几个函数来完成。 也许,但这就是一个好的OOP结构的“价格”。 另一方面,此组件具有多个优点:
- 通过测试和积极支持,组件的覆盖率达到80%。
- 添加新的配置格式非常容易。 定义一个将源数据转换为常规数组的处理程序就足够了。 其他格式也将使用类似的结构。 组件任何部分的扩展都通过添加必要的接口来实现。
- 缓存具有针对
dev
环境和prod
环境的灵活设置,开箱即用。 - 内置验证参数及其值。
- 背景信息生成。