跳到内容

如何简化多个 Bundle 的配置

编辑此页

在构建可重用和可扩展的应用程序时,开发者经常面临一个选择:创建一个大型 Bundle 还是多个较小的 Bundle。创建一个大型 Bundle 的缺点是用户无法移除未使用的功能。创建多个 Bundle 的缺点是配置变得更加繁琐,并且设置通常需要在多个 Bundle 中重复。

通过启用单个扩展来预先添加任何 Bundle 的设置,可以消除多 Bundle 方法的缺点。它可以使用在 config/* 文件中定义的设置来预先添加设置,就像用户在应用程序配置中显式编写它们一样。

例如,这可以用于配置在多个 Bundle 中使用的实体管理器名称。或者,它可以用于启用依赖于另一个 Bundle 也被加载的可选功能。

为了赋予扩展执行此操作的能力,它需要实现 PrependExtensionInterface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class AcmeHelloExtension extends Extension implements PrependExtensionInterface
{
    // ...

    public function prepend(ContainerBuilder $container): void
    {
        // ...
    }
}

prepend() 方法内部,开发者可以在 ContainerBuilder 实例上完全访问,就在每个已注册 bundle 扩展的 load() 方法被调用之前。为了向 bundle 扩展预先添加设置,开发者可以使用 ContainerBuilder 实例上的 prependExtensionConfig() 方法。由于此方法仅预先添加设置,因此在 config/* 文件中显式完成的任何其他设置都将覆盖这些预先添加的设置。

以下示例说明了如何在多个 Bundle 中预先添加配置设置,以及在未注册特定其他 Bundle 时如何在多个 Bundle 中禁用标志

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
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
public function prepend(ContainerBuilder $container): void
{
    // get all bundles
    $bundles = $container->getParameter('kernel.bundles');
    // determine if AcmeGoodbyeBundle is registered
    if (!isset($bundles['AcmeGoodbyeBundle'])) {
        // disable AcmeGoodbyeBundle in bundles
        $config = ['use_acme_goodbye' => false];
        foreach ($container->getExtensions() as $name => $extension) {
            match ($name) {
                // set use_acme_goodbye to false in the config of
                // acme_something and acme_other
                //
                // note that if the user manually configured
                // use_acme_goodbye to true in config/services.yaml
                // then the setting would in the end be true and not false
                'acme_something', 'acme_other' => $container->prependExtensionConfig($name, $config),
                default => null
            };
        }
    }

    // get the configuration of AcmeHelloExtension (it's a list of configuration)
    $configs = $container->getExtensionConfig($this->getAlias());

    // iterate in reverse to preserve the original order after prepending the config
    foreach (array_reverse($configs) as $config) {
        // check if entity_manager_name is set in the "acme_hello" configuration
        if (isset($config['entity_manager_name'])) {
            // prepend the acme_something settings with the entity_manager_name
            $container->prependExtensionConfig('acme_something', [
                'entity_manager_name' => $config['entity_manager_name'],
            ]);
        }
    }
}

如果 AcmeGoodbyeBundle 未注册,并且 acme_helloentity_manager_name 设置为 non_default,则以上内容等同于将以下内容写入 config/packages/acme_something.yaml

1
2
3
4
5
6
7
8
9
# config/packages/acme_something.yaml
acme_something:
    # ...
    use_acme_goodbye: false
    entity_manager_name: non_default

acme_other:
    # ...
    use_acme_goodbye: false

在 Bundle 类中预先添加扩展配置

如果你从 AbstractBundle 类扩展并定义 prependExtension() 方法,你也可以直接在你的 Bundle 类中预先添加扩展配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class FooBundle extends AbstractBundle
{
    public function prependExtension(ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void
    {
        // prepend
        $containerBuilder->prependExtensionConfig('framework', [
            'cache' => ['prefix_seed' => 'foo/bar'],
        ]);

        // prepend config from a file
        $containerConfigurator->import('../config/packages/cache.php');
    }
}

注意

prependExtension() 方法,就像 prepend() 一样,只在编译时被调用。

7.1

从 Symfony 7.1 开始,在 prependExtension() 内部调用 import() 方法将预先添加给定的配置。在之前的 Symfony 版本中,此方法附加配置。

或者,你可以使用 extension() 方法的 prepend 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class FooBundle extends AbstractBundle
{
    public function prependExtension(ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void
    {
        // ...

        $containerConfigurator->extension('framework', [
            'cache' => ['prefix_seed' => 'foo/bar'],
        ], prepend: true);

        // ...
    }
}

7.1

extension() 方法的 prepend 参数是在 Symfony 7.1 中添加的。

多个 Bundle 使用 PrependExtensionInterface

如果存在多个 Bundle 预先添加相同的扩展并定义相同的键,则首先注册的 Bundle 将优先:后续的 Bundle 将不会覆盖此特定配置设置。

这项工作,包括代码示例,均根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本