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

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

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

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

Однак, інколи вам потрібно застосувати патерн дизайну фабрики, щоб делегувати створення об'єктва деякому спеціальному об'єкту під назвою "фабрика". У таких випадках, сервіс-контейнер може викликати метод у вашій фабриці, щоб створити об'єкт, а не напрму інстанціювати клас.

Статичні фабрики

Уявіть, що у вас є фабрика, яка конфігурує та повертає новий об'єкт NewsletterManager, викликаючи статичний метод createNewsletterManager():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/Email/NewsletterManagerStaticFactory.php
namespace App\Email;

// ...

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

        // ...

        return $newsletterManager;
    }
}

Щоб зробити об'єкт NewsletterManager доступним у якості сервісу, використайте опцію factory, щоб визначити, який метод якого класу має бути викликаний для створення його об'єкта:

1
2
3
4
5
6
7
# config/services.yaml
services:
    # ...

    App\Email\NewsletterManager:
        # перший аргумент - це клас, а другий - статичний метод
        factory: ['App\Email\NewsletterManagerStaticFactory', 'createNewsletterManager']

Note

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

Використання класу як фабрики

Коли статичний метод фабрики знаходиться у тому ж класі, що і створений екземпляр,
ім'я класу можна не вказувати в оголошенні фабрики.
Припустимо, що клас NewsletterManager має метод create(), який потрібно викликати для створення об'єкта і потребує відправника:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Email/NewsletterManager.php
namespace App\Email;

// ...

class NewsletterManager
{
    private string $sender;

    public static function create(string $sender): self
    {
        $newsletterManager = new self();
        $newsletterManager->sender = $sender;
        // ...

        return $newsletterManager;
    }
}

Ви можете опустити клас в оголошенні фабрики:

1
2
3
4
5
6
7
8
# config/services.yaml
services:
    # ...

    App\Email\NewsletterManager:
        factory: [null, 'create']
        arguments:
            $sender: 'fabien@symfony.com'

Також можна використовувати опцію constructor замість передачі null в якості класу фабрики:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Email/NewsletterManager.php
namespace App\Email;

use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;

#[Autoconfigure(bind: ['$sender' => 'fabien@symfony.com'], constructor: 'create')]
class NewsletterManager
{
    private string $sender;

    public static function create(string $sender): self
    {
        $newsletterManager = new self();
        $newsletterManager->sender = $sender;
        // ...

        return $newsletterManager;
    }
}

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

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

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']

Викличні фабрики

Уявіть, що тепер ви змінили метод вашої фабрики на __invoke(), щоб ваш сервіс фабрики міг бути використаний у якості зворотного виклику:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Email/InvokableNewsletterManagerFactory.php
namespace App\Email;

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

        // ...

        return $newsletterManager;
    }
}

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

1
2
3
4
5
6
7
# config/services.yaml
services:
    # ...

    App\Email\NewsletterManager:
        class:   App\Email\NewsletterManager
        factory: '@App\Email\InvokableNewsletterManagerFactory'

Використання виразів у фабриках сервісів

Замість використання PHP-класів як фабррики, ви також можете використати вирази. Це дозволяє вам, наприклад, змінити сервіс, зановуючись на параметрі:

1
2
3
4
5
6
7
8
9
10
11
12
# config/services.yaml
services:
    App\Email\NewsletterManagerInterface:
        # використати сервіс "tracable_newsletter", якщо включено налагодження, і "newsletter" - якщо ні.
        # "@=" вказує, що це є виразом
        factory: '@=parameter("kernel.debug") ? service("tracable_newsletter") : service("newsletter")'

    # ви можете використати функцію arg(), щоб отримати аргумент з визначення
    App\Email\NewsletterManagerInterface:
        factory: "@=arg(0).createNewsletterManager() ?: service("default_newsletter_manager")"
        arguments:
            - '@App\Email\NewsletterManagerFactory'

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

Tip

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

Якщо вам потрібно передати аргументи методу фабрики, ви можете використати опцію arguments. Наприклад, уявіть, що метод createNewsletterManager() у попередньому прикладі, бере сервіс templating в якості аргументу:

1
2
3
4
5
6
7
# config/services.yaml
services:
    # ...

    App\Email\NewsletterManager:
        factory:   ['@App\Email\NewsletterManagerFactory', createNewsletterManager]
        arguments: ['@templating']