Comments 32
UFO landed and left these words here
Это статья о создании такой архитектуры кода, чтобы потом этот код легче было адаптировать под изменения или масштабировать горизонтально. Что очень полезно при разработке ПО.
А какая связь между горизонтальным масшабированием и IoC?
Наиболее правильный подход это задавать зависимости в конфигурационном файле. А у вас по сути, чтобы изменить зависимость, все равно приходится по коду бегать и искать что поменять. И фабрика (она же контейнер) тогда в этом случае подходит очень хорошо.
Вот простой контейнер: bitbucket.org/stp008/customioc/src.
И чем этот подход «правильный»?

(а чтобы не надо было бегать по коду, нужно зависимости описывать строго в одном месте — composition root)
Дополню — иными словами, разделять конструирование и использование, т.е. соблюдать SRP.
Тем, что в больших крупных компаниях существуют специальные люди, называемые инженерами по настройке и конфигурированию систем. И исходя из названия их должности они занимаются конфигурацией и поддержкой этих систем. И они очень часто не знают хорошо ЯП. И еще при использовании конфига из файла отпадает необходимость в перекомпиляции кода. Для больших систем этот процесс может занимать очень много времени. Я понимаю, что php эта проблема никогда не коснется ввиду специфики использования этого языка, но тема вашего поста это рассказ про инверсию контроля, а не про особенности ее использования на php. И инверсия контроля впервые изначально появилась в больших энтерпрайз приложениях, потому что там особенно остро стояла проблема зависимостей и конфигурирования.
описание зависимостей в конфигурационном файле не является целью инверсии управления

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

как они будут конфигурироваться — конфиг-файл, параметры из администраторской панели, данные с какого-либо устройства, да что угодно — это специфика приложения
Я написал как правильнее будет задавать зависимости. Меня спросили почему и я ответил. Про цели инверсии вообще ни слова не было сказано. Вы же статью написали с целью помочь людям разобраться. Так и показывайте более правильные реализации. Вы же взяли не конкретное уже реализованное приложение, а написали свой код для демонстрации принципов. А конфигурация параметров в вашем случае происходит прямо в коде. Поэтому я и написал как более правильно будет сделать и объяснил зачем. Ваш код не использует конф файла, панель и не сканирует среду на поиск файлов с совпадающим с параметрами названием.
Вы про devops не слышали? Это к разговору о «специальных людях».

А вообще, то, что вы озвучиваете — это типичная позиция «давайте мы сделаем конфигурируемую систему, в которой все будет делаться через конфиг». Позиция хорошая, но в реальности выживающая исключительно редко, поскольку все комбинации зависимостей, которые можно указать в конфиге, нужно тестировать в разработке. Дело это весьма трудозатратное, а выигрыш от него, скажем так… не очень большой. Поэтому в реальности в конфигурационном файле задаются только те зависимости, которые в системе реально есть смысл конфигурировать (например, адаптер БД или конкретный механизм логирования), а не все, и делается это чаще всего не через конфигурацию контейнера как такового, а через специализированную конфигурацию приложения.
при описании сервис локатора я писал
Объекты в Service Locator могут быть добавлены напрямую, через конфигурационный файл, да и вообще любым удобным программисту способом.

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

а что, если у Вас предусмотрена возможность выбора сервиса в администраторской панели приложения? Например, выбор payment processor. Тут уже конфигурационный файл не поможет, а зависимость в контейнере конфигурируется на основе данных в БД

но как я сказал, это уже специфика приложений, и имеет меньшее отношение к инверсии управления
Как по мне то тру IoC контейнеры есть только в PHP DI и Magento2. Все остольное это сервис локатор сервис локаторов ;)
с Magento честно говоря не работал, но PHP DI действительно отличный контейнер, который удобно добавлять в существующий проект либо при написании проекта на фреймворке, в котором проблема внедрения зависимости не решается из коробки.
в Symfony 2 мне прекрасно хватает их коробочного решения :)
Кстати, есть довольно минималистичный контейнер для зависимостей в меньше чем 10 строчек.

class DI
{
protected $storage = array();

function __set($key, $value)
{
$this->storage[$key] = $value;
}

function __get($key)
{
return $this->storage[$key]($this);
}
}

Спойлер не работает :(
10 строк это замечательно, но отсутствие какого-либо исключения при обращении к несуществующему сервису не очень-то хорошо.
насколько я понял в качестве $value должна передаваться анонимная функция, котораю будет возвращать экземпляр сервиса, верно?

и спасибо за уточнение по Service Locator, действительно добавил лишню точку с запятой после private function __construct(){}
Спасибо за статью. Эта тема действительно много раз поднималась и все равно радует, когда люди несут свет в наше разношерстное PHP комьюнити.
Только одно небольшое «фэ»:
set-метод добавляется из интерфейса
— скорей интерфейс обязует класс реализовать метод.
Вы забыли самый популярный подход — через глобальные переменные.
Спасибо за статью, очень интересно и актуально будет всегда. Мне нравится ещё использовать для этого observer, странно что его не упомянули. В этом случае в теле класса может вообще не быть упоминания о других классах и их методах, а сделать это в одном связующем объекте. Тогда все связи также будут в одном месте.
наверное все таки правильнее сказать не observer, а Publish–subscribe pattern. Создаем класс Event, и будем использовать его для отправки сообщений из одного метода класса1 и подписки на него другого метода класса2. Код тяжело отлаживать, но зависимость минимальная.
Во-первых, pub-sub — это не IoC (а еще точнее, можно сделать pub-sub с IoC и без него).
А ао-вторых, вы просто заменяете прямые зависимости на косвенные. А зачем? Снижение уровня связности — не самоцель же.
я соглашусь с Вами, что такой вариант сделает 2 модуля действительно не зависимыми друг от друга, но как Вы сами сказали — отлаживать код тяжело, да и понятность кода резко упадёт.

я бы observer (не считая классического случая использования) использовал в случае реализации Event Sourcing — тогда действительно это имело бы место и смотрелось гармонично, остальные варианты мне кажется не очень себя оправдывают
Еще бы PHPStorm научился грамотно определять все подключенные компоненты в IoC.
Для того же Yii2 приходится хаком класс Yii переопределять
для Yii не смотрел честно говоря, но для Symfony 2 есть плагин, который распознает все сервисы, прописанные в конфигах, даёт ьыстрый способ навигации по сервисам и естественно type hinting отлично работает.
думаю в ближайшее время, что-нибудь такое должно и для Yii 2 появится
На мой взгяд, крайне неудачный пример Service Locator-а.
$orderModel->setRepository(new MySQLOrderRepository());
Создаёт инстанс класса MySQLOrderRepository сразу. Исользуя lazy-loading используя лямбду будет более экономичным решением, если учесть, что в проекте таких вызовов могут быть тонны. Всё равно каждый раз надо грузить в сценарий лишь используемые классы.
Примера ради:
$orderModel->setRepository(function () { return new MySQLOrderRepository()});

Далее не буду описывать описывать механизмы проверки, загружен ли уже сервис и т.д.
Думаю, алгоритм понятен.
Скажите пожалуйста, разве приведенное в начале статьи определение не является определением Dependency Inversion Principle?
Only those users with full accounts are able to leave comments. Log in, please.