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

Дата оновлення перекладу 2022-12-11

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

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

Існує можливість видалити недолік підходу декількох пакетів, додавши одне розширення, яке додавало б на початку налаштувань будь-якого пакета деяку інформацію. Воно може використовувати налаштування, визначені у файлах 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)
    {
        // ...
    }
}

Всередині методу 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
38
39
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
public function prepend(ContainerBuilder $container)
{
    // отримати всі пакети
    $bundles = $container->getParameter('kernel.bundles');
    // визначити, чи зареєстровано AcmeGoodbyeBundle
    if (!isset($bundles['AcmeGoodbyeBundle'])) {
        // відключити AcmeGoodbyeBundle в пакетах
        $config = array('use_acme_goodbye' => false);
        foreach ($container->getExtensions() as $name => $extension) {
            switch ($name) {
                case 'acme_something':
                case 'acme_other':
                    // встановити use_acme_goodbye, як false у конфігурації
                    // acme_something та acme_other
                    //
                    // відмітьте, що якщо користувач вручну сконфігурував
                    // use_acme_goodbye, як true в config/services.yaml,
                    // то установка зрештою буде true, а не false
                    $container->prependExtensionConfig($name, $config);
                    break;
            }
        }
    }

    // отримати конфігурацію 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:

# config/packages/acme_something.yaml
acme_something:
# ... use_acme_goodbye: false entity_manager_name: non_default
acme_other:
# ... use_acme_goodbye: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- config/packages/acme_something.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:acme-something="http://example.org/schema/dic/acme_something"
    xmlns:acme-other="http://example.org/schema/dic/acme_other"
    xsi:schemaLocation="http://symfony.com/schema/dic/services
        https://symfony.com/schema/dic/services/services-1.0.xsd
        http://example.org/schema/dic/acme_something
        https://example.org/schema/dic/acme_something/acme_something-1.0.xsd
        http://example.org/schema/dic/acme_other
        https://example.org/schema/dic/acme_something/acme_other-1.0.xsd"
>
    <acme-something:config use-acme-goodbye="false">
        <!-- ... -->
        <acme-something:entity-manager-name>non_default</acme-something:entity-manager-name>
    </acme-something:config>

    <acme-other:config use-acme-goodbye="false">
        <!-- ... -->
    </acme-other:config>

</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// config/packages/acme_something.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return static function (ContainerConfigurator $container) {
    $container->extension('acme_something', [
        // ...
        'use_acme_goodbye' => false,
        'entity_manager_name' => 'non_default',
    ]);
    $container->extension('acme_other', [
        // ...
        'use_acme_goodbye' => false,
    ]);
};

Додавання розширення у класі пакета

6.1

Клас AbstractBundle було представлено в Symfony 6.1.

Ви також можете додавати до конфігурації розширення напряму у вашому класі Пакета, якщо ви розширюєте з класу 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 $container, ContainerBuilder $builder): void
    {
        // додати наприкінці
        $builder->prependExtensionConfig('framework', [
            'cache' => ['prefix_seed' => 'foo/bar'],
        ]);

        // додати на початку
        $container->extension('framework', [
            'cache' => ['prefix_seed' => 'foo/bar'],
        ]);

        // додати на початку з файлу
        $container->import('../config/packages/cache.php');
    }
}

Note

Метод prependExtension(), як і prepend(), викликається тільки під час компіляції.

Використання PrependExtensionInterface більше, ніж одним пакетом

Якщо існує більше одного пакета, який додає одне й те саме розширення, та визначає один і той самій ключ, то перший зареєстрований пакет отримає пріоритет: наступні пакети не будуть перевизначати це конкретне налаштування конфігурації.