How to Simplify Configuration of Multiple Bundles

When building reusable and extensible applications, developers are often faced with a choice: either create a single large bundle or multiple smaller bundles. Creating a single bundle has the drawback that it's impossible for users to remove unused functionality. Creating multiple bundles has the drawback that configuration becomes more tedious and settings often need to be repeated for various bundles.

再利用可能で拡張可能なアプリケーションを構築する場合、開発者は多くの場合、単一の大きなバンドルを作成するか、複数の小さなバンドルを作成するかの選択に直面します。単一のバンドルを作成すると、ユーザーが未使用の機能を削除できないという欠点があります。複数のバンドルを作成すると、構成がより面倒になり、さまざまなバンドルに対して設定を繰り返す必要があるという欠点があります。

It is possible to remove the disadvantage of the multiple bundle approach by enabling a single Extension to prepend the settings for any bundle. It can use the settings defined in the config/* files to prepend settings just as if they had been written explicitly by the user in the application configuration.

単一の拡張機能を有効にして任意のバンドルの設定を先頭に追加することにより、複数のバンドル アプローチの欠点を取り除くことができます。 config/* ファイルで定義された設定を使用して、アプリケーション構成でユーザーが明示的に記述したかのように、設定を先頭に追加できます。

For example, this could be used to configure the entity manager name to use in multiple bundles. Or it can be used to enable an optional feature that depends on another bundle being loaded as well.

たとえば、これを使用して、複数のバンドルを使用するエンティティ マネージャー名を構成できます。または、ロードされている別のバンドルにも依存するオプション機能を有効にするために使用できます。

To give an Extension the power to do this, it needs to implement PrependExtensionInterface:

拡張機能にこれを行う権限を与えるには、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)
    {
        // ...
    }
}

Inside the prepend() method, developers have full access to the ContainerBuilder instance just before the load() method is called on each of the registered bundle Extensions. In order to prepend settings to a bundle extension developers can use the prependExtensionConfig() method on the ContainerBuilder instance. As this method only prepends settings, any other settings done explicitly inside the config/* files would override these prepended settings.

prepend() メソッド内では、開発者は、登録された各バンドル拡張で load() メソッドが呼び出される直前に、ContainerBuilder インスタンスに完全にアクセスできます。バンドル拡張機能の前に設定を追加するために、開発者は ContainerBuilder インスタンスで prependExtensionConfig() メソッドを使用できます。このメソッドは設定を先頭に追加するだけなので、config/* ファイル内で明示的に行われた他の設定は、これらの先頭に追加された設定を上書きします。

The following example illustrates how to prepend a configuration setting in multiple bundles as well as disable a flag in multiple bundles in case a specific other bundle is not registered:

次の例は、特定の他のバンドルが登録されていない場合に、複数のバンドルで構成設定を前に追加し、複数のバンドルでフラグを無効にする方法を示しています。
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)
{
    // 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'],
            ]);
        }
    }
}

The above would be the equivalent of writing the following into the config/packages/acme_something.yaml in case AcmeGoodbyeBundle is not registered and the entity_manager_name setting for acme_hello is set to non_default:

上記は、AcmeGoodbyeBundle が登録されておらず、acme_hello の entity_manager_name 設定が non_default に設定されている場合、config/packages/acme_something.yaml に次を書き込むことと同じです。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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

Prepending Extension in the Bundle Class

6.1

6.1

The AbstractBundle class was introduced in Symfony 6.1.

AbstractBundle クラスは Symfony 6.1 で導入されました。

You can also append or prepend extension configuration directly in your Bundle class if you extend from the AbstractBundle class and define the prependExtension() method:

AbstractBundle クラスから拡張して prependExtension() メソッドを定義する場合は、拡張構成を yourBundle クラスに直接追加または先頭に追加することもできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 $container, ContainerBuilder $builder): void
    {
        // prepend
        $builder->prependExtensionConfig('framework', [
            'cache' => ['prefix_seed' => 'foo/bar'],
        ]);

        // append
        $container->extension('framework', [
            'cache' => ['prefix_seed' => 'foo/bar'],
        ]);

        // append from file
        $container->import('../config/packages/cache.php');
    }
}

Note

ノート

The prependExtension() method, like prepend(), is called only at compile time.

prependExtension() メソッドは、prepend() と同様に、コンパイル時にのみ呼び出されます。

More than one Bundle using PrependExtensionInterface

If there is more than one bundle that prepends the same extension and defines the same key, the bundle that is registered first will take priority: next bundles won't override this specific config setting.

同じ拡張子を先頭に追加し、同じキーを定義するバンドルが複数ある場合は、最初に登録されたバンドルが優先されます。次のバンドルは、この特定の構成設定を上書きしません。