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

Використання фабрики для створення сервісів

Сервіс-контейнер 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)
            // У версіях до Symfony 5.1 функція service() називалася 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.