Ленивые сервисы

Ленивые сервисы

See also

Ещё один способ лениво внедрять сервисы - через настройщик сервисов.

Почему ленивые сервисы?

В некоторых случаях, вы можете захотеть внедрить сервис, который немного тяжёлый для инстанциации, но не всегда используется внутри вашего объекта. Например, представьте, что у вас есть NewsletterManager и вы внедряете в него сервис mailer. Только несколько методов в вашем NewsletterManager действительно используют mailer, но даже когда он вам не нужн, сервис mailer всегда инстанциируется, чтобы построить ваш NewsletterManager.

Решением этого являются ленивые сервисы. С ленивым сервисом на самом деле внедряется "прокси" сервиса mailer. Он выглядит и ведёт себя точно так же, как mailer, кроме того, что mailer на самом деле не инстанциируется до того, как вы начнёте какое-либо взаимодействие с прокси.

Caution

Ленивые сервыси не поддерживают классы final, но вы можете использовать Проксификация интерфейса, чтобы обойти это ограничение.

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

6.2

Начиная с Symfony 6.2, вам не нужно устанавливать никакой пакет (например, symfony/proxy-manager-bridge), чтобы использовать инстанциирование ленивого сервиса.

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

Вы можете отметить сервис как lazy, изменив его определение:

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

Как только вы внедрите сервис в другой сервис, должен быть внедрен ленивый объект ghost с такой же подписью класса, представляющей сервис. Ленивый объект ghost - это объект, который создается пустым и который может инициализировать сам себя, когда к нему получают доступ впервые. То же самое происходит при вызове
Container::get() напрямую.

Чтобы проверить, работает ли ваш прокси, вы можете просто проверить интерфейс полученного объекта:

1
2
dump(class_implements($service));
// вывод должен включать в себя "ProxyManager\Proxy\LazyLoadingInterface"

Проксификация интерфейса

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

Чтобы обойти это ограничение, вы можете сконфигурировать прокси, чтобы он реализовывал только конкретные интерфейсы.

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
# config/services.yaml
services:
    App\Twig\AppExtension:
        lazy: 'Twig\Extension\ExtensionInterface'
        # или полное определение:
        lazy: true
        tags:
            - { name: 'proxy', interface: 'Twig\Extension\ExtensionInterface' }

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

Tip

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