Как разработчики мы всегда пытаемся найти новые способы написания хорошо продуманного и чистого кода, применяя новые стили, используя шаблоны проектирования, и пробуя использовать новые надёжные фреймворки. В этой статье мы исследуем шаблон проектирования внедрения зависимостей через компонент Laravel IoC и посмотрим, как он может улучшить наше проектирование. ==Внедрение зависимостей== Внедрение зависимостей - термин, придуманный ((http://en.wikipedia.org/wiki/Martin_Fowler==Мартином Фавлером)), и означающий внедрение компонентов в ваше приложение. Как сказал ((http://en.wikipedia.org/wiki/Ward_Cunningham==Вард Канингхэм)): <[ Внедрение зависимостей является ключевым элементом гибкой архитектуры. ]> Давайте посмотрим на пример: %% 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%% для разрешения зависимости. ===Преимущества=== Теперь при тестировании нашего класса мы можем подделать класс зависимости и передать его как параметр. Каждый класс должен быть сосредоточен на конкретной задаче и не должен заботиться о разрешении своих зависимостей. Таким образом у вас будет нацеленное на решение задач, поддерживаемое приложение. Если вы захотите узнать больше о внедрении зависимостей, Алехандро Гервасио рассмотрел его широко и профессионально в ((http://www.sitepoint.com/managing-class-dependencies-1/==этой серии статей)), поэтому не забудьте прочесть их. А что насчёт IoC? IoC (Inversion of control - Инверсия управления) не обязательна для использования внедрения зависимостей, но она может помочь вам эффективно управлять вашими зависимостями. ==Inversion of control - Инверсия управления== IoC - простой компонент, упрощающий разрешение зависимостей. Вы описываете свои объекты в контейнере, и каждый раз, когда вы используете класс, зависимости внедряются автоматически. ===Laravel IoC=== У Laravel IoC какой-то особенный способ разрешения зависимостей, когда вы запрашиваете объект: {{Image /packages/proger/habravel/uploads/161-di.png, align=center}} Мы будем использовать простой пример, который будем улучшать на протяжении этой статьи. У класса %%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; } } //создание SimpleAuth $auth = new SimpleAuth(); %% Это классический способ, давайте начнём с внедрения конструктора. %% class SimpleAuth{ protected $session; public function __construct( FileSessionStorage $session ){ $this->session = $session; } } %% Теперь создаём наш объект: %% $auth = new SimpleAuth( new FileSessionStorage() ); %% Теперь я хочу использовать ((https://github.com/laravel/framework/tree/master/src/Illuminate/Container==Laravel IoC)) для управления этим всем. Так как класс %%Application%% наследует класс %%Container%%, вы всегда можете получать доступ к контейнеру через фасад %%App%%. %% App::bind( 'FileSessionStorage', function(){ return new FileSessionStorage; }); %% Первый параметр для метода %%bind%% - уникальный идентификатор для привязки к контейнеру, второй - функция обратного вызова, которая будет выполняться каждый раз, когда мы используем класс %%FileSessionStorage%%, мы также можем передать строку, представляющую имя класса, как будет видно дальше. .(alert) **Примечание:** Если вы исследуете пакеты Laravel, то увидите, что иногда привязки как-бы сгруппированы (%%view%%, %%view.finder%%,..). Предположим, что мы хотим переключить хранилище нашей сессии на MySQL, наш класс должен быть похож на: %% class MysqlSessionStorage{ public function __construct(){ //... } public function get($key){ // сделать что-то } public function set( $key, $value ){ // сделать что-то } } %% Теперь, когда мы изменили зависимость, нам надо изменить конструктор %%SimpleAuth%% и привязать новый объект к контейнеру! %%(t) Модули высокого уровня не должны зависеть от модулей низкого уровня. И те и другие должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Роберт С. Мартин %% Наш класс %%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%%. Если мы захотим переключить службу нашего хранилища, мы можем просто обновить привязки. .(alert) **Примечание:** Если вы хотите проверить, привязан ли класс к контейнеру, вы можете использовать %%App::bound('ClassName')%%, или использовать %%App::bindIf('ClassName')%% для регистрации привязки, если она еще не зарегистрирована. Laravel IoC также предлагает %%App::singleton('ClassName', 'resolver')%% для общих привязок. Вы также можете использовать %%App::instance('ClassName', 'instance')%% для создания общего экземпляра. Если контейнер не сможет использовать зависимость, он передаст исключение %%ReflectionException%%, но мы можем использовать %%App::resolvingAny(Closure)%% для использования любого переданного типа или в качестве запасного варианта. .(alert) **Примечание:** Если вы регистрируете распознаватель для переданного типа, метод %%resolvingAny%% тоже будет вызван, но возвращёно значение из метода %%bind%%. ==Последние советы== * Где хранить привязки: Если у вас маленькое приложение, вы можете использовать ваш %%(t)global/start.php%%, но если ваш проект растёт, вы должны использовать ((http://laravel.com/docs/ioc#service-providers==сервис-провайдер)). * Тестирование: Если вы просто тестируете, вам надо попробовать использовать %%(t)php artisan tinker%%, он очень мощный и может улучшить ваше тестирование в Laravel. * Reflection API: PHP Reflection API - очень мощный инструмент, и если вы хотите понять Laravel IoC, вам необходимо ознакомиться с Reflection API, рекомендуем прочитать это ((http://www.sitepoint.com/introspection-and-reflection-in-php/==руководство)). ==Заключение== Как всегда, лучший способ узнать что-то - изучить исходный код. Laravel IoC - всего один файл, и не должен отнять у вас много времени на просмотр всех его функций. Вы хотите узнать больше о Laravel IoC или IoC в целом? Дайте нам знать!