Coding methods and principles (part 2)


Real World Example

In the previous article in this series, I spoke about dependency injection using cars and engines as an example.

In this article I will show you a more practical example by showing you how easy it can be for your system to switch between different database ORMs (Eloquent and PDO in this example) without having to recode everywhere that interacts with the database.

Just in case you’re not sure what an ORM is, ORM stands for Object-Relational Mapping and it is a technique of accessing a relational database from an object-oriented perspective.

Back to our tutorial.

Imagine that we are building an online store that sells CDs and books. We will create a class for each of these product types and both the classes will interact with the database to retrieve the CDs and books data.

To begin with, we decide to create our code to use the PDO ORM to interact with our database, however, we want the flexibility to be able to swap the ORM at a later date if we need to.

First thing we need to do is create our CD and Book classes and also our PDO ORM class:

[php]

class book
{

private $databaseConn;

public function __construct(pdo $databaseConn){

//set the database connection to be PDO when the class is instantiated
$this->databaseConn = $databaseConn;

}

public function getByID($id){

//we then use $this->databaseConn to query the database using PDO
$this->databaseConn->query(‘query to retrieve a book by its ID would go here’);

}

}

class cd
{

private $databaseConn;

public function __construct(pdo $databaseConn){

$this->databaseConn = $databaseConn;

}

public function getByYearReleased($year){

$this->databaseConn->query(‘query to retrieve CDs by year of release would go here’);
}

}

class pdo {
    
    public function query(){
        
        //pdo query here
        
    }
    
}

[/php]

As you can see, whenever a book or CD is first created, we have used dependency injection to ensure that the PDO class is passed through and then we set our database connection variable to be the PDO instance that was passed through.

We would then use the following code to retrieve our CDs and books:

[php]

$pdo_database_orm = new pdo();

$book = new book($pdo_database_orm);

$cd = new cd($pdo_database_orm);

$bookOne = $book->getByID(1);

$cdsFrom2012 = $cd->getByYearReleased(‘2012’);

[/php]

This code would successfully get us the book that has the ID of 1, and any CDs that were released in the year 2012.

Imagine, for one reason or another, we have now decided that we want to use Eloquent as our database ORM instead of PDO.

Therefore, we create a new eloquent class which will hold all our eloquent queries like we did with the PDO class.

[php]

class eloquent {

public function query(){

//eloquent query here

}

}

[/php]

We then modify our existing code to pass through the Eloquent instance rather than the PDO.

[php]

$eloquent_database_orm = new eloquent();

$book = new book($eloquent_database_orm);

$cd = new cd($eloquent_database_orm);

$bookOne = $book->getByID(1);

$cdsFrom2012 = $cd->getByYearReleased(‘2012’);
[/php]

The problem with this code is that the type hinting in the book and CD class constructors would realise that the Eloquent class is not a PDO class and will throw a type hinting error.

It makes sense for this error to be thrown. For example, imagine we use the PDO ORM initially and add a new method in the PDO class which isn’t in the eloquent class. We then call this method inside one of our book or CD classes. This would work fine to begin with, but as soon as we swap our database ORM to be eloquent, then this new method would not exist in the eloquent class, and the code would break.

So how can we fix this?

Programming to an Interface

To solve this issue, we can create an interface and make the book and CD classes implement it.

We then program to the interface rather than the concrete book and CD classes.

So what does program to an interface mean?

What it means is that when we are working with the book or CD object in our code, rather than looking in the concrete book and CD classes to find out what methods we can use for each object, we instead look at the methods in the interface, and therefore, we program to an interface rather than a concrete implementation.

This adds a layer of abstraction and ensures that we only call methods that every other class that implements the interface also has. This allows us to easily swap out different classes as we know for a fact that the method will exist in whatever class we decide to swap it with.

To do this we first need to create an interface and then make both the book and CD class implement this interface.

This will then allow us to type hint the interface rather than a concrete implementation and easily swap between different database ORMs as the interface will ensure they have the same methods, and therefore will work when we switch between them in our code.

So let’s create a new interface for our different database ORMs to implement, then implement it in both the eloquent and PDO classes, then finally use the new interface as a type hint in the book and CD classes construct methods.

[php]

//create a new interface for our different database ORMs
interface database_abstraction_layer {

//ensure all classes that implement it have the query method in them
public function query();

}

//now implements the database_abstraction_layer
class pdo implements database_abstraction_layer {

public function query(){

//pdo query here

}

}

//now implements the database_abstraction_layer
class eloquent implements database_abstraction_layer {

public function query(){

//eloquent query here

}

}

class book
{

private $databaseConn;

//type hint the interface instead
public function __construct(database_abstraction_layer $databaseConn){

$this->databaseConn = $databaseConn;

}

public function getByID($id){

return $this->databaseConn->query(‘SELECT * FROM book WHERE id = ‘.$id);

}

}

class cd {

private $databaseConn;

//type hint the interface instead
public function __construct(database_abstraction_layer $databaseConn){

$this->databaseConn = $databaseConn;

}

public function getByYearReleased($year){

return $this->databaseConn->query("SELECT * FROM cd WHERE year_released = ‘".$id."’");

}

}

[/php]

Now that we are using an interface and we are type hinting with this interface, as long as our different ORM concrete classes implement the interface, we can switch between them easily without there being any issues as the interface will ensure they all have common methods so switching them won’t break anything.

Below shows how easily we can switch between the two database ORMs now we are using an interface:

[php]

//start using pdo ORM
$pdo_database_orm = new pdo();

$book = new book($pdo_database_orm);

$cd = new cd($pdo_database_orm);

$bookOne = $book->getByID(1);

$cdsFrom2012 = $cd->getByYearReleased(‘2012’);

//switch to eloquent ORM
$eloquent_database_orm = new eloquent();

$book = new book($eloquent_database_orm);

$cd = new cd($eloquent_database_orm);

$bookOne = $book->getByID(1);

$cdsFrom2012 = $cd->getByYearReleased(‘2012’);

[/php]

The above code would now work perfectly and shows how we can achieve loose coupling and maintainable code by using dependency injection and coding to an interface rather than a concrete implementation.

You may not feel the need to switch database ORMs often in the real world, however this also allows you to swap between a real and a mock database when testing your code.

However, there is one main issue with this code and that is, even though the code may be more modular, we’ve also piled on confusion and complexity.

Every time we instantiate a product type (book, CD) we are having to manually inject the database ORM variable. This doesn’t seem too much trouble in the above example, but imagine if this had to be done multiple times in multiple files throughout your project when switching to a new ORM. Not only would this be a pain having to find everywhere in our project where a product type is instantiated and modify the dependency we pass through, but also if we miss any, then it will break our code.

Also imagine if the product type class constructors had multiple arguments for different dependances, not just the database ORM. We could easily get confused with what class requires what dependency and in what order when instantiating a new product type.

IoC (Inversion of Control) can solve this issue by building up all the dependency injections in a container class at run time and just use this container to call all future instances.