Search code examples
phplaraveldependency-injectionconstructor-injection

How do I dependency inject in Laravel when already using constructor params?


This is a question aimed at Laravel for PHP.

abstract class AbstractClass
{
    protected $value1;
    protected $value2;
    protected $value3;

    public function __construct($value1, $value2, $value3) 
    {
        $this->value1 = $value1;
        $this->value2 = $value2;
        $this->value3 = $value3;
    }
}

class ClassToBeInjected
{
    public function doSomething() {}
}

class ClassOne extends AbstractClass
{
    public function __construct($value1, $value2, $value3) 
    {
        parent::__construct($value1, $value2, $value3);
    }
}

class ClassTwo extends AbstractClass
{
    public function __construct($value1, $value2, $value3) 
    {
        parent::__construct($value1, $value2, $value3);
    }
}

Hi. Have a look at the example above. I have two classes, ClassOne and ClassTwo, that both extend AbstractClass. I want both of these classes to be able to use doSomething() from ClassToBeInjected

Now my real question is, how do I inject the class when I am already using the constructor? Imagine I have a service that calls ClassOne or ClassTwo like so: new ClassOne(1, 2, 3);

I can't just add ´ClassToBeInjected´ to the signatures because that would mess up my typehinting, making it look like it needs to be manually passed in. A coworker suggested to use a trait on the abstract class, but that seems a bit weird to me. Someone on Laravel IRC suggested that I do it like this:

abstract class AbstractClass
{
    protected $value1;
    protected $value2;
    protected $value3;
    protected $injectedClass;

    public function __construct($value1, $value2, $value3)
    {
        $this->value1        = $value1;
        $this->value2        = $value2;
        $this->value3        = $value3;
        $this->injectedClass = app()->make(ClassToBeInjected::class);
    }
}

This example works and I can live with it. But I am not sure if its the correct approach and if it could cause any problems later, doing it this way.


Solution

  • You need to put the injected parameters before your constructor parameters:

    class ClassWithInjections {
        public function __construct(FirstClassToBeInjected $class1, SecondClassToBeInjected $class2, $parameter1, $parameter2) {
    
        }
    }
    

    Then to use your class you must make it within app container:

    $instance = app()->make(ClassWithInjections::class, ['parameter1value', 'parameter2value'])
    

    This feature is not well documented, but there's a example on Controllers Dependency Injection Documentation that says:

    You may still type-hint the Illuminate\Http\Request and access your id parameter by defining your controller method as follows:

    /**
     * Update the given user.
     *
     * @param  Request  $request
     * @param  string  $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
        //
    }
    

    So since the controller method is also resolved by the Service Container, I've assumed that the same will work for any injections. Reading the app()->make() method you'll find that's true.