Современные фреймворки PHP (такие как Zend, Symfony, Phalcon) используют контейнер DI, и вы просто передаете его для доступа ко всем функциям фреймворка. Мне интересно, должен ли я / мог бы использовать контейнер DI в своем бизнес-коде. Допустим, мне нужно использовать объект доступа к базе данных и объект почтовой программы, и они уже находятся в DI, потому что их использует инфраструктура. Могу ли я просто передать контейнер DI при создании бизнес-класса?
Например, у меня есть класс, который работает с пользователями в базе данных, вы можете назвать его классом моей модели пользователя. Прямо сейчас я просто передаю DI-контейнер конструктору класса модели, когда создаю его экземпляр в контроллере, и это хорошо и просто. Просто бросьте все в контейнер DI и готово.
Но я собираюсь разработать API, который также будет использовать этот класс пользовательской модели. Так как он ожидает контейнер DI, мне нужно было бы знать заранее, каковы зависимости модели, и инициализировать контейнер DI с правильными. Раньше я просто передавал каждую зависимость в качестве параметра в конструкторе, но с IoC мне нужно было знать, не глядя на параметры, каковы зависимости класса и какое имя используется для доступа к каждой зависимости. Например, мне нужно знать, что в контейнере DI должен быть объект PDO, идентифицируемый как ‘db’. Это хороший подход для бизнес / библиотечного кода?
Я, наверное, здесь смешиваю термины, но надеюсь, вы поняли идею.
Спасибо!
Неважно, какой код вы разрабатываете, будь то бизнес-логика или логика фреймворка (или другой вид логики), весь смысл здесь в том, как вы справляетесь с зависимостями классов.
Семестр бизнес логика само по себе очень абстрактно. Вы можете представить бизнес-логику с помощью одного класса (a.k.a доменный объект) или вы можете представить бизнес-логику как слой.
При разработке любого приложения вы должны помнить, что база данных может быть изменена (или вы можете перейти на решение NoSQL в будущем). И когда / если вы делаете, то $pdo
больше не зависимость. Если у вас логика хранения полностью отделена от бизнес-логики, вам будет достаточно легко заменить механизм хранения. В противном случае вы переписываете много вещей, когда меняете его.
Правильно спроектированная архитектура способствует отделению логики хранилища от логики приложения. Эти шаблоны установлены в качестве лучшей практики: Data Mapper или Table Gateway
namespace Storage\MySQL;
use PDO;
abstract class AbstractMapper
{
protected $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
}
class UserMapper extends AbstractMapper
{
private $table = 'cms_users';
public function fetchById($id)
{
$query = sprintf('SELECT * FROM `%s` WHERE `id` =:id', $this->table);
$stmt = $this->pdo->prepare($query);
$stmt->execute(array(
':id' => $id
));
return $stmt->fetch();
}
// the rest methods that abstract table queries
}
Так что в данном случае для текущего движка хранения $pdo
это базовая зависимость, и это не зависимость фреймворка или разрабатываемого вами приложения. Следующая проблема, которую вы должны решить здесь, состоит в том, как автоматизировать этот процесс прохождения $pdo
зависимость от картографов. Есть только одно решение, которым вы можете воспользоваться — заводская модель
$pdo = new PDO();
$mapperFactory = new App\Storage\MySQL\Factory($pdo);
$mapperFactory->build('UserMapper'); // will return UserMapper instance with injected $pdo dependency
Теперь давайте посмотрим на очевидные преимущества:
Прежде всего, это удобочитаемость — любой, кто увидит код, поймет, что Mapper используется для абстрагирования доступа к таблице. Во-вторых, вы можете легко заменить механизм хранения (если вы планируете в будущем выполнить миграцию и добавить поддержку нескольких баз данных)
$mongo = new Mongo();
$mapperFactory = new App\Storage\Mongo\Factory($mongo);
$mapperFactory->build('UserMapper'); // will return UserMapper instance with injected $mongo dependency
Примечание. Все средства отображения для различных механизмов хранения должны реализовывать один интерфейс (применение API).
Когда дело доходит до сети, мы в основном делаем следующее:
Поэтому, когда вы реализуете модель как класс, вы в конечном итоге пишете логику проверки и хранения в одном и том же классе, следовательно, тесно связывая и нарушая SRP.
Один из таких распространенных примеров вы можете увидеть в Yii Framework
Подходящей моделью должна быть папка классов, содержащая логику приложения (см. ZF2 или SF2).
Следует ли использовать DI-контейнер при разработке кода? Хорошо, давайте посмотрим на это классический пример кода:
class House
{
public function __construct($diContainer)
{
//Let's assume that door and window have their own dependencies
// So no, it's not a Service Locator in this case
$this->door = $diContainer->getDoor();
$this->window = $diContainer->getWindow();
$this->floor = $diContainer->getFloor();
}
}
$house = new House($di)
В этом случае вы говорите, что ваш House
зависит от DiC
а не явно на дверях, окнах и полу. Но подождите House
действительно зависит от DiC? Конечно, это не так.
И когда вы начнете тестировать свой класс, вам также придется предоставить подготовленный DiC (что совершенно не имеет значения)
Обычно люди используют контейнеры Di, чтобы избежать инъекций все время. Но с другой стороны, это требует некоторой оперативной памяти и некоторого времени для анализа, так как большинство DI-контейнеров основаны на конфигурации.
Хорошая архитектура может быть свободна от любого контейнера Di, поскольку она использует преимущества фабрики и SRP.
Поэтому хорошие компоненты приложения не должны содержать никаких сервисных локаторов и контейнеров Di.
Других решений пока нет …