Может войдёшь?
Черновики Написать статью Профиль

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

перевод документация 5.х

  1. 1. Введение
  2. 2. Связывание
    1. 2.1. Связывание интерфейса с реализацией
    2. 2.2. Программа для интерфейса
    3. 2.3. Контекстное связывание
    4. 2.4. Тегирование
  3. 3. Использование на практике
  4. 4. Получение из контейнера
  5. 5. События контейнера
Этот перевод актуален для англоязычной документации на (ветка 5.1) и (ветка 5.0). Опечатка? Выдели и нажми Ctrl+Enter.

Введение

Сервис-контейнер в Laravel — это мощное средство для управления зависимостями классов и внедрения зависимостей. Внедрение зависимостей — это модный термин, который означает «внедрение» зависимостей класса в этот класс через конструктор или метод-сеттер.

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

PHP
<?php

namespace App\Jobs;

use 
App\User;
use 
Illuminate\Contracts\Mail\Mailer;
use 
Illuminate\Contracts\Bus\SelfHandling;

class 
PurchasePodcast implements SelfHandling
{
  
/**
   * Реализация почтового сервиса.
   */
  
protected $mailer;

  
/**
   * Создание нового экземпляра.
   *
   * @param  Mailer  $mailer
   * @return void
   */
  
public function __construct(Mailer $mailer)
  {
    
$this->mailer $mailer;
  }

  
/**
   * Покупка подкаста.
   *
   * @return void
   */
  
public function handle()
  {
    
//
  
}
}

В этом примере командный обработчик PHPPurchasePodcast должен отправить письмо пользователю для подтверждения покупки. Итак, мы внедрили сервис, отправляющий электронные письма. Когда сервис внедрён, мы можем легко подменить его с другой реализацией. Мы также можем легко создать «mock» или фиктивную реализацию отправителя почтовых сообщений при тестировании нашего приложения.

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

Связывание

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

В сервис-провайдере всегда есть доступ к контейнеру через переменную экземпляра PHP$this->app. Зарегистрировать привязку можно методом PHPbind(), передав имя того класса или интерфейса, который мы хотим зарегистрировать, вместе с замыканием, которое возвращает экземпляр класса:

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

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

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

Метод PHPsingleton() привязывает класс или интерфейс к контейнеру, который должен быть создан только один раз, и все последующие обращения к нему будут возвращать этот созданный экземпляр:

PHP
$this->app->singleton('FooBar', function ($app) {
  return new 
FooBar($app['SomethingElse']);
});

Связывание существующего экземпляра класса с контейнером

Вы можете также привязать существующий экземпляр объекта к контейнеру, используя метод PHPinstance(). Данный экземпляр будет всегда возвращаться при последующих обращениях к контейнеру:

PHP
$fooBar = new FooBar(new SomethingElse);

$this->app->instance('FooBar'$fooBar);

Связывание интерфейса с реализацией

Довольно мощная функция сервис-контейнера — возможность связать интерфейс с реализацией.

+ 5.0

добавлено в 5.0 ()

Например, если наше приложение интегрировано с веб-сервисом для отправки и получения событий в реальном времени Pusher. И если мы используем Pusher PHP SDK, то можем внедрить экземпляр клиента Pusher в класс:

PHP
<?php namespace App\Handlers\Commands;

use 
App\Commands\CreateOrder;
use 
Pusher\Client as PusherClient;

class 
CreateOrderHandler {

  
/**
   * Экземпляр клиента Pusher SDK.
   */
  
protected $pusher;

  
/**
   * Создание нового экземпляра обработчика заказов.
   *
   * @param  PusherClient  $pusher
   * @return void
   */
  
public function __construct(PusherClient $pusher)
  {
    
$this->pusher $pusher;
  }

  
/**
   * Выполнение данной команды.
   *
   * @param  CreateOrder  $command
   * @return void
   */
  
public function execute(CreateOrder $command)
  {
    
//
  
}

}

В этом примере хорошо то, что мы внедрили зависимости класса, но теперь мы жёстко привязаны к Pusher SDK. Если методы Pusher SDK изменятся, или мы решим полностью перейти на новый сервис событий, то нам надо будет переписывать CreateOrderHandler.

Программа для интерфейса

Чтобы «изолировать» CreateOrderHandler от изменений в сервисе событий, мы можем определить интерфейс EventPusher и реализацию PusherEventPusher:

PHP
<?php namespace App\Contracts;

interface 
EventPusher {

  
/**
   * Push a new event to all clients.
   *
   * @param  string  $event
   * @param  array  $data
   * @return void
   */
  
public function push($event, array $data);

}

Например, допустим, у нас есть интерфейс EventPusher и реализация RedisEventPusher. Когда мы написали нашу реализацию RedisEventPusher для этого интерфейса, мы можем зарегистрировать её в сервис-контейнере так:

PHP
$this->app->bind('App\Contracts\EventPusher''App\Services\RedisEventPusher');

Так контейнер понимает, что должен внедрить RedisEventPusher, когда классу нужна реализация EventPusher. Теперь мы можем использовать указание типа интерфейса EventPusher в конструкторе, или любом другом месте, где сервис-контейнер внедряет зависимости:

PHP
use App\Contracts\EventPusher;

