Unetway

Laravel - Сервисный контейнер

Вступление

Контейнер службы Laravel - это мощный инструмент для управления зависимостями классов и выполнения внедрения зависимостей. Внедрение зависимостей - это причудливая фраза, которая, по сути, означает это: зависимости класса «внедряются» в класс через конструктор или, в некоторых случаях, методы «установки».

Давайте посмотрим на простой пример:

<?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 для извлечения информации о пользователях из базы данных. Однако, поскольку репозиторий внедрен, мы можем легко заменить его другой реализацией. Мы также можем легко «насмехаться» или создавать фиктивную реализацию UserRepositoryпри тестировании нашего приложения.

Глубокое понимание сервисного контейнера Laravel необходимо для создания мощного, большого приложения, а также для внесения вклада в само ядро ​​Laravel.

 

Переплет

Обязательные основы

Почти все привязки вашего контейнера услуг будут зарегистрированы у поставщиков услуг , поэтому в большинстве этих примеров будет продемонстрировано использование контейнера в этом контексте.

Нет необходимости связывать классы в контейнер, если они не зависят от каких-либо интерфейсов. Контейнеру не нужно указывать, как создавать эти объекты, поскольку он может автоматически разрешать эти объекты с помощью отражения.

Простые привязки

Внутри поставщика услуг у вас всегда есть доступ к контейнеру через свойство. Мы можем зарегистрировать привязку, используя метод, передавая имя класса или интерфейса, которое мы хотим зарегистрировать, вместе с a, который возвращает экземпляр класса:$this->appbindClosure

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

Обратите внимание, что мы получаем сам контейнер в качестве аргумента для преобразователя. Затем мы можем использовать контейнер для разрешения зависимостей объекта, который мы строим.

Связывание синглтона

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;
}

 

Контекстная привязка

Иногда у вас может быть два класса, которые используют один и тот же интерфейс, но вы хотите внедрить разные реализации в каждый класс. Например, два контроллера могут зависеть от разных реализаций контракта . Laravel предоставляет простой и удобный интерфейс для определения этого поведения: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');
          });

 

Tagging

Иногда вам может потребоваться разрешить все определенные «категории» привязки. Например, возможно, вы создаете агрегатор отчетов, который получает массив различных 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Метод принимает Closure, который должен вернуть модифицированную услугу, в качестве единственного аргумента:

$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

Сервисный контейнер Laravel реализует интерфейс PSR-11 . Поэтому вы можете напечатать подсказку интерфейса контейнера PSR-11, чтобы получить экземпляр контейнера Laravel:

use Psr\Container\ContainerInterface;

Route::get('/', function (ContainerInterface $container) {
    $service = $container->get('Service');

    //
});

Исключение выдается, если данный идентификатор не может быть разрешен. Исключением будет случай, если идентификатор никогда не был связан. Если идентификатор был привязан, но не может быть разрешен, будет создан экземпляр .Psr\Container\NotFoundExceptionInterfacePsr\Container\ContainerExceptionInterface