Service Container
Service Container (сервіс-контейнер, раніше IoC-контейнер) - це потужний засіб для управління залежностями класів. У світі веб-розробки є такий модний термін - Dependency Injection, «використання залежностей», він означає запровадження деяких класів у створюваний клас через конструктор чи метод-сеттер. Створюваний клас використовує ці класи у роботі. Сервіс-контейнер реалізує саме цей функціонал.
Дещо спрощуючи, можна сказати так: коли фреймворку потрібно створити клас, він застосовує не конструкцію new SomeClass(new SomeService()) , а App::make('SomeClass') , попередньо зареєструвавши функцію, яка створює клас SomeClass і всі класи, які Один клас приймає як аргументи конструктора.
Ось простий приклад:
У цьому прикладі нам потрібно в обробнику PurchasePodcast написати листа користувачеві для підтвердження покупки. Так як ми хочемо дотримуватися першого принципу SOLID - «Принцип поділу відповідальності», ми не пишемо в ньому код спілкування з SMTP-сервером і т.п., а вбудовуємо, впроваджуємо (inject) до нього клас відправлення мейлів. Перевага такого підходу - не змінюючи код класу PurchasePodcast, ми можемо легко змінити спосіб відправки пошти, наприклад, з сервісу MailChimp на Mailjet або інший, а для тестування можемо використовувати клас-заглушку.
Сервіс-контейнер - дуже важлива річ, без нього неможливо побудувати справді велику програму Laravel. Також глибоке розуміння його роботи необхідно, якщо ви хочете змінювати код ядра Laravel та пропонувати нові фічі.
Використання
Зв'язування (Binding, реєстрація)
Оскільки майже всі биндинги, тобто. відповідність рядкового ключа реальному об'єкту в контейнері, у вашому додатку будуть реєструватися у методі register()сервіс-провайдерів, всі наведені нижче приклади дано для цього контексту. Якщо ви хочете використовувати контейнер в іншому місці своєї програми, ви можете впровадити в свій клас Illuminate Contracts Container Container . Також для доступу до контейнера можна використовувати фасад App. (TODO доповнити прикладами)
Реєстрація звичайного класу
Усередині сервіс-провайдера екземпляр контейнера знаходиться в $this-app.
Зареєструвати (bind, зв'язати) клас можна двома шляхами – за допомогою коллбек-функції або прив'язки інтерфейсу до реалізації.
Розглянемо перший метод. Коллбек реєструється в сервіс-контейнері під якимось рядковим ключем (в даному випадку FooBar ) - зазвичай для цього використовують назву класу, який повертатиметься цим коллбеком:
Коли з контейнера буде запрошений об'єкт за ключом FooBar, контейнер створить об'єкт класу FooBar, конструктор якого як аргумент додасть об'єкт з контейнера з ключем SomethingElse.
Реєстрація класу-синглтону
Іноді вам потрібно, щоб об'єкт створювався один раз, а решта разів, коли ви запитуєте його, вам повертався той же створений екземпляр. У цьому випадку замість bind використовуйте singleton:
Додавання існуючого екземпляра класу в контейнер
Ви можете додати в контейнер існуючий екземпляр класу:
Одержання з контейнера
Є кілька способів одержати (resolve) вміст контейнера. По-перше, ви можете використовувати метод make() :
По-друге, ви можете звернутися до контейнера як до масиву:
І, нарешті, по-третє (і в головних) ви можете явно вказати тип аргументу в конструкторі класу і фреймворк сам візьме його з контейнера (у прикладі нижче це UserInterface):
Зв'язування інтерфейсу зреалізацією
Ін'єкції залежностей
Особливо цікава та потужна можливість сервіс-контейнера – пов'язувати інтерфейси з різними їх реалізаціями. Наприклад, наша програма використовує Pusher для відправлення та прийому push-повідомлень. Якщо ми використовуємо Pusher PHP SDK, ми повинні впровадити екземпляр класу PusherClient у наш клас:
Все б нічого, але наш код стає зав'язаним на конкретний сервіс – Pusher. Якщо надалі ми заходимо його змінити, або просто Pusher змінить назви методів у своєму SDK, ми будемо змушені змінювати код у нашому класі CreateOrderHandler.
Від класу до інтерфейсу
Для того, щоб «ізолювати» клас CreateOrderHandler від постійно змінного зовнішнього світу, визначимо якийсь постійний інтерфейс, з реалізаціями якого наш клас тепер працюватиме.
Коли ми створимо реалізацію (implementation) цього інтерфейсу, PusherEventPusher, ми можемо пов'язати її з інтерфейсом у методі register() сервіс-провайдера:
Тут ми говоримо фреймворку, що коли з контейнера буде запрошено EventPusher, замість нього віддавати реалізацію цього інтерфейсу PusherEventPusher. Тепер ми можемо переписати наш конструктор класу СreateOrderHandler таким чином:
Тепер, з якою б реалізацією роботи реалтаймових повідомлень ми не працювали, змінювати код в CreateOrderHandler нам не потрібно.
Контекстне зв'язування
Іноді у вас може бути кілька реалізацій одного інтерфейсу, і ви хочете впроваджувати їх кожен у свій клас. Наприклад, коли робиться нове замовлення, вам потрібно надсилати повідомлення до PubNub замість Pusher. Ви можете зробити це так:
Тегування
Тепер ви можете отримати їх все відразу за тегом:
Використання на практиці
Laravel пропонуєкілька можливостей використання сервіс-контейнера для підвищення гнучкості та тестування вашого коду. Один із характерних прикладів - реалізація Dependency Injection в контролерах. Laravel реєструє всі контролери в сервіс-контейнері і тому при отриманні (resolve) класу контролера з контейнера автоматично виходять всі залежності, вказані в аргументах конструктора та інших методів контролера.
У цьому прикладі OrderRepository буде автоматично створено та подано як аргумент конструктору. Під час тестування ви можете зв'язати ключ 'OrderRepository' із класом-заглушкою та абстрагуватися від шару бази даних, протестувавши лише функціонал самого класу OrdersController .
Інші приклади
Очевидно, контролери не єдині класи, які фреймворк бере з сервіс-контейнера. Ви можете використовувати цей принцип в обробниках маршрутів, подій, черг і т.д. Приклади використання сервіс-контейнера наведено у відповідних розділах документації.
Події контейнера
Реєстрація події на вилучення об'єкта з контейнера
Сервіс-контейнер запускає подію щоразу, коли об'єкт виймається з контейнера. Можна ловити всі події, можна тільки ті, що прив'язані до конкретного ключа.
Об'єкт, що отримується з контейнера, передається у функцію-колбек.