如何使用特定实例覆盖DI容器依赖项?

时间:2014-05-01 13:58:03

标签: php dependency-injection php-di

想象一下,我们有一个Request对象和一个Controller对象。 Controller对象由Request对象构成,如下所示:

abstract class Controller {

    public $request;

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

}

如您所见,这是一个抽象类,因此实际上将构造Controller的子类。让我们假设代码是这样的:

// Instantiate the request.
$request = new Request($params); 

// Instantiate the registration controller.
$controller = new RegistrationController($request);

现在假设我们向RegistrationController添加依赖项,如下所示:

class RegistrationController extends Controller {

    private $user_repo;

    public function __construct(Request $request, UserRepo $user_repo)
    {
        parent::__construct($request);

        $this->user_repo = $user_repo;
    }

}

此时,我想要做的是引入一个依赖注入容器,通过构造函数自动注入依赖项。为此,我一直在使用PHP-DI。通常,这会是这样的:

// Instantiate the registration controller.
$controller = $container->get('RegistrationController');

然后,这将使用RegistrationController的实例和Request的实例来实例化UserRepo。由于反射,它知道用这些对象构造,但如果我想,我也可以通过配置文件覆盖它。

问题是Request对象采用动态参数。我需要将传递给Request的{​​{1}}对象作为特定实例,我刚刚创建了一个实例。

我基本上希望能够说:“给我一个这个类的实例,并注入所有依赖项,但是对于一个特定的参数,传入这个特定的实例”。

我已经看过PHP-DI(和一些其他DI容器)是否支持特定参数的这种“覆盖”,但到目前为止我找不到任何东西。

我想知道的是:

  • 那里有DI容器吗?
  • 是否有一种替代方法可以让类保持干净(我不想使用注释或其他任何可以添加我用作依赖的容器的方法)?

1 个答案:

答案 0 :(得分:2)

PHP-DI作者。

所以有两件事,首先我会回答你的问题:

PHP-DI的容器提供了一个make方法,你可以这样使用:

$request = new Request($myParameters);

$controller = $container->make('RegistrationController', array(
    'request' => $request
));

正如您所见,此make方法与get相同,但它始终会创建一个新实例(这是您想要的,因为您可能不想重复使用现有的控制器实例),它将采取你给它的额外参数。这是工厂的行为,具有容器的好处,可以找到您未提供的其余参数。

这就是我在这里使用的内容。你也可以这样做:

$request = new Request($myParameters);
$container->set('Request', $request);
$controller = $container->get('RegistrationController');

但那不太干净,因为它会在容器中设置请求,这很糟糕(如下所述)。


现在第二件事是请求对象实际上不是服务,它是一个“值对象”。容器通常应该只包含服务对象,即无状态的对象。

这样做的原因是想象你在同一个进程中有多个请求(例如你做“子请求”,或者你有一个处理多个请求的工作进程等等):你的服务将全部搞砸因为他们会注入请求并且请求对象可能会改变。

Symfony就是这么做的,并意识到这是一个错误。从Symfony 2.4开始,他们已弃用容器中的Request:http://symfony.com/blog/new-in-symfony-2-4-the-request-stack

无论如何,我建议你做的不是在容器中有Request对象,而是使用我向你展示的make方法。

或者,甚至更好,我会这样做:

class RegistrationController extends Controller {

    private $user_repo;

    public function __construct(UserRepo $user_repo)
    {
        $this->user_repo = $user_repo;
    }

    public function userListAction(Request $request)
    {
        // ...
    }

}


// in the front controller
$controller = $container->make('RegistrationController');

// This is what the router should do:
$action = ... // e.g. 'userListAction'
$controller->$action(new Request($myParameters));

(这就是Symfony和其他框架所做的事情)