想象一下,我们有一个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容器)是否支持特定参数的这种“覆盖”,但到目前为止我找不到任何东西。
我想知道的是:
答案 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和其他框架所做的事情)