Статьи

Инъекция зависимостей с IoC Laravel

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

Внедрение зависимости

Внедрение зависимостей — это термин, придуманный Мартином Фаулером , и это процесс внедрения компонентов в ваше приложение. Как сказал Уорд Каннингем :

Внедрение зависимостей является ключевым элементом гибкой архитектуры.

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

class UserProvider{ protected $connection; public function __construct(){ $this->connection = new Connection; } public function retrieveByCredentials( array $credentials ){ $user = $this->connection ->where( 'email', $credentials['email']) ->where( 'password', $credentials['password']) ->first(); return $user; } } 

Если вы хотите протестировать или поддерживать этот класс, вам нужно получить доступ к реальной базе данных и выполнить несколько запросов. Чтобы избежать необходимости делать это и отделить класс от остальных, у вас есть один из трех вариантов внедрения класса Connection без непосредственного его использования.

При добавлении компонентов в ваш класс вы можете использовать один из трех вариантов:

Конструктор Инъекция

 class UserProvider{ protected $connection; public function __construct( Connection $con ){ $this->connection = $con; } ... 

Сеттер Инъекция

Точно так же мы можем внедрить нашу зависимость, используя метод установки:

 class UserProvider{ protected $connection; public function __construct(){ ... } public function setConnection( Connection $con ){ $this->connection = $con; } ... 

Интерфейс впрыска

 interface ConnectionInjector{ public function injectConnection( Connection $con ); } class UserProvider implements ConnectionInjector{ protected $connection; public function __construct(){ ... } public function injectConnection( Connection $con ){ $this->connection = $con; } } 

Когда класс реализует наш интерфейс, мы определяем метод injectConnection для разрешения зависимости.

преимущества

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

Если вы хотите узнать больше о DI, Алехандро Джервассио подробно и профессионально освещал эту статью в этой серии , так что обязательно прочитайте эти статьи. А как насчет IoC? Ioc (Inversion of control) не требуется для использования внедрения зависимостей, но он может помочь вам эффективно управлять своими зависимостями.

Инверсия контроля

Ioc — это простой компонент, который делает разрешение зависимостей более удобным. Вы описываете свои объекты в контейнере, и каждый раз, когда вы разрешаете класс, зависимости вводятся автоматически.

Ларавел Иок

Laravel Ioc — это особенный способ разрешения зависимостей, когда вы запрашиваете объект:

Мы будем использовать простой пример, который мы улучшим в этой статье.
Класс SimpleAuth имеет зависимость FileSessionStorage , поэтому наш код может выглядеть следующим образом:

 class FileSessionStorage{ public function __construct(){ session_start(); } public function get( $key ){ return $_SESSION[$key]; } public function set( $key, $value ){ $_SESSION[$key] = $value; } } class SimpleAuth{ protected $session; public function __construct(){ $this->session = new FileSessionStorage; } } //creating a SimpleAuth $auth = new SimpleAuth(); 

Это классический способ сделать это, давайте начнем с использования инжектора конструктора.

 class SimpleAuth{ protected $session; public function __construct( FileSessionStorage $session ){ $this->session = $session; } } 

Теперь мы создаем наш объект:

 $auth = new SimpleAuth( new FileSessionStorage() ); 

Теперь я хочу использовать Laravel Ioc для управления всем этим.

Поскольку класс Application расширяет класс Container , вы всегда можете получить доступ к контейнеру через фасад App .

 App::bind( 'FileSessionStorage', function(){ return new FileSessionStorage; }); 

Первый параметр для метода bind — это уникальный идентификатор для привязки к контейнеру, второй — функция обратного вызова, которая должна выполняться каждый раз, когда мы разрешаем класс FileSessionStorage , мы также можем передать строку, представляющую имя класса, как мы увидим далее.

Примечание: если вы проверяете пакеты Laravel, вы увидите, что иногда привязки группируются как ( view , view.finder ..).

Допустим, что, возможно, мы хотим переключить наше хранилище сеансов на MySql, наш класс должен быть похож на:

 class MysqlSessionStorage{ public function __construct(){ //... } public function get($key){ // do something } public function set( $key, $value ){ // do something } } 

Теперь, когда мы изменили зависимость, нам нужно изменить конструктор SimpleAuth и привязать новый объект к контейнеру!

Модули высокого уровня не должны зависеть от модулей низкого уровня. И то и другое
должно зависеть от абстракций.
Абстракции не должны зависеть от деталей. Детали должны зависеть
на абстракции.

Роберт С. Мартин

Наш класс SimpleAuth не должен беспокоиться о том, как SimpleAuth наше хранилище, вместо этого он должен больше фокусироваться на потреблении сервиса.

Итак, мы можем абстрагировать нашу реализацию хранилища:

 interface SessionStorage{ public function get( $key ); public function set( $key, $value ); } 

Таким образом, мы можем просто реализовать и запросить экземпляр интерфейса SessionStorage :

 class FileSessionStorage implements SessionStorage{ public function __construct(){ //... } public function get( $key ){ //... } public function set( $key, $value ){ //... } } class MysqlSessionStorage implements SessionStorage{ public function __construct(){ //... } public function get( $key ){ //... } public function set( $key, $value ){ //... } } class SimpleAuth{ protected $session; public function __construct( SessionStorage $session ){ $this->session = $session; } } 

Если мы попытаемся разрешить класс SimpleAuth через контейнер с помощью App::make('SimpleAuth') , контейнер сгенерирует BindingResolutionException , после попытки разрешить класс из привязок, вернувшись к методу отражения и разрешив все зависимости ,

 Uncaught  exception 'Illuminate\Container\BindingResolutionException'   with  message 'Target [SessionStorage] is not instantiable.' 

Контейнер пытается создать экземпляр интерфейса. Мы можем исправить это, создав конкретную привязку для нашего интерфейса.

 App:bind( 'SessionStorage', 'MysqlSessionStorage' ); 

Теперь каждый раз, когда мы пытаемся разрешить интерфейс через контейнер, мы получим экземпляр MysqlSessionStorage . Если мы хотим переключить наш сервис хранения, мы можем просто обновить привязки.

Примечание: если вы хотите увидеть, связан ли класс с контейнером, вы можете использовать App::bound('ClassName') или App::bindIf('ClassName') чтобы зарегистрировать привязку, если она еще не была зарегистрировано.

Laravel Ioc также предлагает App::singleton('ClassName', 'resolver') для общих привязок.
Вы также можете использовать App::instance('ClassName', 'instance') для создания общего экземпляра.
Если контейнер не может разрешить зависимость, он сгенерирует ReflectionException , но мы можем использовать App::resolvingAny(Closure) чтобы разрешить любой данный тип или как форму запасного варианта.

Примечание: если вы зарегистрируете распознаватель для данного типа, будет также вызван метод resolvingAny , но возвращается значение из метода bind .

Заключительные советы

  • Где хранить привязки:
    Если у вас небольшое приложение, вы можете использовать ваш global/start.php , но если ваш проект становится больше, вы должны использовать поставщика услуг .
  • Тестирование:
    Когда вы просто тестируете, вам нужно подумать об использовании php artisan tinker , он очень мощный и может увеличить ваш рабочий процесс тестирования Laravel.
  • API отражения:
    PHP Reflection API является очень мощным, и если вы хотите понять Laravel Ioc, вам необходимо ознакомиться с Reflection API, обязательно ознакомьтесь с этим руководством для получения дополнительной информации.

Вывод

Как всегда, лучший способ узнать что-то — это изучить исходный код. Laravel Ioc — это всего лишь один файл, и вам не потребуется много времени, чтобы пройти через все функции. Хотите узнать больше о Laravel IoC или IoC в целом? Дайте нам знать!