This is another post in the Basics series, intended to help you understand the internals enough to make full use of the framework. This time we’re talking about Dependency Injection and Services.

What Is Dependency Injection

This one’s really simple, but let’s cover all of it so that everything’s clear.

A dependency is another resource (often a class instance) that the current class needs to work correctly. If you are writing a UserManager class whose purpose is to take care of all of the business logic, relationships, etc, for the Users of your system, you are going to need a way to access the users in the database. That would be the UserModel. So, the UserManager has a dependency of the UserModel. Simple enough, right?

Dependency Injection is how you get the UserModel into the UserManager. There are a couple of options that you have here. First, let’s go over a common method used whenever Dependency Injection (DI for short) is not used. The UserManager’s constructor might look like this:

class UserManager 
 {
     protected $userModel;

     public function __construct()
     {
         $this->userModel = new UserModel();
     }
}

Here a new instance of the UserModel is created whenever the UserManager is instantiated. That has worked fine in many, many sites over the years, so we do we say that DI is better way to handle things? Because there are a couple of problems right off the bat, here.

First – that’s a little expensive. While you will probably only have a single instance of your UserManager, it’s possible that you will need to access the UserModel in other places. Since you’ve created an instance in the UserManager class, any other place you need to use it (like an Auth layer) will need to create another instance of the model, and that’s wasteful.

Second – What if you change the database you use later in the project, and the UserModel needs to be replaced by a new MongoUserModel or something? Than you have to hunt down every location in your code and replace it.

Third – and probably the most important, is it makes things difficult to test when you can’t easily control what class is being used as the UserModel. You might want to use a mock class so that it never touches the database, greatly speeding up unit tests. You might need a custom UserModel to log information as it happens so you can refer to it in your tests, etc. You can’t do that when it’s being instantiated in the class constructor.

So we turn to Dependency Injection. Which simply means that we take that dependency and inject it into the class from outside of it. This is commonly done in the constructor, like so:

class UserManager 
{
    protected $userModel;
 
    public function __construct(UserModel $model)
    {
        $this->userModel = $model;
    }
}

With this one small change, suddenly the three issues I pointed out above are alleviated. Testing is simple because you can pass whatever class you want in. If you need to change to MongoUserModel you can (though there’s a problem that you might have caught that we’ll get to in a moment). And you can control how many instances of the UserModel you have because you can create it once, most likely in your controller, and then inject it into any other classes that need it, leaving only a single instance in play.

Using Interfaces

I mentioned above there’s a small problem with being able to swap out the UserModel with the MongoUserModel. Currently the constructor will only accept instances of UserModel. That’s not flexible at all.

One option, and a perfectly valid one, is to got to another class higher up in the heirarchy that both classes would share. You might be able to use the Model class in many cases and be just fine. And that’s where I’d start in most of my own code. Any test mocks can easily be derived from the Model class.

However, in our particular example, we’re switching from something that makes use of CodeIgniter’s built in database layer, to a type of driver for MongoDB that doesn’t exist, and would likely be completely separate from CodeIgniter, not extending the Model class. So we can’t go that route. But we might write that class to share many common methods with the model class, but implemented according to each database’s need, so that we can change as little code that calls the model as possible. This is the perfect time to use an interface.

An interface simply defines a set of methods and their parameters that must exist in both classes. Any of the common methods we want to use in both classes are perfect examples, things like the save(), insert(), and update() methods. If both classes implement this interface, then we know we are always safe to swap them out and know that any calls to those methods should work just fine.

First, we’d create the interface:

interface ModelInterface
{
    public function insert(array $data);
    public function update($id, array $data);
    public function save($instance);
}

Next, alter the User model’s to specify they implement that interface so PHP knows it should check that they comply at run-time.

class UserModel extends Model implements ModelInterface {}

class MongoUserModel implements ModelInterface {}

Finally, change our type hint in the UserManager class:

class UserManager
{
    protected $model;

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

Now you can pass either class into the UserManager and be able to count on certain functionality always being there.

Where do I create the instances?

Ok, great. We know what DI is now, and how to use it. Where is a good place to create those instances, though? In most cases, I recommend creating this instance in the controller. The more you read up on best practices, etc the more you’ll find rules to keep your code loosely coupled, which simply means they require as little extra code outside of the class as possible. When you’re using a framework like CodeIgniter, that means any libraries, etc that you create should not be dependent on the framework, or other libraries, except where absolutely necessary.

I’m convinced the one place to ignore that rule is the controller. In my view, the controller is the one class that should be intimately familiar with the framework. It should create the dependencies other classes need and inject them in. It should load helper classes, whenever if makes sense, load libraries, log failures, etc. That way there’s only one place to look for changes whenever the framework gets updated, or you decide to use a third-party system for one section, etc.

Using the above codes as an example, a controller might look something like this:

use CodeIgniter\Controller;
use App\Models\UserModel;
use App\Managers\UserManager;

class UserController extends Controller
{
    public function edit(int $id)
    {
        $model = new UserModel();
        $manager = new UserManager($model);

        try 
        {
            $user = $manager->find($id);
        }
        catch (\DataException $e)
        {
            return redirect()->back()->with('error', 'User not found.');
       }
       echo view('admin.users.form', [
            'user' => $user
        ]);
    }
}

You’ll find that some libraries get quite complex, though, and need a bit of setup, including injecting several dependencies, maybe getting a logger for it, etc. That’s not something that you want to do in every single controller method, as it gets tedious and error prone very fast. And still leaves us with the issue that if we ever need to change it out, we have to change it in many places. That’s not fun. And that’s where Services come into play.


In another week or so, I’ll have a complete tutorial up for paid subscribers that covers Services in detail, with examples, of course.

If you’re not already a patron, I encourage to become one so that you can learn CodeIgniter 4 (and my other open source software) faster, you can influence the direction that future tutorials take, and help support its development.