Правильный способ пропускать контейнер IoC

Я опускаю голову вокруг DI и IoC; используя Pimple. Допустим, что у меня установлен IoC в начале потока выполнения

$container = new Injection\Container();

$container['config'] = function ($c) {
    return new Config($c['loader']);
};

$container['request'] = function ($c) {
    return new Request($c['config']);
};

...

И класс маршрутизатора, который call_user_func_array

//$class = 'Dog', $method = 'woof', $this->args = ['foo', 'bar']
call_user_func_array(array(new $class, $method), $this->args);

Итак, новый объект создается, не зная о IoC, но все же я хотел бы повторно использовать некоторые из определенных служб.

class Dog
{
    public function woof($var1, $var2)
    {
        //$request = IoC service here
    }
}

Мой вопрос:

  • Каким будет правильный способ передать IoC классу (статический, кажется, злой...) или
  • Нужно ли даже передавать контейнер вокруг и другие методы/понятия существуют?

Прочитайте некоторые интересные статьи, однако не смог понять это.

Обновление

Злой способ, которым я это сделал, определял другую службу, которая сохраняет IoC в статическом свойстве

$container['services'] = function ($c) {
    return Services::create($c); //make the service
};

$container['services']; //call the service

и получить доступ к нему позже в

class Dog
{
    public function woof($var1, $var2)
    {
        $services = new Services();

        $request = $services['request']; //retrieving the request service
    }
}

Обновление 2

Решено использовать наименее вредный путь

//passing the container here
call_user_func_array(array(new $class($container), $method), $this->args);

И сохраните параметр в __constructor

public $container;

public function __construct(Injection\Container $container)
{
    $this->container = $container;
}

Ответы

Ответ 1

Есть два шаблона, которые обычно используются для IoC, и сторонники для обоих.

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

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

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

Локатор сервисов - это другой конец спектра. Обычно контейнер ставится или передается классу в качестве единственной зависимости. Возможно, было бы легче создавать классы таким образом, но позже вы заплатите цену, когда вам действительно нужно сконфигурировать контейнер DI.

С DI, зависимости класса явны, поэтому вам не нужно смотреть дальше, чем параметры конструктора. С помощью Service Locator настройка зависимостей намного сложнее. Это основная причина (на самом деле существует много причин), почему в последние годы она считается anti-pattern.

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

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

Полный пример в PHP можно увидеть здесь. Я нашел эту страницу в обсуждении проекта Pimple.

Итак, учитывая ваш пример:

class Dog
{
    public function woof($var1, $var2)
    {
        //$request = IoC service here
    }
}

Вам нужно будет добавить конструктор, чтобы принять вашу службу $request, чтобы ваш класс получил экземпляр.

class Dog
{
    protected $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function woof($var1, $var2)
    {
        //Use $request here, disregard the IoC container
        $this->request->doSomething()
    }
}

И тогда у вас будет раздел, в котором вы определяете контроллеры в своем корне. Здесь вводятся зависимости.

$container = new Injection\Container();

$container['config'] = function ($c) {
    return new Config($c['loader']);
};

$container['request'] = function ($c) {
    return new Request($c['config']);
};

$container['dog'] = $container->factory(function ($c) {
    return new Dog($c['request']);
});

$container['user_controller'] = $container->share(function ($container) {
    return new UserController(
        $container['dog'] //,
        // $container['someOtherDependency']
    );
});

Как вы можете видеть, используя этот подход, ваш класс Dog полностью не знает контейнер DI.

Ответ 2

Вот пример моего обмана с помощью factory, singleton, инжектора зависимостей, локатора сервисов, интерфейса инжектора и других шаблонов.

config.php

return [
    'services' => [
        'request' => 'Request',
        'view' => 'View',
        'db' => function() {
            return new DB( 'host', 'username', 'password', 'dbname' );
        },
        'translator' => function( $sm, $config ) {
            return new Translator( $sm->db, $config );
        },
        'model' => function() {
            return new ModelFactory( $sm );         
        }
    ],
    'interfaces' => [
        'iService' => function( $object, $sm ) {
            $object->sm = $sm;
        }
    ],
    'translator' => [
        'locale' => 'en_US',
    ]
];

контейнер обслуживания

class ServiceManager {
    private $services, $interfaces, $params;

    function __construct( $config )
    {
        foreach( $config[ 'services' ] as $name => $value )
            $this->add( $name, $value, isset( $config[ $name ] ) ? $config[ $name ] : null );

        $this->interfaces = isset( $config[ 'interfaces' ] ) ? $config[ 'interfaces' ] : null;
    }

    function add( $name, $service, $params = null )
    {
        $this->services[ $name ] = $service;
        $this->params[ $name ] = $params;
    }

    function __get( $name ) 
    {
        if ( is_string( $this->services[ $name ] ) )
            $this->services[ $name ] = new $this->services[ $name ]( $this->params[ $name ] );  

        if ( is_callable( $this->services[ $name ] ) )
            $this->services[ $name ] = $this->services[ $name ]( $this, $this->params[ $name ] );

        foreach( $this->interfaces as $interface => $value ) {
            if ( $this->services[ $name ] instanceof $interface )
                $value( $this->services[ $name ], $this );                  
        }

        return $this->services[ $name ];
    }

}

Пример

interface iService {}

class View implements iService {
    function render() {
        print_r( $this->sm );
    }
};

class DB {
    function __construct( $host, $username, $password, $dbname ) {}
};

class Translator {
    function __construct( $db, $config ) {}
};

class ModelFactory {
    function __construct( $sm ) {}
}

class App {
    function __construct() {
        $sm = new ServiceManager( include 'config.php' );
        $sm->view->render();
    }
}

new App;

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