Подтвердить что ты не робот

Понимание контейнеров IoC и инъекций зависимостей

Мое понимание:

  • Зависимость - это когда экземпляр ClassA требует экземпляр ClassB для создания нового экземпляра ClassA.
  • Внедрение зависимости - это когда ClassA передается экземпляр ClassB, либо через параметр в конструкторе ClassA, либо через функцию set ~ DependencyNameHere ~ (~ DependencyNameHere ~ $ param). (Это одна из областей, в которой я не совсем уверен).
  • Контейнер IoC - это одноэлементный класс (в каждый момент времени может быть создан только один экземпляр), где можно зарегистрировать конкретный способ создания экземпляров объектов этого класса для этого проекта. Здесь ссылка на пример того, что я пытаюсь описать вместе с определением класса для контейнера IoC, который я использую

Поэтому в этот момент я начинаю пытаться использовать контейнер IoC для более сложных сценариев. На данный момент кажется, что для использования контейнера IoC я ограничен отношением has-практически для любого класса, который я хочу создать и который имеет зависимости, которые он хочет определить в контейнере IoC. Что делать, если я хочу создать класс, который наследует класс, но только если родительский класс был создан определенным образом, он был зарегистрирован в контейнере IoC.

Например, я хочу создать дочерний класс mysqli, но хочу зарегистрировать этот класс в контейнере IoC, чтобы создавать экземпляры только с родительским классом, созданным так, как я ранее зарегистрировал в контейнере IoC. Я не могу придумать способ сделать это без дублирования кода (и, поскольку это учебный проект, я стараюсь сделать его как можно более "чистым"). Вот еще несколько примеров того, что я пытаюсь описать.

Итак, вот некоторые из моих вопросов:

  • Возможно ли то, что я пытаюсь сделать выше, не нарушая принцип ООП? Я знаю, что в c++ я мог бы использовать динамическую память и конструктор копирования для этого, но я не смог найти такую функциональность в php. (Я признаю, что у меня очень мало опыта использования любых других магических методов, кроме __construct, но из чтения и __clone, если я правильно понял, я не смог использовать его в конструкторе для создания экземпляра дочернего класса как клона экземпляр родительского класса).
  • Куда должны идти все мои определения классов зависимостей по отношению к IoC? (Должен ли мой IoC.php просто иметь набор require_once ('dependencyClassDefinition.php') вверху? Моя внутренняя реакция такова, что есть лучший способ, но я еще не нашел его)
  • В каком файле я должен регистрировать мои объекты? В настоящее время выполняются все вызовы IoC :: register() в файле IoC.php после определения класса.
  • Нужно ли регистрировать зависимость в IoC, прежде чем я зарегистрирую класс, который нуждается в этой зависимости? Поскольку я не вызываю анонимную функцию до тех пор, пока не создам экземпляр объекта, зарегистрированного в IoC, я полагаю, что нет, но это все еще вызывает беспокойство.
  • Есть ли что-то еще, что я упускаю из виду, что я должен делать или использовать? Я пытаюсь делать это по одному шагу за раз, но я также не хочу знать, что мой код будет многократно использоваться и, самое главное, что кто-то, кто ничего не знает о моем проекте, сможет прочитать его и понять.
4b9b3361

Ответ 1

Проще говоря (потому что это не проблема, ограниченная только для мира ООП), зависимость - это ситуация, когда компоненту A требуется (зависит от) компонент B, чтобы делать то, что он должен был делать. Это слово также используется для описания зависимого компонента в этом сценарии. Чтобы поместить это в ООП /PHP, рассмотрите следующий пример с обязательной аналогией автомобилей:

class Car {

    public function start() {
        $engine = new Engine();
        $engine->vroom();
    }

}

Car зависит от Engine. Engine является зависимостью Car. Этот фрагмент кода довольно плохой, потому что:

  • зависимость неявная; вы не знаете этого там, пока не проверите код Car
  • классы тесно связаны; вы не можете заменить Engine на MockEngine для целей тестирования или TurboEngine, который расширяет исходный текст без изменения Car.
  • Кажется, глупо для автомобиля, чтобы он мог построить двигатель для себя, не так ли?

