SOLID principles in PHP

SOLID stands for the 5 principles that makes software more understandable, flexible and maintainable.
It stands for Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion.

The most important thing this solves is OK code. You write code that you think is OK, OK this works. This will have to do for now. No!! You need to write awesome code. Code that will always be understandable, flexible and maintainable. Code that’s written with love and mind. If you don’t know about this principles then you will unintentional write just OK code that becomes so coupled that any bug you fix, might create more bugs. You will get code that gets its own life. But you need to stay in control!  You need to think to code…

Single responsibility (SRP)

A class should have only a single responsibility (i.e. changes to only one part of the software’s specification should be able to affect the specification of the class)

What this means is that a class should have only reason to change. Each class you create should have only one responsibility so if you make one change that this change won’t effect anything else. For example if you have an order where you can also generate a PDF. If you have all those functions in one class. Then if you make a change in the PDF generation it might effect the order calculations. When you make 1 change it will affect more then one reason. So you have twice as much chance to break code and create a bug. There are 2 reason, 2 possible responsibilities to break code.  Then if you add everything related to orders in one class. You have multiple reason to change it. Calculation changes, database changes, pdf generation changes etc. Then you are set for disaster when your code starts to grow.

Example

In this example I show you the bad way. I have 3 functions, each has its own reason to change. The first is to change some calculations in the order. For example someone asks to change how something is calculated (reason 1). Then the second is to generate a PDF. Every time someone asks to change how the PDF looks like, you will have to change this class (reason 2). Then third we have a function to get order data from the database. Every time the database changes you will have to change this class (reason 3).

<?php
class Order 
{
    public function calculatePrice() 
    {
        /* calculate price with order data */
    }

    public function generatePdf() 
    {
        /* Code that generates a PDF */
    }

    public function getOrderFromDatabase() 
    {
        /* Get the order data from database */
    }
}

Now how do we solve this? Well we create different classes. First we have the Order class. This will change whenever we need to change how the order is calculated. Then we create a Data Mapper class which will handle getting data from the database for an order. Then we split the Order PDF generation in 2 classes. One for all the calculations for creating the PDF and one for the layout of the PDF. Because the Order PDF can still have 2 reasons to change. It’s a good idea to split code to create a layout or output from the code that generates or calculates the data to show.

<?php
class Order 
{
    public function calculatePrice() 
    {
        /* calculate price with order data */
    }
}

class OrderDataMapper 
{
    public function getFromId() 
    {
        /* Get the order data from database */
    }
}

class OrderPdf 
{
    public function generate() 
    {
        /* Code that generates a PDF */

        $pdfLayout = new OrderPdfLayout($this);
    }
}

class OrderPdfLayout 
{

    public function __construct(OrderPdf $orderPDF)
    {
        /* generate layout based on data for pdf */
    }
}

Open-closed (OCP)

software entities … should be open for extension, but closed for modification.

What this means is that we should always try to add new code instead of changing existing code.
This is also one of the big reasons code starts to become unmanageable. You might have different types of Customers with all their own class. But every time when you do something with those different type’s of Customers you maybe use a switch case to handle each possible Customer type, because based on a Customer type you need to do some calculations. Or maybe we have some Customer type that we don’t need to take into account, so we leave that out in a switch case. Now imagine you done this on 100 places in the code. But you have no 100% guaranty to find all of them. You have created 100 possible risks to break your code. I think you can start imagine why this is bad…

Example

In this example if we have 2 different customers and handle them with a switch case. Based on the type variable in the array we load in the correct customer class to then echo the correct visual name. If we add a third Customer type then we would also need to change the existing switch case and thus break this principle.

    <?php
class RetailCustomer 
{
    public $type = 'Retail';
    private $firstName;
    private $lastName;

    public function __construct($customer) { ... }

    public function getFirstName() 
    {
        return $this->firstName;
    }

    public function getLastName() 
    {
        return $this->lastName;
    }
}

class BusinessCustomer 
{
    public $type = 'Business';
    private $companyName;

    public function __construct($customer) { ... }

    public function getCompanyName() {
        return $this->companyName;
    }
}

class AllCustomers 
{
    public function show($customers) {
        foreach ($customers as $customer) {
            switch ($customer->type) {
                case 'Business':
                    echo $customer->getFirstName().' '.$customer->getLastName();
                    break;
                case 'Retail':
                    echo $customer->getCompanyName();
                    break;
            }
        }
    }
}

Now how should we solve this so we can add Customer types without the need to edit existing code?
Well look at the following example:

<?php
interface Customer {
    public function getName();
}

class RetailCustomer implements Customer {
    private $firstName;
    private $lastName;

    public function __construct($customer) { ... }

    public function getName() {
        return $this->firstName.' '.$this->lastName;
    }
}

class BusinessCustomer implements Customer 
{
    private $companyName;

    public function __construct($customer) { ... }

    public function getName() 
    {
        return $this->companyName;
    }
}

class AllCustomers 
{
    public function show($customers) 
    {
        foreach ($customers as $customer) {
            echo $customer->getName();
        }
    }
}

