Dependency Injection In PHP: Made Easy

In object oriented programming, tightly coupled objects / classes often result in an untestable, and a difficult to manage code base. These tightly coupled classes can be re-factored, and coupling can be achieved through DI (Dependency Injection). The principle of decoupling is based on the fact that, every object should be assigned one and only one responsibility, and there is no need for one class to be aware of the other.

Single Responsibility Principle:
In object-oriented programming, the single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.
wikipedia

Separation Of Concerns:
In computer science, separation of concerns (SoC) is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern. A concern is a set of information that affects the code of a computer program. A concern can be as general as the details of the hardware the code is being optimized for, or as specific as the name of a class to instantiate.
wikipedia

Consider the following Product class which uses a database to store and retrieve products:

<?php
/**
 * Product class
 */
class Product {

    /**
     * database layer to be used
     * @access protected
     * @var DB
     */
    protected $db;

    /**
     * creates Product 
     * @param string $dbParams for example
     * @param array $fields product fields
     */     
    public function __construct($dbParams,$fields) 
    {
        $this->db = new DB($dbParams);
        $this->fields = $fields;
    }

    /**
     * retreive products by id
     * @param  int $id product id
     * @return mixed  
     */ 
    public function getProductById($id)
    {
        return $this->db->findById($id);
    }
    /**
     * find product by executing a query
     * @param  array $query array of query options
     * @return mixed        
     */
    public function getProductByQuery($query)
    {
        return $this->db->execQuery($query);
    }
    //other operations....
}

$product = new Product($dbParams,$fields);

In the above code example the Product class is violating the principle of single responsibility, the __construct function is receiving an argument $dbParams which has nothing to do with the Product itself: it is not the responsibility of a Product to create and manage databases. Also, the new operator at line 20 is compromising the independence of the Product by introducing an acquaintance relationship with DB class (tight coupling).

Aside from the facts explained above, this kind of approach usually works, it will not cause any serious trouble for a small project containing less then a dozen of classes, and as long as you are willing to test your code manually. But, for a large project, this approach has at least two issues:

  • Testing can be a very time consuming and a difficult task.
  • Tightly coupled classes will make it difficult to manage your code base: a change in one class will affect the behavior of other classes.

Decoupling Through Dependency Injection

Dependency injection is about taking the responsibility of creating and injecting class dependencies. It is not a class’s responsibility to do things on our behalf. If a class needs the services of other classes, we should be taking care of it, not the class itself. We can decouple Product class from DB class by providing a DB object through Product's constructor.

Consider the following revised code:

<?php

/**
 * Product class
 */
class Product {

    /**
     * database layer to be used
     * @access protected
     * @var DB 
     */
    protected $db;

    /**
     * Creates Product 
     * @param string $db for example
     * @param array $fields product fields
     */     
    public function __construct($db,$fields) 
    {
        $this->db = $db;
        $this->fields = $fields;
    }

    /**
     * retreive products by id
     * @param  int $id product id
     * @return mixed  
     */ 
    public function getProductById($id)
    {
        return $this->db->findById($id);
    }
    /**
     * find product by executing a query
     * @param  array $query array of query options
     * @return mixed        
     */
    public function getProductByQuery($query)
    {
        return $this->db->execQuery($query);
    }
    //other operations....
}

$db = new DB($dbParams);// creating DB object 
$product = new Product($db,$fields);//injecting DB object through constructor

I think above code is pretty self explanatory.

Final Remarks

This article explains dependency injection itself, not how to use it. Like other design patterns dependency injection has its own positives and negatives. For example, it makes your code more testable, but on the other side, it can clutter your code with extra complexity that comes with it. To use it properly, it requires an overall good object oriented design, which obviously requires time and cost.


Leave a Reply

Your email address will not be published. Required fields are marked *


5 − = one

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>