> Symfony中文手册 > 如何给Bundle创建友好的配置

如何给Bundle创建友好的配置

如果你打开你的应用程序配置文件(通常是app/config/config.yml),你将看到若干不同的配置块,比如frameworktwigdoctrine。这些东东中的每一个,配置的是一个特定的bundle,允许你从一个“较高层面”来定义选项,使得bundle基于你的这些设置,来做出所有“底层化”的、复杂的改变。

举例来说,下面的配置告诉FrameworkBundle去启用表单集成,它涉及到相当多的服务的定义以及其他相关组件的集成:

1
2
framework:
    form: true
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="Http://symfony.com/schema/dic/services"
    xmlns:framework="http://symfony.com/schema/dic/symfony"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony
        http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
    <framework:config>
        <framework:form />
    </framework:config>
</container>
1
2
3
$container->loadFromExtension('framework', array(
    'form' => true,
));

使用参数来配置您的Bundle

如果项目之间你没有计划去分享你的bundle,他就没有意义去使用这个更高级的配置方式。因为你使用的这个bundle只在一个项目中,你只需要每次改变服务配置即可。

如果你确实想在 config.yml 中设置一些东西,你可以在那里创建一个参数并且在其它地方使用这个参数。

使用Bundle扩展 ¶

基本的思想是,不让用户重写各个参数,而是让用户只配置少量的,需要特定创建的选项。作为Bundle开发人员,你以后会通过分析配置并在"Extension"类内部加载正确的服务和参数。

作为一个例子,假设你创建一个社会化bundle,提供Twitter集成等。为了能够使bundle重用,你不得不制作client_idclient_secret变量使其可配置。你的bundle配置看起来就像是这样:

1
2
3
4
5
# app/config/config.yml
acme_social:
    twitter:
        client_id: 123
        client_secret: your_secret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
 
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:acme-social="http://example.org/dic/schema/acme_social"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">
 
   <acme-social:config>
       <twitter client-id="123" client-secret="your_secret" />
   </acme-social:config>
 
   <!-- ... -->
</container>
1
2
3
4
5
// app/config/config.php
$container->loadFromExtension('acme_social', array(
    'client_id'     => 123,
    'client_secret' => 'your_secret',
));

在“如何在Bundle内加载服务配置”文章中有更多关于扩展的信息。

如果一个bundle提供一个扩展(Extension)类,那么你不应该从那个bundle中重写任何的服务容器参数。我们的想法是如果一个扩展类存在,每一个设置都应尽量可配置,这些设置应该存在在配置文件中,通过扩展类来让它们有效。换句话说,扩展(extension)类定义了所有这些公共配置的设置,这些设置将保持向后兼容性(译注:也就是说这些配置被用到你分离的bundle中,你避免你直接修改bundle)。

在依赖注入容器中处理参数的问题可以阅读在依赖注入类内部使用参数。

处理$configs数组 ¶

先说重要的,你要创建一个扩展(extension)类,就像如何在Bundle内加载服务配置所解释的一样。

每当用户在配置文件中包含 acme_social 键(这是一个 DI 别名)时,在它之下的配置就会添加到一个配置的数组中,并且会传到你的扩展(extension)类 的 load() 方法中(symfony将自动转化XML和YAML数据为数组)。

根据上面介绍的配置示例,传入到load()方法中的这些数组,看起来像这样:

1
2
3
4
5
6
7
8
array(
    array(
        'twitter' => array(
            'client_id' => 123,
            'client_secret' => 'your_secret',
        ),
    ),
)

请注意,这是一个数组的数组,而不是一个简单的扁平的配置值的数组。就这么设计的,因为它允许Symfony解析各自的配置资源。举例来说,如果acme_social 出现在其他的配置文件中,比如config_dev.yml,它们会有不同的值,里面的数组看起来应该是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
array(
    // values from config.yml
    array(
        'twitter' => array(
            'client_id' => 123,
            'client_secret' => 'your_secret',
        ),
    ),
    // values from config_dev.yml
    array(
        'twitter' => array(
            'client_id' => 456,
        ),
    ),
)

两个数组的顺序取决于哪一个首先被设置。

但别担心!Symfony的配置组件将帮助您合并这些值,并提供默认的值,并且对用户的错误的设置进行校验。在这里它是如何工作的。在DependencyInjection目录中创建一个Configuration类,并构建一个树来定义你bundle配置的结构。

Configuration 类处理示例配置如下:

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
// src/Acme/SocialBundle/DependencyInjection/Configuration.php
namespace Acme\SocialBundle\DependencyInjection;
 
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
 
class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('acme_social');
 
        $rootNode
            ->children()
                ->arrayNode('twitter')
                    ->children()
                        ->integerNode('client_id')->end()
                        ->scalarNode('client_secret')->end()
                    ->end()
                ->end() // twitter
            ->end()
        ;
 
        return $treeBuilder;
    }
}

Configuration类可能比这里更加复杂,它可以支持“prototype”节点,高级的验证,XML - 标准化以及高级合并。在 定义和处理配置的值 中你可以了解到更多信息。通过检查一些核心的配置类,你也可以在它们的代码中看到,比如FrameworkBundle Configuration和 TwigBundle Configuration。