Включение зависимостей - это способ решения всех этих проблем, поскольку тот факт, что Car нуждается в явном выражении Engine и явно предоставляет ему один:

class Car {

    protected $engine;

    public function __construct(Engine $engine) {
        $this->engine = $engine;
    }

    public function start() {
        $this->engine->vroom();
    }

}

$engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine
$car = new Car($engine);

Вышеприведенный пример встраивания конструктора, в котором зависимая (объект-зависимый) предоставляется зависимому (потребителю) через конструктор класса. Другой способ - выдать метод setEngine в классе Car и использовать его для ввода экземпляра Engine. Это называется инъекцией установщика и полезно в основном для зависимостей, которые должны быть заменены во время выполнения.

Любой нетривиальный проект состоит из кучи взаимозависимых компонентов, и становится легко потерять следы от того, что вводится там довольно быстро. Контейнер для инъекций - это объект, который знает, как создавать и настраивать другие объекты, знает, какова их связь с другими объектами в проекте, и делает инъекцию зависимостей для вас. Это позволяет централизовать управление всеми вашими зависимостями проекта (inter) и, что более важно, позволяет изменять/издеваться над одним или несколькими из них без необходимости редактировать кучу мест в вашем коде.

Пусть сравнивает аналогию с автомобилем и смотрит на то, что OP пытается достичь в качестве примера. Скажем, мы имеем объект Database в зависимости от объекта mysqli. Предположим, мы хотим использовать действительно примитивный контейнер контейнера индексов зависимости DIC, который предоставляет два метода: register($name, $callback), чтобы зарегистрировать способ создания объекта под заданным именем и resolve($name), чтобы получить объект от этого имени. Наша настройка контейнера будет выглядеть примерно так:

$dic = new DIC();
$dic->register('mysqli', function() {
    return new mysqli('somehost','username','password');
});
$dic->register('database', function() use($dic) {
    return new Database($dic->resolve('mysqli'));
});

Обратите внимание, что мы говорим нашему контейнеру, чтобы захватить экземпляр mysqli из себя, чтобы собрать экземпляр Database. Затем, чтобы получить экземпляр Database с его автоматической вставкой, мы просто:

$database = $dic->resolve('database');

Это суть этого. Несколько более сложный, но все же относительно простой и легкий для понимания контейнер PHP DI/IoC - Pimple. Подробнее см. Его документацию.


Что касается кода OP и вопросов:

  • Не используйте статический класс или синглтон для вашего контейнера (или для чего-нибудь еще, если на то пошло); они оба злы. Проверьте Пимл вместо.
  • Определите, хотите ли вы, чтобы ваш класс mysqliWrapper расширял mysql или зависел от него.
  • Вызывая IoC из mysqliWrapper, вы меняете одну зависимость для другой. Ваши объекты не должны знать или использовать контейнер; в противном случае это не DIC больше, чем шаблон Service Locator (анти).
  • Вам не нужно require файл класса, прежде чем регистрировать его в контейнере, так как вы не знаете, собираетесь ли вы вообще использовать объект этого класса. Сделайте все настройки вашего контейнера в одном месте. Если вы не используете автозагрузчик, вы можете require войти в анонимную функцию, зарегистрированную в контейнере.

Дополнительные ресурсы:

Ответ 2

Я бы порекомендовал: https://developmentmatt.com/building-a-php-framework-part-6-dependency-inversion-inversion-of-control-oh-my/

и следующий шаг по вышеуказанной ссылке: https://developmentmatt.com/building-a-php-framework-part-7-the-container/

Описание на обеих этих страницах вместе с указанными ссылками поможет вам освоить внедрение зависимостей и инверсию управления. Далее я изучал Symphony и Laravel и как другие проекты достигли этого.