0

Consider code below

MyController //MyAction //MyHandler
{
    public function processRequest()
    {
        // ...

        $myObject = new MyObjectClass();
        $myObject->methodCall();

        // ...
    }
}

Should such code be refactored to use dependency injection instead? If so, for what objects? All of them? Are there exceptions to which objects can be instantiated and ones that cannot?

MyController //MyAction //MyHandler
{
    public function __construct(MyObjectClass $myObject)
    {
        $this->myObject = $myObject;
    }

    public function processRequest()
    {
        // ...

        $this->myObject->methodCall();

        // ...
    }
}

The purpose of injecting objects would be to expose Controller dependencies and not to hide them inside the Controller.

Sample Use Case

My immediate use case I have a PdfRenderer object. An object that itself accepts a PDF library instance, and houses methods to render PDF pages, templates, and PDF output. So if I wish to include a PDF in my controller/action class, I will need to instantiate the PdfRenderer somewhere. Should that location be inside a controller or inside a factory somewhere or somewhere else?

How do I test the Controller?

//test code
$expectedResponce = ... 
$controller = new MyController(..);
$actualResponse = $controller->processRequest();
$this->assertSomeProperty($expectedResponce, $actualResponse);
  • Should that location be inside a controller How would you test the controller? – Laiv May 2 at 14:14
  • not sure I understood your question. I've added sample test pseudocode in my question. Instantiate the controller the way it is written, and test its output. I presume I can do this regardless of the first or the second way I have it written in the question – Dennis May 2 at 14:19
  • 1
    Ok, my bad. If the controller initializes the renderer, it means you can not test the controller fully decoupled from the render. If that's ok for you, then fine. In other words. If you consider the Renderer to be an implementation detail of the Controller (something intrinsically involved in the controller implementation), then allow the controller to initialize the render. If both components are complementary, then tell someone else to initialize the render. – Laiv May 2 at 14:27
  • thanks, so I see this as a design decision whether or not I want to tightly couple an object to the controller. And whether or not I want to or can afford testing the controller with its hardcoded dependency – Dennis May 2 at 14:35
  • Yes, Mainly that. If you ever want to use a different Renderer, you will have to update the controller and redeploy it. If the render is a dependency (injected somehow), you can have the Render implementation in a different place, deploy it separately, without messing with the controller and the rest of the application. Not to mention the gain on testability which is always welcome. – Laiv May 2 at 14:38
2

Looking at your code, if you have any requirement to have a unit test cover the processRequest method, without dependency injection it will be impossible for you to provide a mock object for MyObjectClass in your first example. If MyObjectClass interacts with anything (such as making an HTTP request, write / read from a file, save / read stuff to or from a database), then that interaction will also occur during your test because you won't be able to avoid it. With dependency injection you can supply a mock version of MyObjectClass that avoids those types of interactions.

For your sample use case, I would make the argument that rendering a PDF is a clear sign that you should use DI to manage the object's construction. During a test you wouldn't want to actually create a PDF, because tests can be run many times in many different environments. If you have a CI system in place, you don't want hundreds of PDFs eating up space on your CI server whenever it runs your test suite.

Often times I have created objects that just do helpful data manipulation; taking data in one format and converting it to a more useful one that I can use directly. Those types of objects arguably don't need to be mocked during tests, since using them produces no side effects outside of the application so providing those types via dependency injection is a judgement call, especially if those objects are used inside of a single method. I personally still do, just because I know by now that I cannot predict the future or how requirements or code will change, and it's just so easy to typehint an object in a class constructor and let the DI system handle the heavy lifting for me.

As a side benefit of using DI, you get a clear signal that your class is starting to do too much when the number of arguments to the constructor starts getting to be large.

  • 2
    As an aside, I spent most of this answer talking about testing, but "testing" alone doesn't just mean testing. If you can do the things you need to do to write a good encapsulated test, then you can also have flexibility in the future when requirements change. And flexibility in the future is important because things will change. – Jeff Lambert May 2 at 14:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.