Сервис — определение того, как необходимо создать экземпляр класса.
По своей сути, сервисы, это PHP-классы объявленные в *.services.yml файле. Классы, зарегистрированные в виде сервисов, ничем не отличаются от обычных и могут быть инициализированы без использования контейнера сервисов. Однако, на практике, это осложнено тем, что классам нужны различные зависимости и их подготовка может стать проблематичной. Сервисы решают данную проблему.
Описывая классы в виде сервисов, появляется возможность указать название других сервисов в качестве зависимостей для текущего. Сервис контейнер инициализирует объекты для данных сервисов, как это определено для каждого из них, а затем передаёт их экземпляры в конструктор класса текущего сервиса, тем самым создавая новый объект.
Например, у вас есть PHP-класс Foo
, для работы которого требуется экземпляр класса Bar
, а для Bar
требуется экземпляр Baz
. Без использования сервисов, это выглядело бы следующим образом:
$baz = new Baz();
$bar = new Bar($baz);
$foo = new Foo($bar);
Если зависимостей много и они более комплексные, то ситуация быстро выходит из-под контроля и появляется множество дублирующего кода для иницализации зависимостей, а также потребность в их поддержке.
В случае сервисов, мы объявляем каждый класс как сервис с указанием какой класс использовать для инициализации, а также, какие зависимости нужны текущему сервису. Для примера выше, это выглядело бы следующим образом:
services:
baz:
class: Baz
bar:
class: Bar
arguments: ['@baz']
foo:
class: Foo
arguments: ['@bar']
Таким образом, обращаясь к сервису foo
, контейнер сервисов обнаружит что ему нужно создать объект с использованием класса Foo
, которому необходимо передать в конструктор объект созданный для сервиса bar
. В свою очередь, при создании объекта для сервиса bar
, он обнаружит что ему также требуется сервис baz
.
Так, контейнер создаст объект для класса Foo
, передав в него объект Bar
, которому будет передан объект Baz
. Это будет аналогично примеру выше с ручной инциализацией, только в данном случае, инциализация производится контейнером сервисов, а финальный код будет выглядеть примерно так:
$foo = $container->get('foo');
¶Преимущества и недостатки
Как и у всех подходов, у сервисов есть как преимущества и недостатки.
Преимущества сервисов:
- Позволяет использовать Dependency Injection, который сокращает количество кода за счёт автоматический инициализации зависимостей для нужного класса.
- Конечный пользователь (разработчик) работает с сервисами через единый API.
- Сервисы позволяют изменять свои свойства, что предоставляет возможность для подмены класса, аргументов или прочих значений для всего приложения сразу. Так как инициализацией занимается контейнер сервисов, подмена класса для сервиса не потребует обновления кодовой базы проекта.
- Обращение к сервису инициализирует объект единожды, при первом обращении, а при всех последующих, возвращает уже инициализированный экземпляр. Это очень положительно сказывается на производительности приложения и потребляемых им ресурсов. Данное поведение также можно переопределить, указав сервису свойство
shared: false
. - Сервисы могут иметь метки. При помощи контейнера сервисов можно находить все сервисы с определенной меткой и использовать их для каких-то целей. Таким образом, сервисы могут быть частью какой-то коллекции, которые одновременно вызываются при определенных условиях. Вам, как разработчику, не придётся думать, как найти все классы на проекте, которые отвечают каким-то признакам.
Недостатки сервисов:
- Если ваш класс, нуждается в инициализации на каждое обращение, то использование сервисов в данном случае, может сделать обратный эффект, и снизить производительность. Но такие случаи крайне редкие.
- Из-за простоты передачи зависимостей, очень легко создать божественный объект, так как вся «рутина» будет автоматизирована.
¶Когда использовать сервисы
Исходя из преимуществ и недостатков, можно сделать вывод, что сервисы могут как помочь улучшить удобство работы и производительность проекта, так и навредить.
Решается это тем, что нужно правильно определять что нужно завернуть в сервис, а что оставить обычным классом с классическим обращением по неймспейсу.
В общем случае, рекомендации будут следующие:
- Если ваш класс, нуждается в функционале, который предоставляется сторонним сервисом — используйте сервис и указывайте необходимый(е) в качестве аргумента.
- Если ваш класс самодостаточен, или вовсе состоит из статических методов (утилитарный класс, например
Drupal\Component\Utility\Crypt
), и не нуждается в других сервисах или инициализации комплексных классов, то лучше не объявлять его в качестве сервиса. - Если ваш класс нуждается в инициализации каждый раз, когда он вызывается (например Value Object), то сервисы лучше избегать. Хоть они и поддерживают возможность создания экземпляра на каждое обращение к сервису, эта операция намного затратнее по ресурсам, нежели простое создание экземпляра класса.
Выбор всегда очень ситуативен, очень сильно зависит от класса, задачи и назначения. Например, если вы пишете модуль, который хотите опубликовать в открытом доступе, то скорее всего, вы захотите объявить максимальное кол-во классов в виде сервисов, чтобы другие разработчики могли подменять их при помощи API, не прибегая к модификации вашего кода.
Рекомендации выше, лишь рекомендации, в каждой из них можно сделать наоборот и это тоже будет правильно, но делайте это только в том случае, если вы понимаете последствия, и не забывайте производить тестирование до и после.
¶Изменения в релизах
- Drupal 9.3.0 (08.12.2021): Добавлена поддержка автоматического подключения сервисов.
¶Ссылки
- Service Container (англ.), Symfony
- Drupal 8: Services, Niklan, 2017