BugHunterUK
BugHunterUK

Reputation: 8968

Dependency Injection - Injecting The Container, Or Individual Dependencies?

When using Dependency Injection should dependencies be passed into a constructor individually, or is it acceptable to pass the entire DI container?

For example ... I have a repository called 'UserRepository'. It contains the following methods:

<?php

namespace MyApp\Repositories;

use \MyApp\Models\User;

class UserRepository {

    private $ci;

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

    public function hashPassword($password)
    {
        return password_hash($password, PASSWORD_BCRYPT, [
            'cost' => 15
        ]);
    }

    public function create($firstname, $lastname, $email, $password)
    {
        $user = User::create([
            'firstname' => $firstname,
            'lastname'  => $lastname,
            'email'     => $email,
            'password'  => $this->hashPassword($password)
        ]);

        return $user;
    }

    public function activateUser($userID)
    {
        $user = User($userID);
        $user->email_verified = 1;
        $user->save();

        $verification = $user->verification();
        $verification->is_used = 1;
        $verification->validated_at = $this->ci->get('Carbon')::now();
        $verification->save();
    }
}

The Carbon dependancy is available because I've passed in the Pimple container. I can access any dependency in this way (so long as they're registered).

I'm using Slim3 which promotes this sort of DI. But, in applications like Laravel I see dependencies being passed into the constructor individually.

Any advice?

Upvotes: 0

Views: 237

Answers (1)

Federkun
Federkun

Reputation: 36989

When you pass the Dependency Injection Containers to a class, you call it "Service Locator". With a Service Locator your class is still responsible to instantiate its dependencies, and as such you should also unit test it. But how? Your object can't exist without a service locator, and test it isn't easy. If you pass the dependency into the constructor you can just mock them.

In your class you have this:

$verification->validated_at = $this->ci->get('Carbon')::now();

where Carbon is the service name. Now you should keep in mind that the Service Locator that you inject to the class require a service with that name, and it should returns an instance of the Carbon\Carbon class. What if your Service Locator has a missing Carbon service or what if it returns a whole different object? You should test it with something like this to be sure not to break anything:

$this->assertInstanceOf(Carbon\Carbon::class, $container->get('Carbon'));

and more important, if you want reuse your object somewhere else you need to implement a specific service locator implementation.

By using a DIC your object isn't responsible to instantiate its dependencies anymore:

$container['user.repository'] = function ($c) {
    return new UserRepository($c['Carbon']);
};

Your class is more reusable and writing tests is more easier.

Upvotes: 2

Related Questions