Как упростить конфигурацию нескольких пакетов

Дата обновления перевода 2024-06-24

Как упростить конфигурацию нескольких пакетов

При построении повтроно используемых и расширяемых приложений, разработчики часто сталкиваются с выбором: либо создать один большой пакет, либо несколько меньших пакетов. Создание одного пакета имеет недостаток в виде невозможности выбора пользователями удаления неиспользуемой функциональности. Создание нескольких пакетов имеет недостаток в виде утомительно длинной конфигурации и необходимости повторения настроек для разных пакетов.

Существует возможность удалить недостаток подхода нескольких пакетов, включив одно расширение, которое добавляло бы вначале настроек любого пакета некоторую информацию. Оно может использовать настройки, определённые в файлах config/*, чтобы добавлять к настройкам информацию так же, как если бы они были ясно написаны пользователем в конфигурации приложения.

Например, это можеть быть использовано для конфигурации имени менеджера сущностей для использования в нескольких пакетах. Или для включения необязательной функции, зависящей от другого загружаемого пакета.

Для того, чтобы дать расширению возможность делать это, оно должно реализовывать 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 прямо перед вызовом метода load() в каждом из зарегистрированных расширений пакета. Для того, чтобы добавить вначале настройки к пакету, разработчики расширений могут использовать метод prependExtensionConfig() в экземпляре ContainerBuilder. Так как этот метод добавляет только настройки,любые другие настройки, ясно указанные в файлах config/*, будут переопределять их.

Следующий пример демонстрирует, как добавить вначале настройки конфигурации в нескольких пакетах, а также как отключить флажок в нескольких пакетах, на случай если не зарегистрирован другой конкретный пакет:

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
{
    // получить все пакеты
    $bundles = $container->getParameter('kernel.bundles');
    // определить, зарегистрирован ли AcmeGoodbyeBundle
    if (!isset($bundles['AcmeGoodbyeBundle'])) {
        // отключить AcmeGoodbyeBundle в пакетах
        $config = ['use_acme_goodbye' => false];
        foreach ($container->getExtensions() as $name => $extension) {
            match ($name) {
                    // установить use_acme_goodbye, как false в конфигурации
                    // acme_something и acme_other
                    //
                    // заметьте, что если пользователь вручную сконфигурировал
                    // use_acme_goodbye, как true в config/services.yaml,
                    // то установка в итоге будет true, а не false
                'acme_something', 'acme_other' => $container->prependExtensionConfig($name, $config),
                default => null
            };
        }
    }

    // получить конфигурацию AcmeHelloExtension (это список конфигураций)
    $configs = $container->getExtensionConfig($this->getAlias());

    // терировать в обратном порядке, чтобы сохранить изначальный порядок после добавления к конфиуграции
    foreach (array_reverse($configs) as $config) {
        // проверить, установлено ли entity_manager_name в конфигурации "acme_hello"
        if (isset($config['entity_manager_name'])) {
            // добавить к настройкам acme_something entity_manager_name
            $container->prependExtensionConfig('acme_something', [
                'entity_manager_name' => $config['entity_manager_name'],
            ]);
        }
    }
}

Вышенаписанное будет эквивалентно написанию следующего в config/packages/acme_something.yaml в случае, если AcmeGoodbyeBundle не зарегистрирован, а настройка entity_manager_name для acme_hello установлена, как 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

Добавление расширения к началку класса пакета

Вы такоже можете добавлять добавлять к конфигурации расширения прямо в классе Пакета, если расширяете из класса AbstractBundle и определяете метод prependExtension():

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 $containerConfigurator, ContainerBuilder $containerBuilder): void
    {
        // добавить к началу
        $containerBuilder->prependExtensionConfig('framework', [
            'cache' => ['prefix_seed' => 'foo/bar'],
        ]);

        // добавить
        $containerConfigurator->extension('framework', [
            'cache' => ['prefix_seed' => 'foo/bar'],
        ]);

        // добавить из файла
        $containerConfigurator->import('../config/packages/cache.php');
    }
}

Note

Метод prependExtension(), как и prepend(), вызывается только во время компиляции.

В качестве альтернативы можно использовать параметр prepend метода 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
    {
        // ...

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

        // ...
    }
}

7.1

Параметр prepend метода extension() был представлен в Symfony 7.1.

Больше о пакетах и использованием PrependExtensionInterface

Если существует более одного пакета, который добавляет одно и то же расширение, и определяет один и тот же ключ, то первый зарегистрированный пакет получит приоритет: последующие пакеты не будут пеоепределять эту конкретную настройку конфигурации.