Solid Design Principles in PHP

Design Principles was introduced over a decade ago by Robert C Martin (Uncle Bob) and is used across the object-oriented design spectrum.

solid design principles in php

I’m going to try and keep this as jargon-free as possible so it’s easy for beginners to understand the Solid Design Principles in PHP.

Why use these principles I hear you ask? When applied properly it makes your code more extendable, logical and easier to read.

Let’s go through each principle one by one:

S: Single Responsibility Principle

Each class should have one responsibility and one responsibility only. This means that all the methods and properties should all work towards the same goal.

Let’s take the following simple class as an example of how this principle may be violated:

class Customer
{
    public $name;
    public $age;

    public function __contsruct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    public function getCustomerDetails()
    {
        return "Name: ".$this->name." Age: ".$this->age;
    }
}

This may be fine for printing information to the frontend but what about if you need to get that information in JSON or as an array? You use classes or interfaces with defined roles, thus separating the business logic from the presentation layer:

interface CustomerInterface
{
    function getCustomerDetails();
}

We then may have an interface for retail customers that must return the data as a string.

class RetailCustomer implements CustomerInterface
{
    public function getCustomerDetails()
    {
        return "Name: ".$this->name." Age: ".$this->age;
    }
}

We may have the trade interface to return this data as an array:

class TradeCustomer implements CustomerInterface
{
    public function getCustomerDetails()
    {
        return array( "name" => $this->name, "age" => $this->age);
    }
}

O: Open-closed Principle

In a nutshell, your classes should be extendable without actually changing the contents of the class you’re extending.

Here’s a very basic example of how this principle can be violated if we had to introduce another customer type:

class PointsCalculator
{
    public function getPointsSpent($customerType, $points)
    {
        switch ($customerType)
        {
            case 'retail':
            return $points * 0.5;
            break;

            case 'trade':
            return $points * 0.75;
            break;
        }
    }
}

The way around this would be to use interfaces as in the previous principle:

interface CustomerInterface
{
    function getPointsSpent();
}

class RetailCustomer implements CustomerInterface
{
    public function getPointsSpent()
    {
        return $this->type * 0.5;
    }
}

class TradeCustomer implements CustomerInterface
{
    public function getPointsSpent()
    {
        return $this->type * 1;
    }
}

class PointsCalculator
{
    public function getPointsSpent($customerTypes)
    {
        $amount = 0;

        foreach ($customerTypes as $type)
        {
            $amount += $type->getPointsSpent();
        }

        return $amount;
    }
}

L: Liskov Substitution Principle

This principle basically specifies that child classes should be suitable for their parent classes. This means it will adhere to the principles and functionality of the class it extends

class Instrument
{
    public function play() {..}
    public function changeKey() {..}
    public function autoTune() {..}
    public function plugIn() {..}
}

The instrument class may behave as an abstract and could be implemented in the following ways:

class Guitar extends Instrument
{
    public function play() {
        $this->plugIn();
        $this->strum();
        parent::play();
    }

    public function strum() {..}
}

class Trumpet extends Instrument
{
    public function play() {
        $this->blow();
        parent::play();
    }

    public function blow() {..}
}

I: Interface Segregation Principle

This principle is probably the simplest in terms of theory.

A client should not be forced to use interfaces that it doesn’t need.

Here’s an example of violation of this principle:

interface InstrumentInterface {
    public function play();
    public function changeKey();
    public function autoTune();
    public function plugIn();
}

class Guitar implements InstrumentInterface {
    public function play() {..}
    public function changeKey() {..}
    public function autoTune() {..}
    public function plugIn() {..}
}

class Trumpet implements InstrumentInterface {
    public function play() {..}
    public function changeKey() {..}
    public function autoTune() { /* Exception */ }
    public function plugIn() { /* Exception */ }
}

The Trumpet class has autoTune()plugIn() forced upon it – I’m not saying that there are no plugin-in trumpets out there but in this case I’m talking about a standard acoustic trumpet. An ISP-safe method is to create an interface for each of the instruments that employs only the methods needed for the client:

interface InstrumentInterface
{
    public function play();
    public function changeKey();
}

interface GuitarInterface
{
    public function autoTune();
    public function plugIn();
}

class Guitar implements InstrumentInterface, GuitarInterface
{
    public function play() {..}
    public function changeKey() {..}
    public function autoTune() {..}
    public function plugIn() {..}
}

class Trumpet implements InstrumentInterface {
    public function play() {..}
    public function changeKey() {..}
}

D: Dependency Inversion Principle

This principle states that high-level modules should not depend on low-level modules. High-level modules should never change and should be decoupled (separated) from low-level modules that could be changed on a daily basis.

The introduction of dependency injection in Magento 2 has made this principle easier to adhere to, as it is now clear which classes and clients are dependent on each other. Please check my last post Diving into Magento2 Dependency Injection for detailed information.

Take the example below which shows a rather limited example of higher-level code being dependent on the lower level code:

class CountApples
{
    private $apple;
    public function __contsruct($apple)
    {
        $this->apple = $apple;
    }

    public function howMany()
    {
        return count($this->apple->howMany());
    }
}

This does the job, but the class is completely dependent on us always counting apples. There are other fruits out there kids! We should make our counting class more general:

class CountFruit
{
    private $fruit;
    public function __contsruct($fruit)
    {
        $this->fruit = $fruit;
    }

    public function howMany()
    {
        return count($this->fruit->howMany());
    }
}

/** Create our generic fruit interface **/

interface Fruit
{
    public function howMany();
}

/** Create our clients **/

class Apples implements Fruit
{
    public function howMany()
    {
        return 14;
    }
}

class Bananas implements Fruit
{
    public function howMany()
    {
        return 6;
    }
}

The end result is code that is less dependent on the lower level code with which it is working. You could take this much further than the example above but the theory is the same.

I hope I have kept this simple enough for beginner OOPHP developers to understand. Thanks for reading.

Your little help will keep this site alive and help us to produce quality content for you.