Как управлять общими зависимостями с родительскими сервисами

По мере добавление функционала в ваше приложение, вы также можете заиметь родственные классы, которые имеют некоторые общие зависимости. Например, вы можете иметь несколько классов хранилища, которым нужен сервис doctrine.entity_manager и необязательный сервис``logger``:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// src/AppBundle/Repository/BaseDoctrineRepository.php
namespace AppBundle\Repository;

// ...
abstract class BaseDoctrineRepository
{
    protected $entityManager;
    protected $logger;

    public function __construct(EntityManagerInterface $manager)
    {
        $this->entityManager = $manager;
    }

    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    // ...
}

Так же, как вы используете наследование PHP, чтобы избежать дубликатов в вашем PHP-коде, сервис-контейнер позволяет вам расширить родительские сервисы, чтобы избежать дубликатов определений сервиса:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    services:
        AppBundle\Repository\BaseDoctrineRepository:
            abstract:  true
            arguments: ['@doctrine.entity_manager']
            calls:
                - [setLogger, ['@logger']]
    
        AppBundle\Repository\DoctrineUserRepository:
            # расширить сервис AppBundle\Repository\BaseDoctrineRepository
            parent: AppBundle\Repository\BaseDoctrineRepository
    
        AppBundle\Repository\DoctrinePostRepository:
            parent: AppBundle\Repository\BaseDoctrineRepository
    
        # ...
    
  • XML
     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
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="AppBundle\Repository\BaseDoctrineRepository" abstract="true">
                <argument type="service" id="doctrine.entity_manager" />
    
                <call method="setLogger">
                    <argument type="service" id="logger" />
                </call>
            </service>
    
            <!-- extends the AppBundle\Repository\BaseDoctrineRepository service -->
            <service id="AppBundle\Repository\DoctrineUserRepository"
                parent="AppBundle\Repository\BaseDoctrineRepository"
            />
    
            <service id="AppBundle\Repository\DoctrinePostRepository"
                parent="AppBundle\Repository\BaseDoctrineRepository"
            />
    
            <!-- ... -->
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    use AppBundle\Repository\DoctrineUserRepository;
    use AppBundle\Repository\DoctrinePostRepository;
    use AppBundle\Repository\BaseDoctrineRepository;
    use Symfony\Component\DependencyInjection\ChildDefinition;
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->register(BaseDoctrineRepository::class)
        ->setAbstract(true)
        ->addArgument(new Reference('doctrine.entity_manager'))
        ->addMethodCall('setLogger', array(new Reference('logger')))
    ;
    
    // расширить сервис AppBundle\Repository\BaseDoctrineRepository
    $definition = new ChildDefinition(BaseDoctrineRepository::class);
    $definition->setClass(DoctrineUserRepository::class);
    $container->setDefinition(DoctrineUserRepository::class, $definition);
    
    $definition = new ChildDefinition(BaseDoctrineRepository::class);
    $definition->setClass(DoctrinePostRepository::class);
    $container->setDefinition(DoctrinePostRepository::class, $definition);
    
    // ...
    

В этом контексте, наличие сервиса parent предполагает, что аргументы и вызовы метода родительского сервиса должны быть использованы для дочерних сервисов. В особенности, EntityManager будет внедрён, а setLogger() будет вызван, когда будет инстанциирован AppBundle\Repository\DoctrineUserRepository.

Все атрибуты родительского сервиса являются общими с дочерним, кроме shared, abstract и tags. Они не наследуются от родителя.

Note

Если у вас есть раздел _defaults в вашем файле, все дочерние сервисы должны ясно переопределять эти значения, чтобы избежать двусмысленности. Вы увидите чёткое сообщение ошибки об этом.

Tip

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

Переопределение родительских зависимостей

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

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    services:
        # ...
    
        AppBundle\Repository\DoctrineUserRepository:
            parent: AppBundle\Repository\BaseDoctrineRepository
    
            # переопределяет публичную настройку родительского сервиса
            public: false
    
            # добавляет аргумент '@app.username_checker' к родительскому
            # списку аргументов
            arguments: ['@app.username_checker']
    
        AppBundle\Repository\DoctrinePostRepository:
            parent: AppBundle\Repository\BaseDoctrineRepository
    
            # переопределяет первый аргумент (используя специальный ключ index_N)
            arguments:
                index_0: '@doctrine.custom_entity_manager'
    
  • XML
     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
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- ... -->
    
            <!-- переопределяет публичную настройку родительского сервисаe -->
            <service id="AppBundle\Repository\DoctrineUserRepository"
                parent="AppBundle\Repository\BaseDoctrineRepository"
                public="false"
            >
                <!-- добавляет аргумент '@app.username_checker' к родительскому
                     списку аргументов -->
                <argument type="service" id="app.username_checker" />
            </service>
    
            <service id="AppBundle\Repository\DoctrinePostRepository"
                parent="AppBundle\Repository\BaseDoctrineRepository"
            >
                <!-- переопределяет первый аргумент (используя атрибут index) -->
                <argument index="0" type="service" id="doctrine.custom_entity_manager" />
            </service>
    
            <!-- ... -->
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    use AppBundle\Repository\DoctrineUserRepository;
    use AppBundle\Repository\DoctrinePostRepository;
    use AppBundle\Repository\BaseDoctrineRepository;
    use Symfony\Component\DependencyInjection\ChildDefinition;
    use Symfony\Component\DependencyInjection\Reference;
    // ...
    
    $definition = new ChildDefinition(BaseDoctrineRepository::class);
    $definition->setClass(DoctrineUserRepository::class);
    // переопределяет публичную настройку родительского сервиса
    $definition->setPublic(false);
    // добавляет аргумент '@app.username_checker' к родительскому списку аргументов
    $definition->addArgument(new Reference('app.username_checker'));
    $container->setDefinition(DoctrineUserRepository::class, $definition);
    
    $definition = new ChildDefinition(BaseDoctrineRepository::class);
    $definition->setClass(DoctrinePostRepository::class);
    // переопределяет первый аргумент
    $definition->replaceArgument(0, new Reference('doctrine.custom_entity_manager'));
    $container->setDefinition(DoctrinePostRepository::class, $definition);
    

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.