如何簡化多個 Bundles 的設定
在建構可重複使用且可擴展的應用程式時,開發者經常面臨一個選擇:建立單一大型 bundle 或多個較小的 bundle。建立單一 bundle 的缺點是用戶無法移除未使用的功能。建立多個 bundle 的缺點是設定變得更加繁瑣,並且設定經常需要在多個 bundle 中重複。
透過啟用單一 Extension 來預先設定任何 bundle 的設定,有可能消除多個 bundle 方法的缺點。它可以使用在 config/*
檔案中定義的設定來預先設定設定,就像它們是由使用者在應用程式設定中明確寫入一樣。
例如,這可以用於設定在多個 bundle 中使用的實體管理器名稱。或者它可以被用於啟用一個可選功能,該功能也取決於另一個 bundle 是否已載入。
為了給予 Extension 執行此操作的能力,它需要實作 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 的 Extensions 呼叫 load() 方法之前。為了將設定預先設定到 bundle extension,開發者可以使用 prependExtensionConfig() 方法在 ContainerBuilder 實例上。由於此方法僅預先設定設定,因此在 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 未註冊的情況下,將以下內容寫入 config/packages/acme_something.yaml
,並且 acme_hello
的 entity_manager_name
設定設定為 non_default
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 類別中預先設定 Extension
如果您從 AbstractBundle 類別擴展並定義 prependExtension() 方法,您也可以直接在您的 Bundle 類別中預先設定 extension 配置
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');
}
}
注意
與 prepend()
一樣,prependExtension()
方法僅在編譯時呼叫。
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 預先設定相同的 extension 並定義相同的鍵,則首先註冊的 bundle 將具有優先權:後續的 bundle 將不會覆蓋此特定配置設定。