Service Container
Service Container
Вступление
Сервисный контейнер Laravel является мощным инструментом для управления классовыми зависимостями и выполнения инъекций зависимостей. Инъекция зависимостей — это причудливая фраза, которая, по сути, означает следующее: классовые зависимости "инжектируются" в класс через конструктор или, в некоторых случаях, через методы "setter".
Давайте рассмотрим простой пример:
В этом примере UserController
должен извлекать пользователей из источника данных. Таким образом, мы внедрим службу, которая может извлекать пользователей. В этом контексте наш UserRepository
скорее всего использует Eloquent для получения информации о пользователе из базы данных. Однако, поскольку репозиторий инжектируется, мы можем легко поменять его на другую реализацию. Также мы можем легко "мокнуть", или создать фиктивную реализацию UserRepository
при тестировании нашего приложения.
Глубокое понимание сервисного контейнера Laravel необходимо для создания мощного, крупного приложения, а также для внесения вклада в само ядро Laravel.
Биндинг
Основы биндинга
Почти все биндинги к сервис-контейнерам будут зарегистрированы в сервис-провайдере, поэтому большинство из этих примеров продемонстрируют использование контейнера в данном контексте.
Нет необходимости привязывать классы к контейнеру, если они не зависят от каких-либо интерфейсов. Контейнер не нуждается в инструктаже о том, как строить эти объекты, так как он может автоматически разрешать эти объекты с помощью рефлексии.
Простые биндинги
Внутри поставщика услуг вы всегда имеете доступ к контейнеру через свойство $this->app
. Мы можем зарегистрировать привязку, используя метод bind
, передав имя класса или интерфейса, который мы хотим зарегистрировать вместе с Closure
, который возвращает экземпляр класса:
Обратите внимание, что мы получаем сам контейнер в качестве аргумента в резолвер. Затем мы можем использовать контейнер для разрешения суб-зависимостей строящегося объекта.
Биндинг синглтона
Метод singleton
привязывает к контейнеру класс или интерфейс, который должен быть разрешен только один раз. Как только singleton-привязка будет разрешена, тот же самый экземпляр объекта будет возвращен при последующих вызовах в контейнер:
Биндинг экземпляра
Вы также можете привязать существующий экземпляр объекта к контейнеру с помощью метода instance
. Данный экземпляр всегда будет возвращаться при последующих обращениях в контейнер:
Биндинг интерфейса к реализации
Очень мощной особенностью сервисного контейнера является его способность привязывать интерфейс к заданной реализации. Например, предположим, что у нас есть интерфейс EventPusher
и реализация RedisEventPusher
. После того, как мы закодировали нашу реализацию RedisEventPusher
этого интерфейса, мы можем зарегистрировать его в сервисном контейнере таким образом:
Это выражение говорит контейнеру, что он должен внедрять RedisEventPusher
, когда классу нужна реализация EventPusher
. Теперь мы можем писать тайп-хинт на интерфейс EventPusher
в конструкторе, или в любом другом месте, где зависимости инжектируются сервисным контейнером:
Контекстный биндинг
Иногда у вас может быть два класса, которые используют один и тот же интерфейс, но вы хотите внедрять различные реализации в каждый класс. Например, два контроллера могут зависеть от различных реализаций контракта Illuminate\Contracts\Filesystem\Filesystem
. Ларавел предоставляет простой интерфейс для определения такого поведения:
Биндинг примитивов
Иногда вы можете иметь класс, который получает некоторые вводимые классы, но также нуждается в внедрении примитивного значения, такого как целое число. Вы можете легко использовать контекстную привязку, чтобы ввести любое значение, которое может понадобиться вашему классу:
Иногда класс может зависеть от массива тегированных экземпляров. Используя метод giveTagged
, можно легко ввести все привязки контейнеров с помощью этого тега:
Binding Typed Variadics
Иногда можно иметь класс, который получает массив типизированных объектов с помощью конструктора variadic:
Используя контекстную привязку, вы можете разрешить эту зависимость, предоставив метод give
с Closure, который возвращает массив разрешенных экземпляров Filter
:
Для удобства, вы также можете просто предоставить массив имен классов, которые будут разрешаться контейнером всякий раз, когда Firewall
нуждается в Filter
экземплярах:
Variadic Tag Dependencies
Иногда класс может иметь вариадическую зависимость, которая имеет тайп-хинт (Report ...$reports
). Используя методы needs
и giveTagged
, можно легко ввести все привязки контейнеров с этим тегом для данной зависимости:
Тегирование
Иногда может понадобиться решить все проблемы определенной "категории" привязки. Например, возможно, Вы строите агрегатор отчетов, который получает массив из множества различных реализаций интерфейса Report
. После регистрации реализаций Report
, Вы можете присвоить им тег, используя метод tag
:
После того, как сервисы будут тегированы, вы можете легко разрешить их все с помощью метода tagged
:
Расширенные биндинги
Метод extend
позволяет модифицировать разрешенные сервисы. Например, когда сервис разрешен, вы можете запустить дополнительный код для украшения или настройки сервиса. Метод extend
в качестве единственного аргумента принимает Closure, который должен возвращать модифицированный сервис. Closure получает разрешаемый сервис и экземпляр контейнера:
Разрешение
Метод make
make
Вы можете использовать метод make
для разрешения экземпляра класса из контейнера. Метод make
принимает имя класса или интерфейса, который вы хотите разрешить:
Если Вы находитесь в месте, где находится Ваш код, который не имеет доступа к переменной $app
, Вы можете воспользоваться глобальным помощником resolve
:
Если некоторые из зависимостей вашего класса не разрешимы через контейнер, вы можете внедрить их, передав в виде ассоциативного массива в метод makeWith
:
Автоматическое внедрение
Или же, что немаловажно, можно "type-hint" зависимость в конструкторе класса, которая разрешается контейнером, в том числе в контроллерах, слушателях событий, посредниках, и многих других. Кроме того, вы можете type-hint зависимости в методе handle
очереди заданий. На практике, это то, как большинство ваших объектов должны быть решены контейнером.
Например, вы можете type-hint в конструкторе контроллера подсказку к репозиторию, определённому вашим приложением. Хранилище будет автоматически разрешено и внедрено в класс:
События контейнера
Сервисный контейнер запускает событие каждый раз при разрешении объекта. Вы можете прослушать это событие, используя метод resolving
:
Как видите, разрешаемый объект будет передан функции обратного вызова, что позволит вам установить любые дополнительные свойства на объекте до того, как он будет передан его потребителю.
PSR-11
Сервисный контейнер Laravel реализует интерфейс PSR-11. Поэтому, чтобы получить экземпляр контейнера Laravel, Вы можете указать type-hint на интерфейс контейнера PSR-11:
Исключение бросается, если заданный идентификатор не может быть разрешен. Исключением будет экземпляр Psr\Container\NotFoundExceptionInterface
, если идентификатор никогда не был связан. Если идентификатор был связан, но не смог быть разрешен, то будет выброшен экземпляр Psr\Container\ContainerExceptionInterface
.
Last updated