Ліниві сервіси

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

Ліниві сервіси

See also

Ще один спосіб впроваджувати ліниві сервіси - через підписника сервісу.

Чому ліниві сервіси?

У деяких випадках, ви можете захотіти впровадити сервіс, який трохи заважкий для інстанціювання, але не завжди використовується всередині вашого обʼєкта. Наприклад, уявіть, що у вас є NewsletterManager і ви впроваджуєте у нього сервіс mailer. Лише декілька методів у вашому NewsletterManager дійсно використовують mailer, але навіть коли він вам не потрібний, сервіс mailer завжди інстанціюється, щоб побудувати ваш NewsletterManager.

Рішенням цього є ліниві сервіси. З лінивим сервісом насправді впроваджується "проксі" сервісу mailer. Він виглядає та поводить себе точно так само, як і mailer, крім того, що mailer насправді не інстанціюється до того як ви почнете якусь взаємодію з проксі.

Caution

Ліниві сервіси не підтримують класи final, але ви можете використати Проксування інтерфейсу, щоб обійти це обмеження.

У версіях PHP до 8.0, ліниві сервіси не підтримують параметри зі значеннями за замовчуванням для вбудованих PHP-класів (наприклад, PDO).

Конфігурація

Ви можете відмітити сервіс як lazy, змінивши його визначення:

1
2
3
4
# config/services.yaml
services:
    App\Twig\AppExtension:
        lazy: true

Як тільки ви впровадите сервіс в інший сервіс має бути впроваджено лінивий обʼєкт-привид з таким же підписом класу, що представляє сервіс. Лінивий обʼєкт-привид - це обʼєкт, який створюється порожнім і який може ініціалізувати сам себе, коли до нього отримують доступ вперше. Теж саме відбувається, якщо викликати Container::get() напряму.

Щоб перевірити, чи працює ваш лінивий сервіс, ви можете перевірити інтерфейс отриманого обʼєкта:

1
2
dump(class_implements($service));
// виведення повинно включати в себе "Symfony\Component\VarExporter\LazyGhostObjectInterface"

Ви також можете сконфігурувати лінивість вашого сервісу завдяки атрибуту Autoconfigure. Наприклад, щоб визначити ваш сервіс як лінивий, використайте наступне:

1
2
3
4
5
6
7
8
9
10
namespace App\Twig;

use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Twig\Extension\ExtensionInterface;

#[Autoconfigure(lazy: true)]
class AppExtension implements ExtensionInterface
{
    // ...
}

Ви також можете сконфігурувати лінивість, коли ваш сервіс впроваджується за допомогою атрибута Autowire:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace App\Service;

use App\Twig\AppExtension;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class MessageGenerator
{
    public function __construct(
        #[Autowire(service: 'app.twig.app_extension', lazy: true)] ExtensionInterface $extension
    ) {
        // ...
    }
}

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

1
2
3
4
5
public function __construct(
    #[Autowire(service: 'foo', lazy: FooInterface::class)]
    FooInterface|BarInterface $foo,
) {
}

Іншою можливістю є використання атрибута Lazy:

1
2
3
4
5
6
7
8
9
10
namespace App\Twig;

use Symfony\Component\DependencyInjection\Attribute\Lazy;
use Twig\Extension\ExtensionInterface;

#[Lazy]
class AppExtension implements ExtensionInterface
{
    // ...
}

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

1
2
3
4
5
public function __construct(
    #[Lazy(FooInterface::class)]
    FooInterface|BarInterface $foo,
) {
}

7.1

Атрибут #[Lazy] було представлено в Symfony 7.1.

Проксування інтерфейсу

За лаштунками, проксі, згенеровані для лінивого завантаження сервісів, наслідують з класу, використовуваного сервісом. Однак, іноди це взагалі неможливо (наприклад, через те, що клас - final і не може бути розширений) або незручно.

Щоб обійти це обмеження, ви можете сконфігурувати проксі, щоб він реалізовував тільки конкретні інтерфейси.

1
2
3
4
5
6
7
8
# config/services.yaml
services:
    App\Twig\AppExtension:
        lazy: 'Twig\Extension\ExtensionInterface'
        # or a complete definition:
        lazy: true
        tags:
            - { name: 'proxy', interface: 'Twig\Extension\ExtensionInterface' }

Як і в розділі Конфігурація , ви можете використати атрибут Autoconfigure, щоб сконфігурувати інтерфейс для проксування, передавши його FQCN як значення параметра lazy:

1
2
3
4
5
6
7
8
9
10
namespace App\Twig;

use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Twig\Extension\ExtensionInterface;

#[Autoconfigure(lazy: ExtensionInterface::class)]
class AppExtension implements ExtensionInterface
{
    // ...
}

Віртуальний проксі, впроваджений в інші сервіси, реалізовуватиме лише вказані інтерфейси і не розширюватиме оригінальний клас сервісу, що дозволить ліниве завантаження сервісів з використанням класів final. Ви можете сконфігурувати проксі так, щоб він реалізовував декілька інтерфейсів, додавши нові теги "proxy".

Tip

Ця функція може також діяти як охороень: враховуючи, що проксі не розширює оригінальний клас, лише методи, визначені інтерфейсом, можуть бути викликані; що запобігає виклику реалізації конкретних методів. Це також запобоігає впровадженню залежності взагалі, якщо ви додали підказку конкретної реалізації, замість інтерфейсу.