Частичная инъекция зависимостей

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

Чтобы создать экземпляр службы с фиктивным источником данных при тестировании, конструктор службы ожидает источник данных. То же самое относится и к контроллеру, конструктор которого ожидает службы.

При создании контроллера я также хочу передать ему объект запроса, потому что я бы предпочел это

new Controller(request, service).action_name

к этому

new Controller(service).action_name(request)

Добиться этого без использования какого-либо контейнера для внедрения зависимостей очень просто.

Чего я не понимаю, так это того, как это сделать с помощью php-di

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

ОБНОВЛЕНИЕ 1

Это мой ApplicationController

namespace DEC;


class ApplicationController {

    private $service;
    private $request;

    public function __construct(Foo $service, $request) {
        $this->service= $service;
        $this->request = $request;
    }


    public function index() {
        $out = $this->service->foo();
        $out .= $this->request->method();
        return $out;
    }

}

Фу следует

namespace DEC;

class Foo {

    public function __construct() {
    }

    public function foo() {
        return "FOO";
    }
}

Это мой запрос

namespace DEC;

class Foo {

    public function __construct() {
    }

    public function foo() {
        return "FOO";
    }
}

И это моя попытка заставить DI работать так, как мне бы хотелось:

$container = ContainerBuilder::buildDevContainer();
$response = $container->call([ApplicationController::class, 'index'], [
            'request' => new Request('GET')
]);
echo $response;

Это ошибка, которую я получаю:

Entry "DEC\ApplicationController" cannot be resolved: Parameter $request of __construct() has no value defined or guessable
Full definition:
Object (
    class = DEC\ApplicationController
    scope = singleton
    lazy = false
    __construct(
        $service = get(DEC\Foo)
        $request = #UNDEFINED#
    )
)

Примечание: ошибка остается прежней, если я набираю запрос и/или меняю порядок параметров в конструкторе

Глядя на ошибку, я делаю вывод, что решение ::call(), предложенное Мэтью Наполи, работает, если я создаю экземпляр контроллера только с сервисом и передаю запрос в качестве параметра для метода действия.

Означает ли это, что я не могу полагаться на контейнер для «частичной» инъекции?

ОБНОВЛЕНИЕ 2

Для решения, описанного в этом обновлении, посмотрите мой собственный ответ на вопрос


person Cec    schedule 28.04.2017    source источник
comment
Есть ли причина, по которой вы хотите использовать php-di? Он использует отражение (медленно). Вы просто собираетесь добавить в свой проект немного сторонней черной магии. Сколько печатания вы на самом деле собираетесь сэкономить? Просто мое ИМХО.   -  person E_p    schedule 28.04.2017
comment
@E_p нет потери производительности, все кешируется. Если вам не нравится отражение, вы также можете отключить его: php-di.org/doc/autowiring.html#configuration   -  person Matthieu Napoli    schedule 28.04.2017
comment
Поскольку моя кодовая база невелика, я не ожидаю измеримых проблем с производительностью. Я выбрал php-di, потому что у него почти 1000 подписчиков на GitHub и он активно поддерживается.   -  person Cec    schedule 28.04.2017
comment
github.com/silexphp/Pimple ?   -  person E_p    schedule 29.04.2017


Ответы (3)


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

$container->call([MyController::class, 'action_name'], [
    'request' => $request,
]);

Подробнее о call() читайте здесь: http://php-di.org/doc/container.html#call

person Matthieu Napoli    schedule 28.04.2017

О запросе:

Не указывайте параметр $request:

public function __construct($request, /*...*/) {}

Или введите для него RequestInterface:

public function __construct(RequestInterface $request, /*...*/) {}

В обоих случаях DIC не сможет автоматически создать экземпляр запроса. Затем вы можете ввести его самостоятельно.

О сервисе:

Типа намекните конкретно, типа "Сервис". Затем экземпляр службы будет автоматически создан DIC со всеми его зависимостями (data_source).

Или введите его с интерфейсом, например «ServiceInterface», и установите для него запись в DIC, используя псевдоним в определениях PHP. Что-то вроде этого:

return [
    'ServiceInterface' => DI\get('<NAMESPACE-TO>\Service'),
];

Видеть:

«Ограничения» PHP-DI на http://php-di.org/doc/autowiring.html#limitations

PHP-DI «Определения PHP — псевдонимы» на http://php-di.org/doc/php-definitions.html#aliases

Надеюсь, это помогло.

person Community    schedule 30.04.2017

Мне удалось сделать это, настроив свой запрос в контейнере, прежде чем запрашивать контроллер:

$container->set('DEC\Request', new Request('GET'));
$controller = $container->get('DEC\ApplicationController');
$response = $controller->index();
person Cec    schedule 05.05.2017