PHP Design Patterns: Dependency Injection
Tags: php, designpatterns, tutorial
Published: 2023-11-07 19:44:55

### What is Dependency Injection?

It is a pattern of computer program development used when it is necessary to keep the coupling level between different modules in a system.
In this solution the dependencies between modules are not defined programically, but by configuring a software infrastructure that is responsible for "injecting" into each component its declared premises.
Dependency Injection is related to the Inversion of Control pattern but cannot be considered a synonym of it.

![1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pvhnwesyz3qmyf6e53lo.png)

### Directory System

```
📦Dependency Injection
 ┣ 📂api
 ┃ ┗ 📜ExporterInterface.php
 ┣ 📂classes
 ┃ ┣ 📜JSONExporter.php
 ┃ ┣ 📜Product.php
 ┃ ┗ 📜XMLExporter.php
 ┗ 📜index.php
```

### Fixed form

The fixed form does not allow changing a functionality at run time.

- **JSONExporter Class**

```php
<?php

namespace classes;

class JSONExporter
{
    public function export(array $data): string
    {
        return json_encode($data);
    }
}

```
The `export` method receives the data array and returns it encoded in `JSON`.

- **Product Class**

```php
<?php

namespace classes;

class Product
{
    private array $data;

    public function __get(string $prop): mixed
    {
        return $this->data[$prop];
    }

    public function __set(string $prop, mixed $value): void
    {
        $this->data[$prop] = $value;
    }

    public function toJSON(): string
    {
        $je = new JSONExporter();
        return $je->export($this->data);
    }
}

```
The `toJSON` method instances the `JSONExporter` class and returns the result obtained by the `exporter` method.

- **Test**

```php
<?php

require_once 'classes/JSONExporter.php';
require_once 'classes/Product.php';

use classes\JSONExporter;
use classes\Product;

$product = new Product();
$product->name = 'Juice';
$product->price = 2.50;

print $product->toJSON();

```
**Result:**

```json
{"name":"Juice","price":2.5}
```
In this example we simulate a class(`Product`) that depends on another class(`JSONExporter`).
The problem with this example is that the `Product` class has a fixed dependency created at development time, so there is no way to change this dependency.

### Flexible Form (Dependency Injection)

- **JSONExporter Class**

```php
<?php

namespace classes;

class JSONExporter
{
    public function export(array $data): string
    {
        return json_encode($data);
    }
}

```
The `JSONExporter` class has not undergone any changes.

- **XMLExporter Class**

```php
<?php

namespace classes;

use DOMDocument;

class XMLExporter
{
    public function export(array $data): string
    {
        $dom = new DOMDocument('1.0', 'UTF-8');
        $dom->formatOutput = true;

        $products = $dom->createElement('products');

        $product = $dom->createElement('product');
        $attr = $dom->createAttribute('id');
        $attr->value = 1;
        $product->appendChild($attr);

        $product->appendChild($dom->createElement('name', $data['name']));
        $product->appendChild($dom->createElement('price', $data['price']));

        $products->appendChild($product);

        return $dom->saveXML($products);
    }
}

```
**XML DOM** was used in the `XMLExporter` class.

- **Product Class**

```php
<?php

namespace classes;

class Product
{
    private array $data;

    public function __get(string $prop): mixed
    {
        return $this->data[$prop];
    }

    public function __set(string $prop, mixed $value): void
    {
        $this->data[$prop] = $value;
    }

    public function export(object $exporter): string
    {
        return $exporter->export($this->data);
    }
}
```
Instead of explicitly using the **class name**, we now pass it as a parameter to the `export` method.

- **Test**

```php
<?php

require_once 'classes/JSONExporter.php';
require_once 'classes/XMLExporter.php';
require_once 'classes/Product.php';

use classes\JSONExporter;
use classes\XMLExporter;
use classes\Product;

$product = new Product();
$product->name = 'Juice';
$product->price = 2.50;

// print $product->export(new JSONExporter());
print $product->export(new XMLExporter());

```
In the `export` method we can define which class we want to use to perform the export, this definition is injecting a dependency into the class at run time.

**JSONExporter output:**

```json
{"name":"Juice","price":2.5}
```

**XMLExporter output:**

```xml
<products>
  <product id="1">
    <name>Juice</name>
    <price>2.5</price>
  </product>
</products>
```
But we still have a problem, because the class passed to the `export` method of the `Product` class must contain an `export` method, if a class is passed without this method we will have an error at run time.
To solve this problem, we use the concept of **interface** to **sign a contract** with the **classes** ensuring that they have the `export` method.

### Applying Interface

![2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cysdfy65pcgmj3o8xul2.png)

```php
<?php

namespace api;

interface ExporterInterface
{
    public function export(array $data): string;
}

```

**Changing the classes**

- JSONExporter

```php
<?php

namespace classes;

require_once 'api/ExporterInterface.php';

use api\ExporterInterface;

class JSONExporter implements ExporterInterface
{
    public function export(array $data): string
    {
        return json_encode($data);
    }
}

```

- XMLExporter

```php
<?php

namespace classes;

require_once 'api/ExporterInterface.php';

use api\ExporterInterface;
use DOMDocument;

class XMLExporter implements ExporterInterface
{
    public function export(array $data): string
    {
        $dom = new DOMDocument('1.0', 'UTF-8');
        $dom->formatOutput = true;

        $products = $dom->createElement('products');

        $product = $dom->createElement('product');
        $attr = $dom->createAttribute('id');
        $attr->value = 1;
        $product->appendChild($attr);

        $product->appendChild($dom->createElement('name', $data['name']));
        $product->appendChild($dom->createElement('price', $data['price']));

        $products->appendChild($product);

        return $dom->saveXML($products);
    }
}

```

- Product

```php
<?php

namespace classes;

use api\ExporterInterface;

class Product
{
    private array $data;

    public function __get(string $prop): mixed
    {
        return $this->data[$prop];
    }

    public function __set(string $prop, mixed $value): void
    {
        $this->data[$prop] = $value;
    }

    public function export(ExporterInterface $exporter): string
    {
        return $exporter->export($this->data);
    }
}

```
We place **ExporterInterface** in front of the **object**, this is done to ensure that the object passed as a parameter has the `export` method.

So **dependency injection** is a feature that allows you to inject an object into a class, usually via a parameter and this class calls a method of that injected object