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

Дата обновления перевода 2024-07-27

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

Сервис-контейнер 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']