Инъекция зависимостей PHP

Я пытаюсь задуматься над Injection Dependency, и я это понимаю по большей части.

Однако скажите, если по какой-то причине один из моих классов зависел от нескольких классов, вместо того, чтобы передавать все это в этот один класс в конструкторе, есть ли лучший, более разумный метод?

Я слышал о контейнерах DI, так ли я решил решить эту проблему? С чего начать с этого решения? Передаю ли я зависимости от моего DIC, а затем передаю это классу, который нуждается в этих зависимостях?

Любая помощь, которая указывала бы мне в правильном направлении, была бы фантастической.

Ответ 1

Если у вас есть несколько зависимостей для решения, тогда да, контейнер DI может быть решением.

Контейнер DI может быть объектом или массивом, построенным из различного зависимого объекта, который вам нужен, который передается конструктору и распаковывается.

Предположим, вам нужен объект конфигурации, соединение с базой данных и объект информации о клиенте, переданный каждому из ваших классов. Вы можете создать массив, который их удерживает:

// Assume each is created or accessed as a singleton, however needed...
// This may be created globally at the top of your script, and passed into each newly
// instantiated class
$di_container = array(
  'config' = new Config(),
  'db' = new DB($user, $pass, $db, $whatever),
  'client' = new ClientInfo($clientid)
);

И ваши конструкторы классов принимают контейнер DI как параметр:

class SomeClass {
  private $config;
  private $db;
  private $client;

  public function __construct(&$di_container) {
    $this->config = $di_container['config'];
    $this->db = $di_container['db'];
    $this->client = $di_container['client'];
  }
}

Вместо массива, как я сделал выше (что просто), вы также можете создать контейнер DI как сам класс и создать экземпляр его с помощью классов компонентов, вводимых в него отдельно. Одно преимущество использования объекта вместо массива состоит в том, что по умолчанию он будет передаваться по ссылке в классы, используя его, тогда как массив передается по значению (хотя объекты внутри массива все еще являются ссылками).

Изменить

Есть несколько способов, в которых объект более гибкий, чем массив, хотя более сложный код первоначально.

Объект-контейнер также может создавать/создавать экземпляры содержащихся классов в его конструкторе (а не создавать их снаружи и передавать их). Это может сэкономить вам некоторое кодирование на каждом script, который его использует, поскольку вам нужно всего лишь создать экземпляр одного объекта (который сам создает несколько других).

Class DIContainer {
  public $config;
  public $db;
  public $client;

  // The DI container can build its own member objects
  public function __construct($params....) {
    $this->config = new Config();

    // These vars might be passed in the constructor, or could be constants, or something else
    $this->db = new DB($user, $pass, $db, $whatever);

    // Same here -  the var may come from the constructor, $_SESSION, or somewhere else
    $this->client = new ClientInfo($clientid);
  }
}

Ответ 2

Инъекция зависимостей! == DIC

Люди действительно должны перестать смешивать их. Dependency Injection - это идея, которая вытекает из Принцип инверсии зависимостей.

DIC - это "волшебное излечение", которое promises позволяет вам использовать инъекцию зависимостей, но в PHP обычно реализуется путем нарушения любого другого принципа объектно-ориентированного программирования. Худшие реализации, как правило, также привязывают его к глобальному состоянию через статические Registry или Singleton.

В любом случае, если ваш класс зависит от слишком многих других классов, то в целом это означает недостаток дизайна в самом классе. У вас в основном есть класс со слишком многими причинами для изменения, таким образом, нарушая принцип единой ответственности.

В этом случае контейнер инъекции зависимостей только скроет проблемы с подстилающим дизайном.

Если вы хотите узнать больше о Injection Dependency, я бы порекомендовал вам посмотреть "Чистые коды разговоров" на youtube:

Ответ 3

Я написал статью об этой проблеме. Идея заключается в использовании комбинации абстрактных factory и инъекций зависимостей для достижения прозрачной зависимости зависимостей (возможных вложенных) зависимостей. Я буду копировать/вставлять здесь основные фрагменты кода:

namespace Gica\Interfaces\Dependency;

interface AbstractFactory
{
    public function createObject($objectClass, $constructorArguments = []);
}

Абстрактная реализация factory:

namespace Gica\Dependency;

class AbstractFactory implements \Gica\Interfaces\Dependency\AbstractFactory, \Gica\Interfaces\Dependency\WithDependencyInjector
{
    use WithDependencyInjector;

    /**
     * @param string $objectClass
     * @param array $constructorArguments
     * @return object instanceof $class
     */
    public function createObject($objectClass, $constructorArguments =     [])
    {
        $instance = new $objectClass(...$constructorArguments);

        $this->getDependencyInjector()->resolveDependencies($instance);

        return $instance;
    }
}

Инжектор зависимостей таков:   пространство имен Gica\Dependency;

class DependencyInjector implements \Gica\Interfaces\Dependency\DependencyInjector
{
    use \Gica\Traits\WithDependencyContainer;

    public function resolveDependencies($instance)
    {
        $sm = $this->getDependencyInjectionContainer();

        if ($instance instanceof \Gica\Interfaces\WithAuthenticator) {
            $instance->setAuthenticator($sm->get(\Gica\Interfaces\Authentication\Authenticator::class));
        }
        if ($instance instanceof \Gica\Interfaces\WithPdo) {
            $instance->setPdo($sm->get(\Gica\SqlQuery\Connection::class));
        }

        if ($instance instanceof \Gica\Interfaces\Dependency\WithAbstractFactory) {
            $instance->setAbstractFactory($sm->get(\Gica\Interfaces\Dependency\AbstractFactory::class));
        }
        //... all the dependency declaring interfaces go below
    }
}

контейнер зависимостей является стандартным. Код клиента может выглядеть примерно так:

$abstractFactory = $container->get(\Gica\Interfaces\Dependency\AbstractFactory::class);

$someHelper = $abstractFactory->createObject(\Web\Helper\SomeHelper::class);

echo $someHelper->helpAction();

Обратите внимание, что зависимости скрыты, и мы можем сосредоточиться на основном бизнесе. Мой клиентский код не заботится или знает, что $someHelper нужен Authenticator или для того, чтобы helpAction нужен SomeObject для выполнения своей работы;

В фоновом режиме происходит много вещей, обнаруживается, разрешается и вводится множество зависимостей. Обратите внимание, что я не использую оператор new для создания $someObject. Ответственность фактического создания объекта передается AbstractFactory

P.S. Gica - мой ник:)

Ответ 4

Я рекомендую вам использовать Singltones или Mutlitones. В этих случаях вы всегда сможете получать объекты с помощью методов статического класса.

Другой способ (не удалось найти правильное имя шаблона, но может быть Registry) - использовать один глобальный статический объект для хранения экземпляров нескольких объектов. Например. (упрощенный код без каких-либо проверок):

class Registry {
    private static $instances = array();

    public static function add($k, $v) {
        $this->instances[$k] = $v;
    }

    public static function get($k) {
        return $this->instances[$k];
    }
}

class MyClass {
    public function __construct() {
        Registry::add('myclass', $this);
    }
}