SOLID Principles Explained

Attempting to write code which satisfies the current requirements and future requirements should be the goal.


Attempting to write code which satisfies the current requirements and future requirements should be the goal.
Single Responsibility Principle (SRP)

A class should have a single responsibility and thats it! E.g open a database, fetch certain data from database, send an email. If these needed to be changed in future you’d be changing it in one place, one class, not several.

https://laracasts.com/series/solid-principles-in-php/episodes/1

What not to do!

class DoSomeStuff() {
    public function __construct() {
        echo 'Open Database';
        echo 'Pull data from Database';
        echo 'Send an email';
    }
}

class DoSomeOtherStuff() {
    public function __construct() {
        echo 'Open Database';
        echo 'Pull data from Database';
        echo 'Log some data';
    }
}
What to do!

class DoSomeStuff() {
    public function __construct()
    {
        new SendEmail(new PullData(new Db()));
    }
}

class DoSomeOtherStuff() {
    public function __construct()
    {
        new LogSomeData(new PullData(new Db()));
    }
}

class Db() {
    public function __construct()
    {
        echo 'Open Database';
    }
}

class PullData()) {
    public function __construct(Db $db)
    {
        // You'd use $db here but for the sake of the example...
        echo 'Pull data from Database';
    }
}

class SendEmail() {
    public function __construct(PullData $data)
    {
        // You'd use $data here but for the sake of the example...
        echo 'Send an email';
    }
}

class LogSomeData() {
    public function __construct(PullData $data)
    {
        // You'd use $data here but for the sake of the example...
        echo 'Log some data';
    }
}
Open / Closed Principle (OCP)

Entities should be CLOSED for modification but OPEN for extension. Basically, it should be simple to change the behaviour of a class, without changing its source code.

If you manage to adhere to this, you’d prevent code rot or seeing code which is at the point where you’re actually afraid to touch it (Imagine continually editing a class or a function so its over 3k lines of code).

https://laracasts.com/series/solid-principles-in-php/episodes/2

What not to do!

class Square {
    public $width;
    public $height;

    function __construct($height, $width)
    {
        $this->height = $height;
        $this->width = $width;
    }

}

class Circle {

    public $radius;

    function __construct($radius)
    {
        $this->radius = $radius;
    }

}

// This would calculate the area of all given shapes
class AreaCalculator {

    public function calculate($shapes)
    {
        $area = 0;

        foreach ($shapes as $shape) {

            // This breaks OCP, imagine if we need to add a different shape (Triangle)
            // We'd have to modify this code AGAIN
            // Imagine years down the line, what would the code then look like..
            // This file is OPEN for modification
            if ($shape instanceof (Square)) {
                $area += $shape->width * $shape->height;
            } elseif ($shape instanceof (Circle)) {
                $area += $shape->radius * $shape->radius * pi();
            }
        }

        return $area;
    }
}
What to do!

If you did come across this code, you’d ideally want to separate the behaviour (in this case our potentially increasing IF … ELSE IF case) behind an interface.

An interface is a contract which classes (that implement it) MUST adhere to - otherwise the code will break.

In the example below we’re going to force our shape classes to have an area method and move the logic which works out a shapes area into that method.


interface ShapeInterface {
    public function area();
}

class Circle implements ShapeInterface {
    public $radius;

    function __construct($radius)
    {
        $this->radius = $radius;
    }

    public function area()
    {
        return $this->radius * $this->radius * pi();
    }
}

class Square implements ShapeInterface {
    public $width;
    public $height;

    function __construct($height, $width)
    {
        $this->height = $height;
        $this->width = $width;
    }

    public function area()
    {
        return $this->width * $this->height;
    }
}

// This would calculate the area of all given shapes
class AreaCalculator {

    // Type hinting to an interface means we know for sure that
    // whatever is passed in definitely has an area() function
    public function calculate(ShapeInterface $shapes)
    {
        $area = 0;
        // This would never, ever, ever now need to be changed
        // You'd only change the area method within a shapes class.
        // No more code rot here!
        foreach ($shapes as $shape) {
            $area += $shape->area();
        }
        return $area;
    }
}
Liskov’s Substitution Principle (LSP)