We created an interface which we use to extend each of the Customer types. If we now add a third type, then we have no reason to change any existing code. We just add new code to add new functionality.

Liskov substitution (LSP)

objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

This means we should only extend classes and use interfaces or abstract classes if it makes sense. If you extend from a class or interface which have functions that you don’t need. Then you have created an incorrectness. In some cases you need to realize its not possible to extend a class from another. You need to realize this and find a solution. There are different problems and solutions.

Example

In this example we extend Square from Rectangle. Because why not? A rectangle is a square right? It both has 4 sides. The only difference is that a square its sides are always the same size. So if you set height or width we need to change both height and width, because else we don’t have a square anymore.

<?php
class Rectangle
{
    protected $height;
    protected $width;

    public function setHeight(int $height)
    {
        $this->height = $height;
    }

    public function setWidth(int $width)
    {
        $this->width = $width;
    }
}


class Square extends Rectangle
{
    public function setHeight(int $height)
    {
        $this->height = $height;
        $this->width = $height;
    }

    public function setWidth(int $width)
    {
        $this->height = $width;
        $this->width = $width;
    }
}

So when you accidentally use Square instead of Rectangle you can create weird situations. For some reason when you set height, the width is also changed. This might unintentionally create bugs and issues for you in the future.

A solution to this example is to have an Abstract class where both are extended from. Both will have different setters to calculate the sides and we have one general function which calculates the area. In each of the classes we add this function and implement the calculation for that shape.

<?php
abstract class AbstractShape
{
    abstract public function Area() : int;
}


class Rectangle extends AbstractShape
{
    private $height;
    private $width;

    public function setHeight(int $height)
    {
        $this->height = $height;
    }

    public function setWidth(int $width)
    {
        $this->width = $width;
    }

    public function Area() : int
    {
        return $this->height * $this->width;
    }
}

class Square extends AbstractShape
{
    private $sideLength;

    public function setSideLength(int $sideLength)
    {
        $this->sideLength = $sideLength;
    }

    public function Area() : int
    {
        return $this->sideLength * $this->sideLength;
    }
}

Interface segregation (ISP)

many client-specific interfaces are better than one general-purpose interface.

You should not use interfaces that don’t make sense. You should not use an interface to implement a class that has no need to all the methods in the interface. You should create multiple interfaces for similar but different class blueprints.

Example

In this example we have an interface Bird. But when we implement an Ostrich, then we have a problem. Because an Ostrich is a bird, but it can’t fly.

<?php
interface Bird 
{
    public function eat();
    public function fly();
}

class Swallow implements Bird
{
    public function eat() { ... }
    public function fly() { ... }
}

class Ostrich implements Bird
{
    public function eat() { ... }
    public function fly() { /* exception */ }
}

We should solve this by creating a separate interface for flying and running birds. Then we implement the interface we need.

<?php
interface Bird
{
    public function eat();
}

interface FlyingBird 
{
    public function fly();
}

interface RunningBird 
{
    public function run();
}

class Swallow implements Bird, FlyingBird
{
    public function eat() { ... }
    public function fly() { ... }
}

class Ostrich implements Bird, RunningBird 
{
    public function eat() { ... }
    public function run() { ... }
}

Dependency inversion (DIP)

one should “depend upon abstractions, [not] concretions.

In code we have high level modules and low level modules. In simplest terms the high level modules are the ones we call directly and do all the high level business logic. Low level modules are classes that help our high level modules, this can be inserting data to a database or printing a date in the correct format.

In some cases you have different levels, there is always a high level module and a low level module.  Your high level module can be a class that posts comments. The low level module is then inserting the post to the database. But inserting the post to database can be a high level module for another low level module to validate the data to be posted to the database.

The direction is always from high to low level. But the trick here is to inverse this dependency. So the high level module does not depend on the low level one. This way we decouple them so we can use the high level modules without the need of the low level ones. We can create our own low level modules when we use a high level one. We should define interfaces instead of implementations!

Example

So we pass all our low level modules to the high level modules.

<?php
interface Adapter
{
    public function getData();
}

class Mysql implements Adapter
{
    public function getData()
    {
        return 'some data from database';
    }
}

class Controller
{
    private $adapter;

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

    function getData()
    {
        $this->adapter->getData();
    }
}

There is still one problem here. What if we are going to use another type of database? We will get an exception error because we can only pass an object of the Mysql type. What we should do is define our interface.

<?php
interface Adapter
{
    public function getData();
}

class Mysql implements Adapter
{
    public function getData()
    {
        return 'some data from database';
    }
}

class Controller
{
    private $adapter;

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }

    function getData()
    {
        $this->adapter->getData();
    }
}

Now we can pass whatever implementation we want based on our Adapter interface. You want to use an PostgreSQL database? Well just a create a new implementation of the Adapter interface and pass it to our controller. You wont need to change any other code. Everyone using this controller can use the database they want.

Conclusion

There is still a lot more to this. And I recommend diving deeper in this subject. I hope I gave you a basic overview of these the principles.