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

Внедрение зависимостей с помощью Laravel IoC

перевод

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

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

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

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

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

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

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

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

Внедрение конструктора

PHP
class UserProvider{
    protected 
$connection;

    public function 
__constructConnection $con ){
        
$this->connection $con;
    }
    ...

Внедрение сеттера

Аналогичным образом мы можем внедрить нашу зависимость с помощью метода сеттера:

PHP
class UserProvider{
    protected 
$connection;
    public function 
__construct(){
        ...
    }

    public function 
setConnectionConnection $con ){
        
$this->connection $con;
    }
    ...

Внедрение интерфейса

PHP
interface ConnectionInjector{
    public function 
injectConnectionConnection $con );
}

class 
UserProvider implements ConnectionInjector{
    protected 
$connection;

    public function 
__construct(){
        ...
    }

    public function 
injectConnectionConnection $con ){
        
$this->connection $con;
    }
}

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

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

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

Если вы захотите узнать больше о внедрении зависимостей, Алехандро Гервасио рассмотрел его широко и профессионально в этой серии статей, поэтому не забудьте прочесть их. А что насчёт IoC? IoC (Inversion of control — Инверсия управления) не обязательна для использования внедрения зависимостей, но она может помочь вам эффективно управлять вашими зависимостями.

Inversion of control — Инверсия управления

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

Laravel IoC

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

/packages/proger/habravel/uploads/161-di.png

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

У класса PHPSimpleAuth есть зависимость PHPFileSessionStorage, поэтому наш код может быть таким:

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

//создание SimpleAuth
$auth = new SimpleAuth();

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

PHP
class SimpleAuth{
  protected 
$session;

  public function 
__constructFileSessionStorage $session ){
    
$this->session $session;
  }
}

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

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

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

Так как класс PHPApplication наследует класс PHPContainer, вы всегда можете получать доступ к контейнеру через фасад PHPApp.

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

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

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

Предположим, что мы хотим переключить хранилище нашей сессии на MySQL, наш класс должен быть похож на:

PHP
class MysqlSessionStorage{

  public function 
__construct(){
    
//...
  
}

  public function 
get($key){
    
// сделать что-то
  
}

  public function 
set$key$value ){
    
// сделать что-то
  
}
}

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

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

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

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

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

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

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

PHP
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 
__constructSessionStorage $session ){
    
$this->session $session;
  }

}

Если мы попытаемся использовать класс PHPSimpleAuth через контейнер, используя PHPApp::make('SimpleAuth'), то после попытки использовать класс из привязок, отката к методу отражения и разрешения всех зависимостей контейнер выдаст исключение PHPBindingResolutionException.

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

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

PHP
App:bind'SessionStorage''MysqlSessionStorage' );

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

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

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

Примечание: Если вы регистрируете распознаватель для переданного типа, метод PHPresolvingAny тоже будет вызван, но возвращёно значение из метода PHPbind.

Последние советы

  • Где хранить привязки:

Если у вас маленькое приложение, вы можете использовать ваш global/start.php, но если ваш проект растёт, вы должны использовать сервис-провайдер.

  • Тестирование:

Если вы просто тестируете, вам надо попробовать использовать php artisan tinker, он очень мощный и может улучшить ваше тестирование в Laravel.

  • Reflection API:

PHP Reflection API — очень мощный инструмент, и если вы хотите понять Laravel IoC, вам необходимо ознакомиться с Reflection API, рекомендуем прочитать это руководство.

Заключение

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

Как вы считаете, полезен ли этот материал? Да Нет

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

Разметка: ? ?

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