Classes which extend others (subclasses) should be able to be substituted wherever that class (that they’re extending) is utilised. Basically, subclasses should return the same as the class they’re extending, if overriding a method.

https://laracasts.com/series/solid-principles-in-php/episodes/3

What not to do!

classA {
    function someFunction()
    {
        return 'someString';
    }
}

classB extends ClassA {
    function someFunction()
    {
        // Breaks LSP, its not returning a String (Like ClassA)
        // Therefore you wouldnt be able to substitute ClassB for ClassA
        return true;
    }
}
What to do!

PHP has nothing which will validate a functions output, best you can do is to use doc blocks.

In the example below you would now be safety able to substitute ClassB wherever you’ve got ClassA (Both return a string).


classA {
    /**
    * Some function which returns a string
    *
    * @return string
    */
    function someFunction()
    {
        return 'someString';
    }
}

classB extends ClassA {
    /**
    * Some function which returns a string
    *
    * @return string
    */
    function someFunction()
    {
        return 'someOtherString';
    }
}
Interface Segregation Principle (ISP)

Classes shouldn’t be forced to implement Interface methods they do not need. You may have classes which implement an interface and you don’t need them to implement every method, then you’re breaking ISP.

To combat this you’d split your Interface accordingly.

https://laracasts.com/series/solid-principles-in-php/episodes/4

What not to do!

In the example below, the MilkmanInterface works for a Milkman, but what about a Coder?


interface MilkmanInterface {
    public function deliver();
    public function walk();
    public function talk();
}

class Milkman implements MilkmanInterface {
    public function deliver() {
        return 'Milk delivery complete';
    }

    public function talk() {
        return 'Hello!';
    }

    public function walk()
    {
        return '.... Footstep noises...';
    }
}

class Coder implements MilkmanInterface {

    public function deliver() {
        // Do nothing
    }

    public function code() {
        echo 'Coding... I love SOLID principles!';
    }

    public function talk() {
        echo 'GRUNT';
    }

    public function walk() {
        echo 'My legs dont work...';
    }

}
What to do!

You could split the Interfaces so they make more sense, such as:


interface Deliverable {
    public function deliver();
}

// Im assuming all people can walk here....
interface Personable {
    public function walk();
    public function talk();
}

interface Codeable {
    public function code();
}

class Milkman implements Deliverable, Personable {
    public function deliver() {
        return 'Milk delivery complete';
    }

    public function talk() {
        return 'Hello!';
    }

    public function walk()
    {
        return '.... Footstep noises...';
    }
}

class Coder implements Codeable, Personable {
    public function code() {
        echo 'Coding... I love SOLID principles!';
    }

    public function talk() {
        echo 'GRUNT';
    }

    public function walk() {
        echo 'My legs dont work...';
    }

}
Dependency Inversion Principle (DIP)

Code should depend on abstractions (classes that can not be directly Instantiated - i.e Interfaces or Abstract classes) and not concretions. Doing this, allows us to change whats passed into an object easily.

https://laracasts.com/series/solid-principles-in-php/episodes/5

What not to do!

In the example, why does PasswordReminder need to know about how we connect to our database? It only needs to know there's some database connection it can work with.

If we needed to connect to a MSSQL database, you’d have to make some tweaks as we’re locked into using MySQLConnection.


class PasswordReminder {
    /**
    * @var MySQLConnection
    */
    private $dbConnection;

    public function __construct(MySQLConnection $dbConnection) // Breaks DIP
    {
        $this->dbConnection = $dbConnection;
    }
}
What to do!

Instead, you’d code to an Interface, and type hint the construct parameter. The low-level code (MySQLConnection and MSSQLConnection) depends the abstraction (its extending the Interface).

The high-level code (PasswordReminder) is expecting an object which implements the Interface (type hinting). Now you can pass different database connections to the construct.


interface ConnectionInterface {
    public function connect();
}

class MySQLConnection implements ConnectionInterface {
    public function connect()
    {
        // Code to connect to database here.
    }
}

class MSSQLConnection implements ConnectionInterface {
    public function connect()
    {
        // Code to connect to database here.
    }
}

class PasswordReminder {
    /**
    * @var ConnectionInterface
    */
    private $dbConnection;

    public function __construct(ConnectionInterface $dbConnection)
    {
        $this->dbConnection = $dbConnection;
    }
}