DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Sarfraz Ahmed
Sarfraz Ahmed

Posted on • Originally published at codeinphp.github.io on

Coding to Interface

One of the nicest things you can add to your programming skills is coding to interface. One of the five principles of S.O.L.I.D is Dependency inversion principle which states:

In object-oriented programming, the dependency inversion principle refers to a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details. The principle states:1

A. High-level modules should not depend on low-level modules. Both should depend on abstractions.

B. Abstractions should not depend on details. Details should depend on abstractions.

Pretty formal definition hun ? let's understand through example actually.

Imagine we have rudimentary MySQL wrapper class:

class Mysql
{
    protected $db = null;

    public function connect($dsn, $user = '', $pass = '')
    {
        $this->db = new PDO($dsn, $user, $pass);
    }

    public function query($query)
    {
        return $this->db->query($query);
    }
}

Pretty common and valid code in old days we used to be proud while writing such class. There are couple of problems associated with it though:

  • The class Mysql has implicit dependency (tight-coupling) on the PDO class. All dependencies should always be explicit not implicit.
  • It is hard to unit-test due to implicit dependency

Let's use above class:

class User
{
    private $database = null;

    public function __construct()
    {
        $this->database = new Mysql();
        $this->database->connect('mysql:host=localhost;dbname=test', 'root', '');
    }

    public function getUsers()
    {
        $users = $this->database->query('SELECT * FROM users ORDER BY id DESC');
        print_r($users);
    }
}

$user = new User();
$user->getUsers();

Again pretty common piece of code but again this one has even more problems:

  • The class User has implicit dependency (tight-coupling) on the Mysql class. All dependencies should always be explicit not implicit.
  • It is hard to unit-test due to implicit dependency
  • If we wanted to change database credentials, we need to edit the User class which is not good; every class should be completely modular or black box. If we need to operate further on it, we should actually use its public properties and methods instead of editing it again and again.
  • Let's assume right now class is using MySQL as database. What if we wanted to use some other type of database ? You will have to modify class.
  • The User class does not necessarily need to know about database connection, it should be confined to its own functionality only. So writing database connection code in User class doesn't make it modular. This defeats the Single responsibility principle.

You might wonder above code has problems but these days we write code based on MVC design pattern and follow best practices and write modern code but I am sure below code is pretty common even today using any framework you use:

class UserController extends Controller
{
    public function actionListAllUsers()
    {          
       // fetch all users from database
        $users = User::model()->findAll();

       // show them on screen
        print_r($users);          
    }
}

If you analyze this has same problems again in that class UserController is tightly coupled (implicit dependency) with User model class which also makes it hard to unit-test.

This means:

Coupled Code is Evil BIG DOT

So what's the solution ?

The solution is Dependency Injection (see my previous post Dependency Injection in PHP). The idea behind Dependency Injection is that instead of writing implicit dependencies, we should pass/provide those via constructor or setter method or even interface.

Let's refactor User class code and pass our dependency via constructor instead of hard-coding it inside User class method:

class User
{
    private $database = null;

    public function __construct(Mysql $database)
    {
        $this->database = $database;
    }

    public function getUsers()
    {
        $users = $this->database->query('SELECT * FROM users ORDER BY id DESC');
        print_r($users);
    }
}

$database = new Mysql();
$database->connect('mysql:host=localhost;dbname=test', 'root', '');
$user = new User($database);
$user->getUsers();

This is much better! We no more have implicit dependency on Mysql class which also makes our User class easier to unit-test. The class makes clear intention that it needs some sort of Mysql class. It also doesn't know (or care) where Mysql will come form, it just needs one to operate. The User class is now completely modular and follows single responsibility principle (that of manipulating users).

In the similar way, we can also improve our modern code:

class UserController extends Controller
{
    protected $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function actionListAllUsers()
    {          
       // fetch all users from database
        $users = $this->user->findAll();

       // show them on screen
        print_r($users);          
    }
}

Here again we pass dependency (User class) of UserController via its constructor making it much better. So all good ? Not really...

We still have a problem

One problem with User class which still persists is that it is tied to specific type of database (Mysql). What if we wanted to use some other database like Sqlite or SQL Server or some other ? Well this is where dependency inversion principle comes into play.

To overcome this problem, we need to create an interface (abstraction or contract - see definition of DIP above) which would allow us to use any database with User class. Here is our interface that all databases would implement:

interface DB
{
    public function connect($dsn, $user = '', $pass = '');
    public function query($query);
}

Here is Mysql class now implementing above interface:

class Mysql implements DB
{
    protected $db = null;

    public function connect($dsn, $user = '', $pass = '')
    {
        $this->db = new PDO($dsn, $user, $pass);
    }

    public function query($query)
    {
        return $this->db->query($query);
    }
}

Another database class implementing above interface:

class Sqlite implements DB
{
    protected $db = null;

    public function connect($dsn, $user = '', $pass = '')
    {
        $this->db = new PDO($dsn);
    }

    public function query($query)
    {
        return $this->db->query($query);
    }
}

As you can see both Mysql and Sqlite implement methods imposed by the interface. Finally here is our User class:

class User
{
    private $database = null;

    public function __construct(DB $database)
    {
        $this->database = $database;
    }

    public function getUsers()
    {
        $users = $this->database->query('SELECT * FROM users ORDER BY id DESC');
        print_r($users);
    }
}

Notice that in constructor of above class, we are now passing interface rather than specific type of database.

Now we can use any database for the User class, here is example of Mysql:

$database = new Mysql();
$database->connect('mysql:host=localhost;dbname=test', 'root', '');
$user = new User($database);
$user->getUsers();

And example of Sqlite database class:

$database = new Sqlite();
$database->connect('sqlite:database.sqlite');
$user = new User($database);
$user->getUsers();

Amazing! we can now readily change database to be used with our User class, our User class is much more flexible by following the dependency inversion principle. If client requires to use some other database, no problem we implement one and specify to User class in similar fashion!

As you can see we took basic and common example of code and converted it into re-usable, flexible, modular and testable piece of code.

You might want to read more about S.O.L.I.D principles to write pleasant code and as bonus avoid S.T.U.P.I.D code.

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.