DI-контейнер для объекта, который содержит объект с внедренной зависимостью - PullRequest
5 голосов
/ 26 мая 2011

Используя pimple в качестве моего DI-контейнера, я смело проводил рефакторинг небольших классов, чтобы полагаться на DI-инъекцию, устраняя жестко запрограммированные зависимости, которые, как я видел, могли быть легко удалены.

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

Я создал класс ContainerFactory, который подклассами подклассов, и в этом подклассе создал методы, которые просто возвращают контейнер для определенного объекта.

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

function __construct($type=null, $mode = null){

 if(isset($type)){  
    switch ($type) {
      case 'DataFactory':
         $this->buildDataFactoryContainer($mode);     
        break;
      case 'DbConnect':
         $this->buildDbConnectContainer($mode);  
        break;
     default:
        return false;
    }
  }
}

Сигнатура метода для создания объекта контейнера следующая:

public function buildDataFactoryContainer($mode=null)

Идея состоит в том, чтоЯ могу установить $ mode для проверки при вызове этого контейнера и сделать так, чтобы он загружал тестовые значения вместо реальных настроек времени выполнения.Я хотел избежать написания отдельных контейнерных классов для тестирования, и я подумал, что это не простой код, связанный с тестированием.

Вместо этого я мог бы создать подкласс ContainerFactory, то есть: ContainerFactoryTesting extends ContainerFactory и переопределить его вместо того, чтобы смешивать тестовый код с кодом приложения и сигнатуры методов загромождения с $ mode = null, но это не главное в этом посте.Двигаясь дальше, чтобы создать контейнер для определенного объекта, я просто делаю это:

 // returns container with DataFactory dependencies, holds $db and $logger objects.
 $dataFactoryContainer = new ContainerFactory('DataFactory');

// returns container with test settings.
$dataFactoryTestContainer = new ContainerFactory('DataFactory','test');

// returns container with DbConnect dependencies, holds dbconfig and $logger objects.
$dbConnectContainer = new ContainerFactory('DbConnect');

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

Глядя на вышесказанное, DataFactory содержит объект $ db, который содержит соединения с базой данных.Сейчас я делаю рефакторинг этого dbclass, чтобы удалить его зависимости от объекта $ registry, но как мне создать $ dataFactoryContainer, когда я добавлю объект $ db, которому нужен $ dbConnectContainer?

Например, в контейнере datafactory я добавляюэкземпляр dbconnect, но ИТ-отделу теперь понадобится передать ему контейнер ...

Я понимаю, что мой английский не настолько хорош, и надеюсь, что объяснил достаточно хорошо, чтобы его понимал товарищ SO.

У меня двоякий вопрос, как вы, ребята, просто обрабатываете создание объектов для зависимостей, которые сами содержат зависимости?

И ... как вы разделяете конфигурацию контейнера для создания объектов для тестирования?Цели?

Как всегда, любые комментарии или ссылки на соответствующие сообщения приветствуются.

1 Ответ

6 голосов
/ 26 мая 2011

Вы не должны создавать отдельные контейнеры для всего, вместо этого используйте один контейнер. Вы можете создать контейнер «Расширение», который в основном просто устанавливает службы в вашем контейнере.

Вот обширный пример моей предложенной установки. Когда у вас есть один контейнер, рекурсивное разрешение зависимостей тривиально. Как видите, security.authentication_provider зависит от db, что зависит от db.config.

Из-за лени замыканий легко определить службы, а затем определить их конфигурацию позже. Кроме того, их также легко переопределить, как вы можете видеть с помощью TestExtension.

Можно было бы разделить определения служб на несколько отдельных расширений, чтобы сделать их более пригодными для повторного использования. Это в значительной степени то, что делает Silex microramework (используется прыщ).

Надеюсь, это ответит на ваши вопросы.

ExtensionInterface

class ExtensionInterface
{
    function register(Pimple $container);
}

AppExtension

class AppExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = $container->share(function ($container) {
            return new Mailer($container['mailer.config']);
        });

        $container['db'] = $container->share(function ($container) {
            return new DatabaseConnection($container['db.config']);
        });

        $container['security.authentication_provider'] = $container->share(function ($container) {
            return new DatabaseAuthenticationProvider($container['db']);
        });
    }
}

ConfigExtension

class ConfigExtension
{
    private $config;

    public function __construct($configFile)
    {
        $this->config = json_decode(file_get_contents($configFile), true);
    }

    public function register(Pimple $container)
    {
        foreach ($this->config as $name => $value) {
            $container[$name] = $container->protect($value);
        }
    }
}

config.json

{
    "mailer.config": {
        "username": "something",
        "password": "secret",
        "method":   "smtp"
    },
    "db.config": {
        "username": "root",
        "password": "secret"
    }
}

TestExtension

class TestExtension
{
    public function register(Pimple $container)
    {
        $container['mailer'] = function () {
            return new MockMailer();
        };
    }
}

ContainerFactory

class ContainerFactory
{
    public function create($configDir)
    {
        $container = new Pimple();

        $extension = new AppExtension();
        $extension->register($container);

        $extension = new ConfigExtension($configDir.'/config.json');
        $extension->register($container);

        return $container;
    }
}

Применение

$factory = new ContainerFactory();
$container = $factory->create();

Тест Bootstrap

$factory = new ContainerFactory();
$container = $factory->create();

$extension = new TestExtension();
$extension->register($container);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...