Как разработчики мы всегда пытаемся найти новые способы написания хорошо продуманного и чистого кода, применяя новые стили, используя шаблоны проектирования, и пробуя использовать новые надёжные фреймворки. В этой статье мы исследуем шаблон проектирования внедрения зависимостей через компонент Laravel IoC и посмотрим, как он может улучшить наше проектирование.
Внедрение зависимостей
Внедрение зависимостей — термин, придуманный Мартином Фавлером, и означающий внедрение компонентов в ваше приложение. Как сказал Вард Канингхэм:
Внедрение зависимостей является ключевым элементом гибкой архитектуры.
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
без его прямого использования.
При внедрении компонентов в ваш класс вы можете использовать один из трёх вариантов:
Внедрение конструктора
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;
}
}
Когда класс реализует наш интерфейс, мы определяем метод PHPinjectConnection
для разрешения зависимости.
Преимущества
Теперь при тестировании нашего класса мы можем подделать класс зависимости и передать его как параметр. Каждый класс должен быть сосредоточен на конкретной задаче и не должен заботиться о разрешении своих зависимостей. Таким образом у вас будет нацеленное на решение задач, поддерживаемое приложение.
Если вы захотите узнать больше о внедрении зависимостей, Алехандро Гервасио рассмотрел его широко и профессионально в этой серии статей, поэтому не забудьте прочесть их. А что насчёт IoC? IoC (Inversion of control — Инверсия управления) не обязательна для использования внедрения зависимостей, но она может помочь вам эффективно управлять вашими зависимостями.
Inversion of control — Инверсия управления
IoC — простой компонент, упрощающий разрешение зависимостей. Вы описываете свои объекты в контейнере, и каждый раз, когда вы используете класс, зависимости внедряются автоматически.
Laravel IoC
У Laravel IoC какой-то особенный способ разрешения зависимостей, когда вы запрашиваете объект:
Мы будем использовать простой пример, который будем улучшать на протяжении этой статьи.
У класса PHPSimpleAuth
есть зависимость PHPFileSessionStorage
, поэтому наш код может быть таким:
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();
Это классический способ, давайте начнём с внедрения конструктора.
class SimpleAuth{
protected $session;
public function __construct( FileSessionStorage $session ){
$this->session = $session;
}
}
$auth = new SimpleAuth( new FileSessionStorage() );
Теперь я хочу использовать Laravel IoC для управления этим всем.
Так как класс PHPApplication
наследует класс PHPContainer
, вы всегда можете получать доступ к контейнеру через фасад PHPApp
.
App::bind( 'FileSessionStorage', function(){
return new FileSessionStorage;
});
Первый параметр для метода PHPbind
— уникальный идентификатор для привязки к контейнеру, второй — функция обратного вызова, которая будет выполняться каждый раз, когда мы используем класс PHPFileSessionStorage
, мы также можем передать строку, представляющую имя класса, как будет видно дальше.
Примечание: Если вы исследуете пакеты Laravel, то увидите, что иногда привязки как-бы сгруппированы (PHPview
, PHPview.finder
,..).
Предположим, что мы хотим переключить хранилище нашей сессии на MySQL, наш класс должен быть похож на:
class MysqlSessionStorage{
public function __construct(){
//...
}
public function get($key){
// сделать что-то
}
public function set( $key, $value ){
// сделать что-то
}
}
Теперь, когда мы изменили зависимость, нам надо изменить конструктор PHPSimpleAuth
и привязать новый объект к контейнеру!
Модули высокого уровня не должны зависеть от модулей низкого уровня. И те и другие должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Роберт С. Мартин
Наш класс PHPSimpleAuth
не должен заботиться о том, как работает наше хранилище, вместо этого он должен просто пользоваться его услугами.
Поэтому мы можем абстрагировать реализацию нашего хранилища:
interface SessionStorage{
public function get( $key );
public function set( $key, $value );
}
Таким образом, мы можем просто реализовать и запросить экземпляр интерфейса PHPSessionStorage
:
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;
}
}
Если мы попытаемся использовать класс PHPSimpleAuth
через контейнер, используя PHPApp::make('SimpleAuth')
, то после попытки использовать класс из привязок, отката к методу отражения и разрешения всех зависимостей контейнер выдаст исключение PHPBindingResolutionException
.
Uncaught exception 'Illuminate\Container\BindingResolutionException' with message 'Target [SessionStorage] is not instantiable.'
Контейнер пытается создать экземпляр интерфейса. Мы можем исправить это путём создания конкретной привязки для нашего интерфейса.
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 в целом? Дайте нам знать!