/**
 * Создание нового экземпляра класса.
 *
 * @param  EventPusher  $pusher
 * @return void
 */
public function __construct(EventPusher $pusher)
{
    
$this->pusher $pusher;
}

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

Иногда у вас может быть два класса, которые используют один интерфейс. Но вы хотите внедрить различные реализации в каждый класс. Например, когда наша система получает новый заказ, нам может понадобиться отправка сообщения через PubNub, а не через Pusher. Laravel предоставляет простой и гибкий интерфейс для описания такого поведения:

PHP
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->
needs('App\Contracts\EventPusher')
          ->
give('App\Services\PubNubEventPusher');

Вы даже можете передать замыкание в метод PHPgive():

PHP
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->
needs('App\Contracts\EventPusher')
          ->
give(function () {
              
// Извлечение зависимости...
            
});

Тегирование

Иногда вам может потребоваться получить все реализации в определенной категории. Например, вы пишете сборщик отчётов, который принимает массив различных реализаций интерфейса Report. После регистрации реализаций Report вы можете присвоить им тег, используя метод PHPtag():

PHP
$this->app->bind('SpeedReport', function () {
  
//
});

$this->app->bind('MemoryReport', function () {
  
//
});

$this->app->tag(['SpeedReport''MemoryReport'], 'reports');

Теперь вы можете получить их по тегу методом PHPtagged():

PHP
$this->app->bind('ReportAggregator', function ($app) {
  return new 
ReportAggregator($app->tagged('reports'));
});
+ 5.0

добавлено в 5.0 ()

Использование на практике

Laravel предоставляет несколько способов использования сервис-контейнера для повышения гибкости и тестируемости вашего приложения. Одним из характерных примеров является получение контроллеров. Все контроллеры регистрируются через сервис-контейнер, и поэтому при получении класса контроллера из контейнера автоматически получаются все зависимости, указанные в аргументах конструктора и других методах контроллера.

PHP
<?php namespace App\Http\Controllers;

use 
Illuminate\Routing\Controller;
use 
App\Repositories\OrderRepository;

class 
OrdersController extends Controller {

  
/**
   * Экземляр репозитория заказа.
   */
  
protected $orders;

  
/**
   * Создание экземпляра контроллера.
   *
   * @param  OrderRepository  $orders
   * @return void
   */
  
public function __construct(OrderRepository $orders)
  {
    
$this->orders $orders;
  }

  
/**
   * Показать все заказы.
   *
   * @return Response
   */
  
public function index()
  {
    
$orders $this->orders->all();

    return 
view('orders', ['orders' => $orders]);
  }

}

В этом примере класс OrderRepository будет автоматически внедрён в контроллер. Это означает, что «mock» OrderRepository может быть привязан к контейнеру во время unit-тестирования, позволяя сделать безболезненную заглушку для взаимодействия на уровне базы данных.

Другие примеры использования контейнера

Естественно, как уже упоминалось, контроллеры — это не единственные классы, которые Laravel получает из сервис-контейнера. Вы также можете использовать указание типов в функциях-замыканиях маршрутов, фильтрах, очередях, слушателях событий и т.д. Примеры использования сервис-контейнера приведены в соответствующих разделах документации.

Получение из контейнера

Есть несколько способов получения содержимого контейнера. Во-первых, вы можете использовать метод PHPmake(), который принимает имя класса или интерфейса, который вы хотите получить:

PHP
$fooBar $this->app->make('FooBar');

Во-вторых, вы можете обратиться к контейнеру как к массиву, поскольку он реализует PHP-интерфейс ArrayAccess:

PHP
$fooBar $this->app['FooBar'];

И, наконец, самое главное, вы можете просто указать тип зависимости в конструкторе класса, который имеется в контейнере, включая контроллеры, слушатели событий, очереди задач, посредники и т.д. Это те способы, с помощью которых получаются большинство объектов из контейнера на практике.

Контейнер автоматически внедрит зависимости для своих классов. Например, вы можете указать тип репозитория, определённого вашим приложением в конструкторе контроллера. Репозиторий будет автоматически получен и внедрён в класс:

PHP
<?php

namespace App\Http\Controllers;

use 
Illuminate\Routing\Controller;
use 
App\Users\Repository as UserRepository;

class 
UserController extends Controller
{
  
/**
   * Экземпляр репозитория пользователя.
   */
  
protected $users;

  
/**
   * Создание нового экземпляра контроллера.
   *
   * @param  UserRepository  $users
   * @return void
   */
  
public function __construct(UserRepository $users)
  {
    
$this->users $users;
  }

  
/**
   * Показать пользователя с данным ID.
   *
   * @param  int  $id
   * @return Response
   */
  
public function show($id)
  {
    
//
  
}
}

События контейнера

Контейнер создаёт событие каждый раз, когда из него извлекается объект. Вы можете слушать эти события, используя метод PHPresolving():

PHP
$this->app->resolving(function ($object$app) {
  
// Вызывается при извлечении объекта любого типа...
});

$this->app->resolving(FooBar::class, function (FooBar $fooBar$app) {
  
// Вызывается при извлечении объекта типа "FooBar"...
});

Как видите, объект, получаемый из контейнера, передаётся в функцию обратного вызова, что позволяет вам задать любые дополнительные свойства для объекта перед тем, как отдать его тому, кто его запросил.

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.