Question about container DI and constructor parent

Hi,

I have just three questions about good practice with container DI.

First question:
If in a class constructor, I instancie directly a class (simple class without parameters, see bellow) which are not created in my container DI definition, I see it’s working (because of autowiring I think), but it’s preferable do declare them in container DI definition or not needed?

use App\Models\UserAuth;

class MyClass
{
    private $userAuth;

    public function __construct(UserAuth $userAuth)
    {
        $this->userAuth= $userAuth;
    }

Second question:
I have multiple Controller class (HomeController, LoginController, etc …) (which doesn’t have constructor) and extends a ParentController like this:

use App\Controllers\ParentController;

class LoginController extends ParentController
{
...
}

My ParentController has multiple class in the constructor (accessible for all my Container Files) and directly injected with container DI autowiring:

<?php
namespace App\Controllers;

use App\Helpers\Flash;
use Slim\Views\PhpRenderer;

class ParentController
{
    public $settings;
    private $phpView;
    public $flash;

    public function __construct(array $settings, PhpRenderer $phpView, Flash $flash)
    {
        $this->settings = $settings;
        $this->phpView = $phpView;
        $this->flash = $flash;
    }
...

My “settings” is create in container DI definitions, but it’s not a class, so I can’t instanciate them with autowiring (automatically) in my constructor, so the only way I have found for inject them to my ParentController of all my Controller files (HomeController, LoginController, etc …) is to do this (see bellow) in container DI definitions (with the wildcard * for select all Controller files), I have the right method ?


return [
    'settings' => function()
    {
        return require __DIR__.'/settings.php';
    },
    'App\Controllers\*Controller' => autowire()->constructorParameter('settings', get('settings')), 
];

Third question:
It’s about to create a constructor in one of my child Controller, for example “LoginController” (which extend parentController). If I want to inject a class “UserAuth” like this:

use App\Models\UserAuth;
use App\Controllers\ParentController;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class LoginController extends ParentController
{
    private $userAuth;

    public function __construct(UserAuth $userAuth)
    {
        $this->userAuth = $userAuth;
        parent::__construct();
    }
...

I can’t because my parent::__construct() also need the parameters create in “ParentController”, so the only way I have found it’s to call again all parameters like this bellow, I have the right method ?

use App\Helpers\Flash;
use App\Models\UserAuth;
use Slim\Views\PhpRenderer;
use App\Controllers\ParentController;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class LoginController extends ParentController
{
    private $userAuth;

    public function __construct(array $settings, PhpRenderer $phpView, Flash $flash, UserAuth $userAuth)
    {
        $this->userAuth = $userAuth;
        parent::__construct($settings, $phpView, $flash);
    }
...

If you can help me to see better with my three question, thanks a lot :slight_smile:

Hi!

First question: If you directly instantiate a class in the constructor, you are not using the full benefits of dependency injection. It would also be harder to maintain if you change something. It is better to declare all dependencies in constructor and let the container DI automatically inject it for you.

There is one exception, for example when you have a data transfer object (DTO). DTO’s can / should be created manually.

Second question: The DI container is only able to autowire objects, but not scalar data types such as array, int, string etc. In your case, I would recommend implementing a Config or ControllerConfig class that can then configured within the DI container definition and injected where you declare it.

Third question: Your question referred to best practices, so I will focus my answer on those aspects. While inheritance can be useful in some cases, I would generally recommend using composition instead.

Composition involves creating separate classes that each perform a specific set of functionality, and then using these classes (or interfaces) in your controller classes as needed. This allows you to create more focused and cohesive classes that are easier to understand, maintain, and test.

Thanks for your answer Odan, it’s help me a lot.

Just for first question when you say:

It is better to declare all dependencies in constructor and let the container DI automatically inject it for you.

In my constructor if I just declare my depencies like this (see bellow) for class without parameters, is that enough ?

public function __construct(Flash $flash, UserAuth $userAuth)
    {
...

Or I also need to create them in the container DI definition like this (see bellow) ?

return [
    Flash::class => function(ContainerInterface $container)
    {
        return new Flash();
    },
    UserAuth::class => function(ContainerInterface $container)
    {
        return new UserAuth();
    },
];

For Flash and UserAuth I think I don’t need to declare them in the container DI definition (like previous code) because they don’t use any parameters, so the container DI automatically inject them when I use my “__construct” it is what you meen ?

For my second question you have right, I will do like that.

For the third question, if I understand well, you think it’s better to not “extends” my class and instead group my needs with specific classes or interface for my needs ?

Thanks for your help

Regarding your first question, you are correct that you do not need to define dependencies that do not require any parameters in the container DI definition. In the case of the Flash and UserAuth classes, since they do not have any parameters in their constructors, you can simply declare them in the constructor of the class that uses them, as you have done in your example code.

In general, you should define dependencies in the container DI definition only if they require some special initialization, like database connections or services that need to be bootstrapped.

Third question: Yes, I would go even further and implement “final” classes by default. Then build small (single action) controller classes that handle only one route and declare only the needed dependencies in the constructor. You will see that you don’t need to buy a jungle if you just want to eat a banana :wink:

Thank you very much, I understand well because of your explanation.

I have try to remove the extends method and just include the needed dependencies for each route controller (one controller per route), and it’s really better to maintain and understand instead of extends to a parent.

Thanks :slight_smile:

1 Like