3

想象一下,我们有一个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传递给的对象RegistrationController是一个特定的实例,一个我刚刚创建的实例。

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

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

我想知道的是:

  • 那里有可以做到这一点的 DI 容器吗?
  • 是否有另一种方法可以使类保持干净(我不想使用注释或其他任何会添加我用作依赖项的容器的方法)?
4

1 回答 1

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 以来,他们不赞成在容器中包含请求: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 和其他框架所做的)

于 2014-05-02T08:27:38.607 回答