如何简化多个 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_hello
的 entity_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 将不会覆盖此特定配置设置。