Как разработчики, мы всегда пытаемся найти новые способы написания хорошо спроектированного и чистого кода, внедряя новые стили, используя шаблоны проектирования и пробуя новые надежные фреймворки. В этой статье мы рассмотрим шаблон проектирования внедрения зависимостей через компонент 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 в целом? Дайте нам знать!