支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
从全部类型的资源中加载配置值之后,这些值和它们的结构,可以通过Config组件的“Definition”定义部分,进行验证。配置值,通常被预期来显示某些类型的架构(hierarchy)。同时,值应当有其类型,被限制为数字,或是成为给定“值组合”(set of values)中的一员。例如,下列配置(以YAML格式)显示了一个清楚的架构,而一些验证规则应当被应用到其中(像是“ auto_connect
必须是布尔值”):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
在加载多个配置文件时,应当允许合并和覆写一些值。其他的值则不应被合并且维持原样。再有,一些键仅当另一个键是一个特定值时才能使用。(在上面例程配置中: memory
键仅在 driver
是 sqlite
时才有意义)
关乎配置值的所有规则,都可以通过 TreeBuilder
来定义。
一个 TreeBuilder
实例应该返回一个自定义的 Configuration
类,该类实现的是 ConfigurationInterface
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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');
// ... add node definitions to the root of the tree
// ... 添加树根处的节点定义
return $treeBuilder;
}
} |
树状结构(tree),包含了“可以通过一种语义化方式展开”的节点定义。这意味着,使用缩进和箭头符号,是可以映射出配置值的真实结构的:
根节点自身,是一个数组节点,并且有子项,就像布尔值节点 auto_connect
和标量节点 default_connection
。大体上:在定义一个节点之后,调用 end()
即可令你在层级架构中前进一步。
通过使用合适的节点定义,就有可能验证提交的配置值类型。可用的节点类型有:
null
)它们由 node($name, $type)
创建,或者通过它们各自关联的 xxxxNode($name)
快捷方法(来创建)。
数字节点 (浮点和整型) 提供了两个额外约束 - min()
和 max()
- 用来验证配置值:
枚举节点提供了一个“针对一组值来匹配给定的输入值”的约束:
这将限定 gender
选项为 male
或 female
之一。
使用数组节点,令添加深层架构成为可能。数组节点本身,可以是一组预定义的(pre-defined)变量节点:
或者你也可以对数组节点中的每一个节点定义一个原型(prototype):
所谓原型,可以被用来在当前节点中“多次重复地添加一个定义”。根据上例中的原型定义,程序可以拥有多个连接数组(各自包括 driver
, host
等等)。
在定义一个数组节点的子节点之前,你可以使用如下选项:
useAttributeAsKey()
requiresAtLeastOneElement()
isRequired()
同时被调用时)。addDefaultsIfNotSet()
normalizeKeys(false)
false
),键中的中杠将 不 被normalized为下划线。当用户定义了一个“键-值”映射关系时,为了避免不必要的转换,推荐在原型节点中使用之。基本的prototype数组配置可以按下例进行:
当使用下列YAML配置信息时:
1 | drivers: ['mysql', 'sqlite'] |
或使用下列XML配置信息时:
1 2 | <driver>mysql</driver>
<driver>sqlite</driver> |
处理后的配置为:
1 2 3 4 | Array(
[0] => 'mysql'
[1] => 'sqlite'
) |
更加复杂的例子应该是在ptototyped数组节点中使用子节点:
当使用下列YAML配置信息时:
1 2 3 | connections:
- { table: symfony, user: root, password: ~ }
- { table: foo, user: root, password: pa$$ } |
或使用下列XML配置信息时:
1 2 | <connection table="symfony" user="root" password="null" />
<connection table="foo" user="root" password="pa$$" /> |
处理后的配置为:
上面的输出,匹配了预想中的结果。然而,给定一个配置树,当使用下列YAML配置信息时:
1 2 3 4 5 6 7 8 9 | connections:
sf_connection:
table: symfony
user: root
password: ~
default:
table: foo
user: root
password: pa$$ |
输出的配置将和之前一模一样。换言之, sf_connection
和 default
配置键丢失了。原因是Symfony的Config组件默认把数组当作清单(list)来对待。
Note
截止到本文写作为止,仍有一个前后矛盾的地方:如果只有一个文件提供考虑中的配置信息,键(如 sf_connection
和 default
)并不 会丢失。一旦当多个文件提供配置信息时,键就会像上面提到的那样丢失了。
为了维护数组的键,可使用 useAttributedAsKey()
方法:
这个方法的参数(上例中的 name
)定义了添加到每一个XML节点中的属性名,以便区分它们。现在你可以使用与之前相同的YAML配置或是使用下列XML配置了:
1 2 3 4 | <connection name="sf_connection"
table="symfony" user="root" password="null" />
<connection name="default"
table="foo" user="root" password="pa$$" /> |
两种情况下,处理过的结果都包含 sf_connection
和 default
键:
对于所有节点类型,当节点是一个特定值的时,定义默认值和取代值都是可能的:
defaultValue()
isRequired()
cannotBeEmpty()
default*()
null
, true
, false
), 这是 defaultValue()
的快捷方式treat*Like()
null
, true
, false
), 在值是 *.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | $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()
; |
所有选项可以通过 info()
方法进行文档化。
当使用 config:dump-reference
命令来剥离配置树时,上述信息将作为文档进行输出。
在YAML中你会有:
1 2 | # This value is only used for the search results page.
entries_per_page: 25 |
在XML中是:
1 2 | <!-- entries-per-page: This value is only used for the search results page. -->
<config entries-per-page="25" /> |
如果你有整段可选配置并且可以被开启/关闭,你可以利用 canBeEnabled()
和 canBeDisabled()
快捷方法:
canBeDisabled
方法基本一样,除了配置区块默认是开启的。
可以提供关乎“合并进程”的附加选项,对于数组是:
performNoDeepMerging()
对于全部节点:
cannotBeOverwritten()
如果你有用于验证的复杂配置,树状结构会极其巨大,你可能希望把它们分成不同区块。通过令一个区块成为一个独立节点,然后用 append()
把它附加在主力树状之中,即可实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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;
} |
如果你位于不同位置的配置区块包含重复的话,这招非常有用,可以帮你避免重复。
上面代码导致如下结果:
1 2 3 4 5 6 7 8 9 10 11 12 | database:
connection:
driver: ~ # Required / 必须
host: localhost
username: ~
password: ~
memory: false
parameters: # Required / 必须
# Prototype / 原型
name:
value: ~ # Required / 必须 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <database>
<!-- driver: Required / 必须 -->
<connection
driver=""
host="localhost"
username=""
password=""
memory="false"
>
<!-- prototype / 原型 -->
<!-- value: Required / 必须 -->
<parameters
name="parameters name"
value=""
/>
</connection>
</database> |
当配置文件被处理时,它们首先被normalize(标准化),然后被合并为最终的树状结构,以用于验证结果数组。标准化的进程,是用于消除某些“由不同配置类型,特别是XML和YAML之间的不同”所带来的差异。
在YAML中,典型的分隔符是 typically _
,而XML中却是 -
。例如,YAML中的 auto_connect
和XML中的 auto-connect
。标准化将把这些统一为 auto_connect
。
Caution
目标键将不会被修改,如果是类似 foo-bar_moo
这种混合型的话,又或修改后的键已经存在时。
YAML和XML之间的另一个不同点是,值数组被呈现的方式。在YAML中你可能会有:
1 2 | twig:
extensions: ['twig.extension.foo', 'twig.extension.bar'] |
然后XML中是:
1 2 3 4 | <twig:config>
<twig:extension>twig.extension.foo</twig:extension>
<twig:extension>twig.extension.bar</twig:extension>
</twig:config> |
通过对XML中使用的键进行复数化(pluralizing),这种差异在标准化过程中会被消除。你可以配合 fixXmlConfig()
以这种方式来指定“希望变成复数的键”:
如果是一个不规则的复数,你可以在第二个参数中指定要使用的复数形式:
除了修复这个, fixXmlConfig
也确保了单一的XML元素仍被转换成数组。所以你可能会有:
1 2 | <connection>default</connection>
<connection>extra</connection> |
但有时就只是:
1 | <connection>default</connection> |
默认时, connection
在第一个例子中将是一个数组,而在第二例中是一个字符串,这令它难于验证。使用 fixXmlConfig()
你可以确保它始终是个数组。
如果需要,你可以进一步控制标准化的过程。例如,你可能希望允许一个字符串类型作用于一个特定的键,或者,若干个键被显式地设置(而用于那个特定的键)。以便,在以下配置中, name
之外的任何一部分是可选的:
1 2 3 4 5 6 | connection:
name: my_mysql_connection
host: localhost
driver: mysql
username: user
password: pass |
你可以允许这样配置:
1 | connection: my_mysql_connection |
通过把一个字符串值,转化为一个以 name
为键名的关联数组:
更加高端的验证规则(validation rule),可以由 ExprBuilder
来提供。这个builder针对广为人知的控制结构,实现了一个流畅的接口。builder被用于对节点定义(node definition)添加高端验证规则:
一个验证规则始终由一个“if”部分开头。你可以指定以下几种方法:
ifTrue()
ifString()
ifNull()
ifArray()
ifInArray()
ifNotInArray()
always()
同时,一个验证规则需要一个“then”部分:
then()
thenEmptyArray()
thenInvalid()
thenUnset()
通常,“then”是一个closure(闭包)。它返回的值将是作为节点的新值来使用,而不是节点原来的值。
Processor
会使用树状结构,因为它被构造时用到了 TreeBuilder
来处理多个配置值的数组。任何一个值没有按照预期类型提供,必须提供而未提供或者未定义,或者未通过某种方式的验证,都会抛出一个异常。反之,处理结果就是一个整齐的配置值数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Config\Definition\Processor;
use Acme\DatabaseConfiguration;
$config1 = Yaml::parse(
file_get_contents(__DIR__.'/src/Matthias/config/config.yml')
);
$config2 = Yaml::parse(
file_get_contents(__DIR__.'/src/Matthias/config/config_extra.yml')
);
$configs = array($config1, $config2);
$processor = new Processor();
$configuration = new DatabaseConfiguration();
$processedConfiguration = $processor->processConfiguration(
$configuration,
$configs
); |
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。