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

Дата обновления перевода 2023-01-20

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

Сервисы, созданные пакетами, не определяются в основном файле config/services.yaml, используемом приложением, а определяются в самих пакетах. Эта статья объясняет, как создавать и загружать такие файлы сервисов пакета.

Создание класса расширения

Для того, чтобы загружать конфигурацию сервисов, вам нужно создать Расширение внедрения зависимости (ВЗ) для вашего пакета. По умолчанию, класс Расширения должен следовать таким соглашениям (но позже вы узнаете, как их пропускать, если это необходимо):

  • Он должен жить в пространстве имён DependencyInjection пакета;
  • Он должен реализовывать ExtensionInterface, что обычно достигается путём расширения класса Extension ;
  • Имя равняется имени пакета с суффиксом Bundle, заменённым на Extension (например класс расширения AcmeBundle будет называться AcmeExtension, а AcmeHelloBundle - AcmeHelloExtension).

Вот как должно выглядеть расширение AcmeHelloBundle:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;

class AcmeHelloExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        // ... позднее вы будете загружать здесь файлы
    }
}

Ручная регистрация класса расширения

Если не следовать соглашениям, вам нужно будет вручную регистрировать ваше расширение. Чтобы сделать это, вам нужно переопределить метод Bundle::getContainerExtension(), чтобы он возвращал экземпляр расширения:

1
2
3
4
5
6
7
8
9
10
// ...
use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass;

class AcmeHelloBundle extends Bundle
{
    public function getContainerExtension()
    {
        return new UnconventionalExtensionClass();
    }
}

Кроме того, если новое имя класса Расширения не следует соглашениям об именовании, вам также нужно переопределить метод Extension::getAlias(), чтобы он возвращал правильный псевдоним ВЗ. Псевдоним ВЗ - это имя, используемое для ссылки на пакет в контейнере (например, в файлах config/packages/). По умолчанию, это делается путём удаления суффикса Extension и преобразованя имени класса на нижние подчёркивания (например, псевдоним ВЗ AcmeHelloExtension - это acme_hello).

Использование метода load()

В методе load(), все сервисы и параметры относятся к загружаемому расширению. Этот метод получает не настоящий экземпляр контейнера, а копию. Контейнер имеет только параметры из настоящего контейнера. После загрузки сервисов и параметром, копия будет соединена с настоящим контейнером, чтобы убедитьс, что все сервисы и параметры также добавлены в него.

В методе load(), вы можете использовать PHP-код для регистрации определений сервиса, но чаще вы будете помещать эти определения в файл конфигурации (используя форматы YAML, XML или PHP).

Например, предположим, что у вас есть файл по имени services.xml в каталоге Resources/config/ вашего пакета, ваш метод load() выглядит так:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

// ...
public function load(array $configs, ContainerBuilder $container)
{
    $loader = new XmlFileLoader(
        $container,
        new FileLocator(__DIR__.'/../../config')
    );
    $loader->load('services.xml');
}

Другие доступные загрузчики - YamlFileLoader и PhpFileLoader.

Использование конфигурации для изменения сервисов

Расширение также является классом, которое занимается конфигурацией для этого конкретного пакета (например, конфигурация в config/packages/<bundle_alias>.yaml). Чтобы прочитать больше об этом, см. статью "Как создать дружественную конфигурацию для пакета" article.

Загрузка сервисов прямо в вашем классе пакета

6.1

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

Как вариант, вы можете определять и загружать конфигурацию сервисов прямо в классе пакета, вместо создания специального класса Extension. Вы можете сделать это путём расширения из AbstractBundle и определения метода loadExtension():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ...
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;

class AcmeHelloBundle extends AbstractBundle
{
    public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
    {
        // загрузить файл XML, PHP или Yaml
        $container->import('../config/services.xml');

        // вы также можете добавить или заменить параметры и сервисы
        $container->parameters()
            ->set('acme_hello.phrase', $config['phrase'])
        ;

        if ($config['scream']) {
            $container->services()
                ->get('acme_hello.printer')
                    ->class(ScreamingPrinter::class)
            ;
        }
    }
}

Этот метод работает схоже с методом Extension::load(), но использует новый API для определения и импорта конфиуграции сервиса.

Note

В отличие от параметра $configs в Extension::load(), параметр $config уже прошёл слияние и обработку AbstractBundle.

Note

loadExtension() вызывается только во время компиляции.

Добавление классов для компиляции

Пакеты могут намекать Symfony о том, какие из их классов содержат аннотации, чтобы они были скомпилированы при генерировании кеша приложения для улучшения общей производительности. Определите список классов с аннотациями для компиляции в методе addAnnotatedClassesToCompile():

1
2
3
4
5
6
7
8
9
10
11
12
13
public function load(array $configs, ContainerBuilder $container)
{
    // ...

    $this->addAnnotatedClassesToCompile([
        // вы можете определить полностью квалифицированные имена класса...
        'App\\Controller\\DefaultController',
        // ... но глобальные паттерны также поддерживаются:
        '**Bundle\\Controller\\',

        // ...
    ]);
}

Note

Если какой-то класс расширяется из других классов, все его родители автоматически включаются в список классов для компиляции.

Схемы трансформируются в настоящие пространства имён классов, используя карту классов, сегенерированную Composer. Следовательно, до использования этих схем, вы должны сгенерировать полную карту классов, выполняющую команду Composer dump-autoload.

Caution

Эта техника не может быть использована, когда классы для компиляции используют константы __DIR__ или __FILE__, так как их значения будут изменяться при загрузке этих классов из файла classes.php.