Pregunta

Imagine we have a Request object and a Controller object. The Controller object is constructed with a Request object, like so:

abstract class Controller {

    public $request;

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

}

As you can see, this is an abstract class, so in reality a subclass of Controller will be constructed. Let's imagine the code is something like this:

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

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

Now let's say that we add a dependency to our RegistrationController, like so:

class RegistrationController extends Controller {

    private $user_repo;

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

        $this->user_repo = $user_repo;
    }

}

At this point, what I'd like to do is introduce a dependency injection container to automatically inject the dependencies via the constructor. For this, I've been using PHP-DI. Usually, this would go something like so:

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

This would then instantiate RegistrationController with an instance of Request and an instance of UserRepo. It'd know to construct with those objects thanks to reflection, but if I wanted I could also override this via a configuration file.

The problem is that the Request object takes a parameter which is dynamic. I need the Request object passed to RegistrationController to be a specific instance, one I've just created.

I essentially want to be able to say: "Give me an instance of this class with all of its dependencies injected, but for a particular parameter, pass in this specific instance".

I've looked to see if PHP-DI (and a hand-full of other DI containers) support this kind of "override" for specific parameters, but so far I can't find anything.

What I want to know is:

  • Is there a DI container out there that can do this?
  • Is there an alternative approach which would leave the classes clean (I don't want to use annotations or anything else that'll add the container I use as a dependency)?
¿Fue útil?

Solución

PHP-DI author here.

So there are two things, first I'll answer your question:

PHP-DI's container provides a make method that you can use like that:

$request = new Request($myParameters);

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

This make method, as you can see, is the same as get except it will always create a new instance (which is what you want here since you probably don't want to reuse an existing instance of the controller) and it will take the extra parameters you give it. That's the behavior of a factory, with the benefits of the container that will find the rest of the parameters you didn't provide.

So that's what I would use here. You could also do this:

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

But that's less clean because it will set the request in the container, which is bad (explained below).


Now the second thing is that a request object is not really a service, it's a "value object". A container should generally only contain service objects, i.e. objects that are stateless.

The reason for this is imagine you have several request in the same process (e.g. you do "sub-requests", or you have a worker process that handles several requests, etc...): your services would be all messed up because they would have the request injected and the request object might change.

Symfony did just that and realized it was a mistake. Since Symfony 2.4, they have deprecated having the Request in the container: http://symfony.com/blog/new-in-symfony-2-4-the-request-stack

Anyway, so what I suggest you to do is not to have the Request object in the container, but instead use the make method I showed you.

Or, even better, I would do that:

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));

(this is what Symfony and other frameworks do by the way)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top