Inversion of Control (IoC-контейнер)
11.x
.
Почему это важно?
Введение
Класс-контейнер обратного управления IoC (Inversion of Control) Laravel – мощное средство для управления зависимостями классов. Внедрение зависимостей – это способ исключения вшитых (hardcoded) взаимосвязей классов. Вместо этого зависимости определяются во время выполнения, что даёт бОльшую гибкость благодаря тому, что они могут быть легко изменены.
Понимание IoC-контейнера Laravel необходимо для построения больших приложений, а также для внесения изменений в код ядра самого фреймворка.
Использование
Помещение в контейнер
Есть два способа, которыми IoC-контейнер разрешает зависимости: через функцию-замыкание и через автоматическое определение.
Для начала давайте посмотрим замыкания. Поместим нечто в контейнер и назовём это foo
:
App::bind('foo', function($app)
{
return new FooBar;
});
Извлечение из контейнера
$value = App::make('foo');
При вызове метода App::make
вызывается соответствующая функция-замыкание и возвращается результат её вызова.
Помещение shared ("разделяемого") типа в контейнер
Иногда вам может понадобиться поместить в контейнер нечто, что должно быть создано только один раз, а все последующие вызовы должны возврать тот же объект.
App::singleton('foo', function()
{
return new FooBar;
});
Помещение готового экземпляра в контейнер
Вы также можете поместить уже созданный экземпляр объекта в контейнер, используя метод instance
:
$foo = new Foo
App::instance('foo', $foo);
Где писать код ?
Вышеописанные биндинги (связывание) в IoC, как и регистраторы обработчиков событий или фильтры роутов, можно писать в старт-файле app/start/global.php
, или, например, в файле app/ioc.php
(имя непринципиально) и подключать его (include) в app/start/global.php
. Если же вам ближе ООП-подход, или у вас в приложении много IoC-биндингов и вы хотите разложить их по категориям, а не держать все в одном файле, вы можете описать ваши биндинги в ваших сервис-провайдерах.
Автоопределение класса
Автоопределение класса
Контейнер IoC достаточно мощен, чтобы во многих случаях определять классы автоматически, без дополнительной настройки.
class FooBar {
public function __construct(Baz $baz)
{
$this->baz = $baz;
}
}
$fooBar = App::make('FooBar');
Обратите внимание, что даже без явного помещения класса FooBar в контейнер он всё равно был определён и зависимость Baz
была автоматически внедрена в него.
Если тип не был найден в контейнере, IoC будет использовать возможности PHP для изучения класса и контроля типов в его конструкторе. С помощью этой информации контейнер сам создаёт экземпляр класса.
Связывание интерфейса и реализации
В некоторых случаях класс может принимать экземпляр интерфейса, а не сам объект. В этом случае нужно использовать метод App::bind
для извещения контейнра о том, какая именно зависимость должна быть внедрена.
App::bind('UserRepositoryInterface', 'DbUserRepository');
Теперь посмотрим на следующий контроллер:
class UserController extends BaseController {
public function __construct(UserRepositoryInterface $users)
{
$this->users = $users;
}
}
Благодаря тому, что мы связали UserRepositoryInterface
с “настоящим” классом, DbUserRepository
, он будет автоматически встроен в контроллер при его создании.
Практическое использование
Laravel предоставляет несколько возможностей для использования контейнера IoC для повышения гибкости и тестируемости вашего приложения. Основной пример – внедрение зависимости при использовании контроллеров. Все контроллеры извлекаются из IoC, что позволяет вам использовать зависимости на основе контроля типов в их конструкторах – ведь они будут определены автоматически.
Контроль типов для указания зависимостей контроллера
class OrderController extends BaseController {
public function __construct(OrderRepository $orders)
{
$this->orders = $orders;
}
public function getIndex()
{
$all = $this->orders->all();
return View::make('orders', compact('all'));
}
}
В этом примере класс OrderRepository
автоматически встроится в контроллер. Это значит, что при использовании юнит-тестов класс-заглушка (“mock”) для OrderRepository
может быть добавлен в контейнер, таким образом легко имитируя взаимодействие с БД.
Другие примеры использования IoC
Фильтры, вью-композеры и обработчики событий могут также извлекаться из IoC. При регистрации этих объектов просто передайте имя класса, который должен быть использован:
Route::filter('foo', 'FooFilter');
View::composer('foo', 'FooComposer');
Event::listen('foo', 'FooHandler');
Сервис-провайдеры
Сервис-провайдеры (service providers, “поставщики услуг”) – отличный способ группировки схожих регистраций в IoC в одном месте. Их можно рассматривать как начальный запуск компонентов вашего приложения. Внутри поставщика услуг вы можете зарегистрировать драйвер авторизации, классы-хранилища вашего приложения или даже собственную консольную Artisan-команду.
БОльшая часть компонентов Laravel включает в себя свои сервис-провайдеры. Все зарегистрированные сервис-провайдеры в вашем приложении указаны в массиве providers
файла настроек app/config/app.php
.
Создание сервис-провайдера
Для создания нового сервис-провайдера просто наследуйте класс Illuminate\Support\ServiceProvider
и определите метод register
.
use Illuminate\Support\ServiceProvider;
class FooServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind('foo', function()
{
return new Foo;
});
}
}
Заметьте, что внутри метода register
IoC-контейнер приложения доступен в свойстве $this->app
. Как только вы создали провайдера и готовы зарегистрировать его в своём приложении, просто добавьте его в массивe providers
файла настроек app/config/app.php
.
Регистрация сервис-провайдеров во время выполнения
Вы можете зарегистрировать сервис-провайдеры “на лету”, используя метод App::register
:
App::register('FooServiceProvider');
События контейнера
Регистрация обработчика события
IoC-контейнер запускает событие каждый раз при извлечении объекта. Вы можете отслеживать его с помощью метода resolving
:
App::resolvingAny(function($object)
{
//
});
App::resolving('foo', function($foo)
{
//
});
Созданный объект передаётся в функцию-замыкание в виде параметра.