What Is Dependency Injection in PHP, and How to Use It

One popular and well known methodology of software development is called dependency injection, which helps facilitate the flow ensuring your software always has access to the tools it needs. Many try to make this methodology sound quite complex, but it really isn’t.

Let’s dive into what dependency injection is, how it works, and how it will benefit your software.

What Is Dependency Injection?

A great analogy for dependency injection is a worker with a toolkit who travels along with the software as it’s being processed, ensuring everything flows smoothly. The toolkit can contain all sorts of things including variables, arrays, objects, closures, and anything else needed to complete the task at hand.

When the worker starts a new task (i.e. class or method), it will look at the necessary requirements, and without thinking will pull out the different tools needed to complete the job. This is dependency injection in a nutshell.

You can fill your toolkit with whatever you need, then within the classes and methods of the software specify the tools you need, and they will automatically be there for you.

Install Apex Container

There are many different implementations, but all work basically the same, and we’ll be using the Apex Container as it’s simple and straight forward. It’s assumed you already have PHP installed, and you can check whether or not Composer is installed with the command:

composer --version

If you receive a “command not found” error, you may install Composer with the following command:

sudo curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer

Now create a blank directory, and within the directory run the following commands:

composer require apex/container
composer require twig/twig

This will download both the Apex Container and the popular Twig template engine which will be used within examples below. Both can be found within the /vendor/ sub-directory.

Inject Your Tools

Let’s create a quick class called Car with the following code:


use TwigLoaderArrayLoader;
class Car
{
public function __construct(
public string $model,
public string $color,
public ArrayLoader $db
) {
echo "I'm a $color $model and have a " . $db::class . "
";
}
}

This is a simple class with two properties, the make and color of a car, and loads the ‘ArrayLoader’ class from Twig. Save it as car.php and prepare to put the magic of dependency injection to use. Open another blank file and add the following code:


use ApexContainerContainer;
// Load Composer packages, and the car.php file
require_once(__DIR__ . '/vendor/autoload.php');
require_once(__DIR__ . '/car.php');
// Create container, and add a couple tools
$cntr = new Container(use_attributes: true);
$cntr->set('model', 'Jaguar');
$cntr->set('color', 'silver');
// Create our car object
$car = $cntr->make('Car');
$car2 = $cntr->make('car', ['color' => 'red']);

Save and run this code in terminal, and the results will be:

I'm a silver Jaguar and have a TwigLoaderArrayLoader
I'm a red Jaguar and have a TwigLoaderArrayLoader

In the above code, you instantiated the container (i.e. toolkit), and added a couple tools, the color and make of a car. Instead of creating the car object with the normal new Car();, it was created via the make() method of the container. This went through the class first to check the requirements, then looked at what items (i.e. tools) we had available, and injected them into the class before handing it back.

You will notice the use declaration at the top of the file to the ArrayLoader, and the third argument in the constructor also asks for an ArrayLoader. When the container looked at the requirements of the class, it noticed both of these aspects, automatically created an instance of ArrayLoader` and injected it into the constructor. This is called auto-wiring.

Extending With Attribute Injection

Taking it a step further, instead of only injecting into the constructor, we can also inject directly into properties via attributes. Modify the car file, and change it to:


use TwigLoaderArrayLoader;
use ApexContainerContainer;
class Car
{
#[Inject(Container::class)]
public Container $cntr;
public function __construct(
public string $model,
public string $color,
public ArrayLoader $db
) {
echo "I'm a $color $model and have a " . $db::class . "
";
}
function getCost()
{
echo "Class is " . $this->cntr::class . "
";
}
}

The only modifications were a new use declaration was added, the property with attribute to the Container class was added, and we added a new getCost() function. At the bottom of the test code you previously ran, add the line:

$car->getCost();

Now run the code again, and the results will be:

I'm a silver Jaguar and have a TwigLoaderArrayLoader
I'm a red Jaguar and have a TwigLoaderArrayLoader
Class is ApexContainerContainer

This time when the car.php class was loaded, the container also looked at its properties, noticed the Inject attribute that called for the container class, and injected an instance of it. Injecting via attributes in this manner is sometimes preferred as it helps keep things cleaner and more readable.

Get Your Own Tools

Instead of always injecting items, what happens if you want to retrieve an item from the container yourself? This can easily be done with the container’s get() method. Within the car.php file, modify the previously added getCost() function with:


function getCost()
{
$price = $this->cntr->get('car_price');
echo "The price is $price
";
}

Now within the test code you have been running, anywhere before the line that calls getCost() add a line such as:

$cntr->set('car_price', 24995);

Now run the code, and the results will be:

I'm a silver Jaguar and have a TwigLoaderArrayLoader
I'm a red Jaguar and have a TwigLoaderArrayLoader
Price is 24999

You don’t necessarily need to inject your items, and can always easily grab what you need with the get() method as shown above.

Moving Forward with Dependency Injection

You now have a good overview of exactly what dependency injection is, and how it works. Again, the above is just one of many implementations, but all implementations out there work the same way with the get() / set() / make() methods.

There is still more to dependency injection though such as definitions file and method injection. If interested, please check out the Apex Container manual or other implementations.

Source: makeuseof.com

Related posts

7 Features Smartphone Cameras Need to Beat DSLRs

Are Early Access Games Worth It? The Pros and Cons Explained

Perplexity AI Is the Best AI-Powered Google Search Tool You’re Not Using