Which types of objects that are instantiated inside controller's methods should be injected into the controller instead?

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/391301

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);
有帮助吗?

解决方案

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.

许可以下: CC-BY-SA归因
scroll top