DEV Community

Cover image for PHP Design Patterns: Dependency Injection
Antonio Silva
Antonio Silva

Posted on • Edited on

2

PHP Design Patterns: Dependency Injection

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

Directory System

📦Dependency Injection
 ┣ 📂api
 ┃ ┗ 📜ExporterInterface.php
 ┣ 📂classes
 ┃ ┣ 📜JSONExporter.php
 ┃ ┣ 📜Product.php
 ┃ ┗ 📜XMLExporter.php
 ┗ 📜index.php
Enter fullscreen mode Exit fullscreen mode

Fixed form

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

  • JSONExporter Class
<?php

namespace classes;

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

Enter fullscreen mode Exit fullscreen mode

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

  • Product Class
<?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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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

  • Test
<?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();

Enter fullscreen mode Exit fullscreen mode

Result:

{"name":"Juice","price":2.5}
Enter fullscreen mode Exit fullscreen mode

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

namespace classes;

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

Enter fullscreen mode Exit fullscreen mode

The JSONExporter class has not undergone any changes.

  • XMLExporter Class
<?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);
    }
}

Enter fullscreen mode Exit fullscreen mode

XML DOM was used in the XMLExporter class.

  • Product Class
<?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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Instead of explicitly using the class name, we now pass it as a parameter to the export method.

  • Test
<?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());

Enter fullscreen mode Exit fullscreen mode

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:

{"name":"Juice","price":2.5}
Enter fullscreen mode Exit fullscreen mode

XMLExporter output:

<products>
  <product id="1">
    <name>Juice</name>
    <price>2.5</price>
  </product>
</products>
Enter fullscreen mode Exit fullscreen mode

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

<?php

namespace api;

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

Enter fullscreen mode Exit fullscreen mode

Changing the classes

  • JSONExporter
<?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);
    }
}

Enter fullscreen mode Exit fullscreen mode
  • XMLExporter
<?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);
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Product
<?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);
    }
}

Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up