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

Использование фабрики для созданя сервисов

Сервис-контейнер Symfony предоставляет несколько функция для контролирования создания объектов, которые позволяют вам указывать аргументы, переданные конструктору. а также вызывать методы и устанавливать параметры.

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

Статичные фабрики

Представьте, что у вас есть фабрика, которая конфигурирует и возвращает новый объект NewsletterManager, вызывая статический метод createNewsletterManager():

// src/Email\NewsletterManagerStaticFactory.php
namespace App\Email;

// ...

class NewsletterManagerStaticFactory
{
    public static function createNewsletterManager(): NewsletterManager
    {
        $newsletterManager = new NewsletterManager();

        // ...

        return $newsletterManager;
    }
}

Чтобы сделать объект NewsletterManager доступным в качестве сервиса, используйте опцию factory, чтобы определить. какой метод какого класса должен быть вызван для создания его объекта:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/services.yaml
    services:
        # ...
    
        App\Email\NewsletterManager:
            # первый аргумент - это класс, а второй - статичный метод
            factory: ['App\Email\NewsletterManagerStaticFactory', 'createNewsletterManager']
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- config/services.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"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="App\Email\NewsletterManager">
                <!-- первый аргумент - это класс, а второй - статичный метод -->
                <factory class="App\Email\NewsletterManagerStaticFactory" method="createNewsletterManager"/>
    
                <!-- если класс фабрики такой же, как класс сервиса, вы можете опустить
                     атрибут 'class' и определить только атрибут 'method':
    
                     <factory method="createNewsletterManager"/>
                -->
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use App\Email\NewsletterManager;
    use App\Email\NewsletterManagerStaticFactory;
    
    return function(ContainerConfigurator $configurator) {
        $services = $configurator->services();
    
        $services->set(NewsletterManager::class)
            // первый аргумент - это класс, а второй - статичный метод
            ->factory([NewsletterManagerStaticFactory::class, 'createNewsletterManager']);
    };
    

Note

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

Нестатичные фабрики

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

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    # config/services.yaml
    services:
        # ...
    
        # во-первых, создайте сервис для фабрики
        App\Email\NewsletterManagerFactory: ~
    
        # во-вторых, используйте сервис фбарики как первый аргумент опции
        # 'factory' и метод фабрики как второй аргумент
        App\Email\NewsletterManager:
            factory: ['@App\Email\NewsletterManagerFactory', 'createNewsletterManager']
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- config/services.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"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- во-первых, создайте сервис для фабрики -->
            <service id="App\Email\NewsletterManagerFactory"/>
    
            <!-- во-вторых, используйте сервис фбарики как первый аргумент опции
                 'factory' и метод фабрики как второй аргумент -->
            <service id="App\Email\NewsletterManager">
                <factory service="App\Email\NewsletterManagerFactory"
                    method="createNewsletterManager"
                />
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use App\Email\NewsletterManager;
    use App\Email\NewsletterManagerFactory;
    
    return function(ContainerConfigurator $configurator) {
        $services = $configurator->services();
    
        // во-первых, создайте сервис для фабрики
        $services->set(NewsletterManagerFactory::class);
    
        // во-вторых, используйте сервис фбарики как первый аргумент опции
        // 'factory' и метод фабрики как второй аргумент
        $services->set(NewsletterManager::class)
            // In versions earlier to Symfony 5.1 the service() function was called ref()
            ->factory([service(NewsletterManagerFactory::class), 'createNewsletterManager']);
    };
    

Вызываемые фабрики

Представьте, что теперь вы изменили метод вашей фабрики на __invoke(), чтобы ваш сервис фабрики мог быть использован в качестве обратного вызова:

// src/Email/InvokableNewsletterManagerFactory.php
namespace App\Email;

// ...
class InvokableNewsletterManagerFactory
{
    public function __invoke(): NewsletterManager
    {
        $newsletterManager = new NewsletterManager();

        // ...

        return $newsletterManager;
    }
}

Сервисы могут быть созданы и сконфигурированы через вызываемые фабрики, опуская имя метода:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/services.yaml
    services:
        # ...
    
        App\Email\NewsletterManager:
            class:   App\Email\NewsletterManager
            factory: '@App\Email\NewsletterManagerFactory'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- config/services.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"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- ... -->
    
            <service id="App\Email\NewsletterManager"
                     class="App\Email\NewsletterManager">
                <factory service="App\Email\NewsletterManagerFactory"/>
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use App\Email\NewsletterManager;
    use App\Email\NewsletterManagerFactory;
    
    return function(ContainerConfigurator $configurator) {
        $services = $configurator->services();
    
        $services->set(NewsletterManager::class)
            ->factory(service(NewsletterManagerFactory::class));
    };
    

Передача аргументов методу фабрики

Tip

Аргументы в вашем методе фабрики автомонтируются, если это включено в вашем сервисе.

Если вам над передать аргументы методу фабрики, вы можете использовать опции arguments. Например, представьте, что метод createNewsletterManager() в предыдущем примере берёт сервис templating в качестве аргумента:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/services.yaml
    services:
        # ...
    
        App\Email\NewsletterManager:
            factory:   ['@App\Email\NewsletterManagerFactory', createNewsletterManager]
            arguments: ['@templating']
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- config/services.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"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- ... -->
    
            <service id="App\Email\NewsletterManager">
                <factory service="App\Email\NewsletterManagerFactory" method="createNewsletterManager"/>
                <argument type="service" id="templating"/>
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // config/services.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    
    use App\Email\NewsletterManager;
    use App\Email\NewsletterManagerFactory;
    
    return function(ContainerConfigurator $configurator) {
        $services = $configurator->services();
    
        $services->set(NewsletterManager::class)
            ->factory([service(NewsletterManagerFactory::class), 'createNewsletterManager'])
            ->args([service('templating')])
        ;
    };
    

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