Dependency Injection Container

Quillstack DI is the dependency injection container based on PSR-11: Container interface. The library was built with the main goal: to be fast!

What is the DI Container?

Every time you need a create a new instance of some class, you use the keyword new, like here:

  • $dog = new Dog('Alfie');

In our example the string parameter Alfie, which is a lovely name for a dog, is a dependency we need to know when we're creating a new Dog object.

Of course our example is very simple. Life brings us more difficult problems to solve.


The Repository-Service Pattern

One of the most popular design patterns is the Repository-Service pattern. More complex systems requires layers of abstraction, to keep the code maintainable and easy to understand, learn, and extend. This pattern is a good example of the usage for containers.

The Repository-Service pattern allows you to separate two layers from the rest of you application.

Services provide some business logic and can be used e.g. in Controllers or other Services.

Repositories are the data access layer and can connect e.g. to the database, and should be used only in Services.

In that way we can end up with a lot of dependencies, usually injected via constructors:

  • $media = new MediaService(
  •     new Logger(), new MediaRepository(
  •         new Database(), new Logger()
  •     )
  • );

Do we want to remember all these classes when we need to have an instance of the Media Service? No!

This is a moment when a container helps us to sort it out and change it to this:

  • $media = $container->get(MediaService::class);

So much better! And there's no magic. The dependency injection container checks every parameter of the given class and create a new instance of required objects. If these classes are dependent on other objects, the DI Container also creates them.

The implementation of this kind of library is very often based on recursion.


MVC Pattern

Containers are often used in MVC frameworks. The first initialized class is a Controller, where we can inject some dependencies, usually services:

  • new MediaController(
  •     new MediaService(
  •         new Logger(), new MediaRepository(
  •             new Database(), new Logger()
  •         )
  •     )
  • );

Without a DI Container our life is hard. We can easily improve it:

  • $controller = $container->get(MediaController::class);

All of this work is done by framework for us, so we don't have to worry about it.


Installation

To install this package, run the standard command using Composer:

  • composer require quillstack/di

The package will be ready to use after that.


Usage

You can use Quillstack DI when you want:

  1. To have a simple and fast DI container.
  2. To define dependencies based on interfaces.
  3. Define parameters e.g. credentials for a database in Database class.

Simple usage

You can easily start using a DI Container:

  • <?php
  •     
  • use Quillstack\DI\Container;
  •     
  • require __DIR__ . '/../vendor/autoload.php';
  •     
  • $container = new Container();
  • $controller = $container->get(ExampleController::class);

This code creates an instance of the container class. The container creates every class with get method, in our case it will be ExampleController.

Dependencies based on interfaces

If you want to define which class should be loaded based on an interface, you can easily do that:

  • $container = new Container([
  •     LoggerInterface::class => Logger::class,
  • ]);

When you create a new container, you can define a configuration of interfaces and class which should be use when these interfaces are called.

In this example everytime when LoggerInterface will be used as a dependency, an object of Logger class will be injected.

It's very useful when you want to use an implementation of some interface, but you want to keep your code open to changes of this implementation in the future. If you decide to do that, you'll have to change just one line.

Dependencies with parameters

If some of your classes require parameters, define them as an array passed on the second parameter to the container:

  • $container = new Container([
  •     Database::class => [
  •         'hostname' => 'localhost',
  •     ],
  • ]);

Of course you can take the value of the hostname from the configuration files.

Dependencies as objects

In this example whenever new class of LoggerInterface will be required as a dependency, a previously defined object will be used. This object can be created once in a bootstrap file, and used in the entire application:

  • $handler = new StreamHandler('var/app.log');
  • $logger = new Logger('name');
  • $logger->pushHandler($handler);
  • $container = new Container([
  •     LoggerInterface::class => $logger,
  • ]);