Нет необходимости связывать классы в контейнер, если они не зависят только от интерфейса.
Контейнеру не нужно указывать как построить эти объекты, поскольку он автоматически собирает их
используя отражение.
Сервис контейнер
Содержание:
Введение
Laravel сервис контейнер — это мощный инструмент для управления классовыми зависимостями и выполнения внедрения зависимости. Внедрение зависимости — причудливая фраза, которая значат: классовые зависимости внедрены в класс через конструктор или в некоторых случаях "setter" методы.
Давайте рассмотрим простой пример:
<?php
namespace App\Http\Controllers;
use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* The user repository implementation.
*
* @var UserRepository
*/
protected $users;
/**
* Create a new controller instance.
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the profile for the given user.
*
* @param int $id
* @return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
В этом примере, UserController
должен получить пользователей из источника данных.
Поэтому, мы внедрили сервис, получает пользователей. В этом контексте, наш UserRepository
скорее всего использует Eloquent, чтобы получить
информацию о пользователях из базы данных. Теперь, когда внедрён репозиторий, мы очень быстро можем изменить
реализацию выборки. Очень легко мы можем "имитировать" (mock) или создавать пустую реализацию
UserRepository
при тестировании приложения.
Глубокое понимание, что такое сервис контейнер Laravel — это насущная необходимость, чтобы сделать мощное, большое приложение. Также это ценный вклад в понимание функционирования самого ядра Laravel.
Связывание
Основы связывания
Большинство связей ваших сервис контейнеров будут зарегистрированы внутри сервис провайдеров. поэтому большинство примеров будут показывать использование контейнера в этом контексте.
Простое связывание
Внутри сервис провайдера, вы всегда можете получить доступ к контейнеру через $this->app
.
Мы можем зарегистрировать связь используя метод bind
, передавая класс или имя интерфейса,
который мы бы хотели зарегистрировать отдельно с замыканием, которое вернёт сущность класса:
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
Обратите внимание, что мы получаем сам контейнер в качестве аргумента. Затем мы можем использовать контейнер для установления зависимостей объекта, который мы строим.
Связывание Singleton
Метод singleton
связывает класс или интерфейс в контейнер, который должен быть вызван только
единожды. После связи синглетон, одна и таже сущность объекта будет возвращать при любом запросе к контейнеру:
$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
Связывание существующих экземпляров с контейнером
Можно приявязать существующий объект, используя метод instance
. Данный экземпляр
всегда будет возвращен при последующих вызовах в контейнер:
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\API', $api);
Связывание с дополнительным значениями
Иногда можете быть класс, который получает некоторый внедрённый класс. Дополнительно хотелось бы внедрять и некоторые другие значения. Вы можете использовать контекстное связывание, чтобы внедрить любое значение, которое необходимо классу.
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
Связывание интерфейсов
Очень мощная особенность сервис контейнра — это возможность связывать интерфейс с данной реализацией.
Например, предположим, что у нас есть интерфейс EventPusher
и RedisEventPusher
реализация. После того, как мы написали реализацию RedisEventPusher
этого интерфейса,
мы можем зарегистрировать её с помощью сервис контейнера:
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
Это выражение говорит контейнеру, что он должен внедрить RedisEventPusher
, когда классу
необходима реализация EventPusher
. Теперь мы можем вписывать интерфейс EventPusher
в конструктор или другое место, том где внедряется зависимость через сервис контейнер:
use App\Contracts\EventPusher;
/**
* Create a new class instance.
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
Контекстная привязка
Иногда у вас может быть 2 класса, которые реализуют один и тот же интерфейс, но вы бы внедрить различную
реализацию в каждый класс. Например, 2 контроллера могут зависить от разных реализаций контракта
Illuminate\Contracts\Filesystem\Filesystem
. Ларавел предоставляет простой интерфейс
для реализации этого поведения:
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when([VideoController::class, UploadController::class])
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
Тэги
Может так случиться, что вам потребуется получить все реализации конкретной категории. Например,
предположим, что вы проектируете агрегатор отчётов, который получает массив различных Report
реализаций интерфейса. После регистрации реализации Report
, вы можете присвоить им тег,
используя метод tag
:
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
После присвоения сервисам тегов, вы можете легко получить их методом tagged
:
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
Расширенное привязывание
Метод extend
позволяет делать модификации для решенных сервисов. Например,
когда вы уже определили сервис, ва можете добавить код для украшения или настройки сервиса.
Метод extend
принимет замыкание, которое должно вернуть модифицированный сервис,
как единственный аргумент:
$this->app->extend(Service::class, function ($service) {
return new DecoratedService($service);
});
Решения
Метод Make
Вы можете использовать метод make
, чтобы получить экземпляр класса из контейнера.
Метод make
принимает имя класса или интерфейса, который вы хотите получить:
$api = $this->app->make('HelpSpot\API');
Если вы находитесь в локации, которая не имеет доступа к переменной $app
, вы можете
использовать глобальный помощник resolve
:
$api = resolve('HelpSpot\API');
Если некоторые из ваших классовых зависимостей не могут быть получены через контейнер, вы можете
внедрить их через ассоциативный массив в метод makeWith
:
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
Автоматическая инъекция
Альтернативно, вы можете вписать зависимость в конструкторе класса, который получен контейнером, включая
контроллеры, слушатели событий, промежуточный слой и другое. Дополнительно, вы можете вписывать зависимости
в метод handle
из очереди заданий. На практике, именно так контейнер получает большинство объектов.
Например, вы можете вписать основанныый на вашем приложении репозиторий в конструкцию контроллера. Репозиторий автоматически будет вписан и внедрён в класс:
<?php
namespace App\Http\Controllers;
use App\Users\Repository as UserRepository;
class UserController extends Controller
{
/**
* The user repository instance.
*/
protected $users;
/**
* Create a new controller instance.
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the user with the given ID.
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
}
События контейнера
Каждый раз, когда сервис контейнер получает объект, запускается событие.
Вы можете прослушивать события используя метод resolving
:
$this->app->resolving(function ($object, $app) {
// Called when container resolves object of any type...
});
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
// Called when container resolves objects of type "HelpSpot\API"...
});
Как вы можете видеть, полученный объект будет передан в обратную связь, позволяя устанавливать дополнительные параметры объекту до момента доставки в конечную точку.
PSR-11
Сервис контейнер Ларавел расширяет интерфейс PSR-11. Таким образом, можно вписать интерфейс контейнера PSR-11, чтобы получить экземпляр контейнера Laravel:
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get('Service');
//
});
Вы получите исключение, если представленный идентификатор не будет получен. Исключение будет экземпляром
Psr\Container\NotFoundExceptionInterface
, если идентификатор никогда не был привязан.
Если идентификатор был привязан, но не смогли получить, вы получите исключение
Psr\Container\ContainerExceptionInterface
.