现在可以在你的load()方法中使用这个类了,用它去合并配置并迫使其验证(举例,如果你添加了一个额外的选项,那么他就会抛出一个异常):

1
2
3
4
5
6
7
public function load(array $configs, ContainerBuilder $container)
{
    $configuration = new Configuration();
 
    $config = $this->processConfiguration($configuration, $configs);
    // ...
}

processConfiguration() 方法使用了你已经在 Configuration 类中定义的配置树,并验证这个配置树,使其规范化并将所有配置数组合并在一起。

你扩展(extension)中的processConfiguration()需要你每次都提供一些配置选项,要代替他,你可能想使用ConfigurableExtension自动为你完成这些事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
 
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
 
class AcmeHelloExtension extends ConfigurableExtension
{
    // note that this method is called loadInternal and not load
    protected function loadInternal(array $mergedConfig, ContainerBuilder $container)
    {
        // ...
    }
}

这个类使用getConfiguration()方法去获取配置(Configuration)实例。如果你的 Configuration 类不叫做 Configuration 或者它没有和你的扩展放在同一个命名空间内,那么你应当重写它。

你自己处理这个配置

使用的这个配置组件完全是可选的。这个load()方法会获得一个配置值数组。你自己可以直接的解析这些数组(如,可以重写配置并使用isset来检测值是否存在)。请注意,支持XML是很难的。

1
2
3
4
5
6
7
8
9
10
public function load(array $configs, ContainerBuilder $container)
{
    $config = array();
    // let resources override the previous set value
    foreach ($configs as $subConfig) {
        $config = array_merge($config, $subConfig);
    }
 
    // ... now use the flat $config array
}

修改另一个bundle的配置 ¶

如果你有多个相互依赖的bundle时,允许一个Extension类去修改“被传入到”其他bundle的Extension类的参数,是非常有用的,就好像最后的开发者真正把配置放置在他们的app/config/config.yml文件中一样。你可以使用一个前置的扩展(prepend extension)来完成。更多信息,请看 如何去简化多个Bundle的配置

Dump配置 ¶

config:dump-reference命令会在命令控制台中使用Yaml格式输出一个bundle的默认配置。

只要你的Bundle配置位于标准位置(YourBundle\DependencyInjection\Configuration)并且不需要构造函数,他就会自动工作。如果你想做一些不同的事情,你的Extension类一定要重写Extension::getConfiguration() 方法并返回一个你的Configuration实例。

支持XML ¶

Symfony允许人们提供三种不同格式的配置:Yaml, XML 和 PHP。Yaml 和 PHP使用相同的语法并且当使用 Config 组件时都是默认支持的。要支持XML就需要你做一些事情了。但,当与其他人分享你的Bundle时,建议您遵循这些步骤。

准备好XML配置树 ¶

默认的配置组件提供了一些方法,允许它去正确的处理XML配置。请看组件文档的“Normalization”。然而,你也可以做一些选择,这将会提升使用 XML 配置的体验。

选择一个XML命名空间 ¶

在XML中,XML命名空间用于确定哪些元素属于特定Bundle的配置。这个命名空间从 Extension::getNamespace()方法中返回。按照惯例,名称空间就是一个URL(它并不必须是一个有效的链接且不一定需要存在)。默认情况下,bundle的命名空间是http://example.org/dic/schema/DI_ALIAS,在这里DI_ALIAS 是扩展的DI别名。你可能想要将其改成更加专业的链接:

1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
 
// ...
class AcmeHelloExtension extends Extension
{
    // ...
 
    public function getNamespace()
    {
        return 'http://acme_company.com/schema/dic/hello';
    }
}

提供一个XML schema ¶

XML有一个非常有用的特性称为XML schema。它将允许你在 XML Schema Definition (一个 xsd 文件)中描述所有的可能的元素和属性以及它们的值。XSD文件被IDE使用,这是自动完成的,并且XSD文件也被 Config 组件用来验证元素。

为了使用schema,XML配置文件中必须提供一个xsi:schemaLocation属性指向一个特定的XML命名空间的XSD文件。这个位置总是从XML命名空间开始。接下来,XML命名空间 会被 Extension::getXsdValidationBasePath()方法返回的 经过XSD验证的基本路径(base path)所取代。然后,此命名空间会被路径的其余部分,从基本路径(base path)到文件本身,所跟随使用。

按照惯例,这个XSD文件保存在 Resources/config/schema,但你可以把它放置在任何地方。你应该返回这个路径为基本路径(base path):

1
2
3
4
5
6
7
8
9
10
11
12
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
 
// ...
class AcmeHelloExtension extends Extension
{
    // ...
 
    public function getXsdValidationBasePath()
    {
        return __DIR__.'/../Resources/config/schema';
    }
}

假设这个XSD文件被称为hello-1.0.xsd,schema的位置就是http://acme_company.com/schema/dic/hello/hello-1.0.xsd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
 
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:acme-hello="http://acme_company.com/schema/dic/hello"
    xsi:schemaLocation="http://acme_company.com/schema/dic/hello
        http://acme_company.com/schema/dic/hello/hello-1.0.xsd">
 
    <acme-hello:config>
        <!-- ... -->
    </acme-hello:config>
 
    <!-- ... -->
</container>