BugHunterUK BugHunterUK - 8 days ago 6
PHP Question

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?

Answer

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.

Comments