"SOLID" is an acronym for the five most known Object-Oriented Design principles by Michael Feathers, which are promoted by Robert C. Martin in his books. These principles, among others, tend to make software designs better, flexible, and more maintainable. These five principles are:

  • SRP: Single Responsibility Principle.
  • OCP: Open/Closed Principle.
  • LSP: Liskov Substitution Principle.
  • ISP: Interface Segregation Principle.
  • DIP: Dependency Inverion Principle.

Let's take each one of them and explain it with some examples.

Single Responsibility Principle

A class should have only one reason to change.

This principle simply states that a class or a module should have only a single responsibility and the functionality of it should be entirely encapsulated by that class/module. As simple as that sounds, it is easy to overlook it. The single responsibility principle enforces the small classes idea that was discussed earlier.

Mistakenly, "one responsibility" could be translated as "a class should have one method." Although it might, that is not what is meant in this context. It means that the behavior should be related to the class responsibility. Let's take an example.

namespace App;

use Database;

class Product
{
    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function onSale()
    {
        // Get the products that are on sales
        $products = $this->getProductsOnSale();

        // Return formatted results
        return $this->format($products);
    }

    private function getProductsOnSale()
    {
        return $this->db->table('products')->where('sales', '=', 'yes')->all();
    }

    private function format($products)
    {
        return json_encode($products);
    }
}

Apparently, the SRP has been violated because this class has more than one responsibility, namely connecting to the database, fetching data, and returning data as JSON. The way to fix this is to extract some of these responsibilities to their classes.

One way to look at the SRP is the "one reason to change," which doesn't apply in the previous class. For instance, if we want to modify the persistence layer in the future, we would have to change the getProductsOnSale() implementation, therefore the class is changed. The same if we want to modify the representation to be XML instead of JSON in the format($products). Now we have two reasons to change violating the SRP.

It is not the Product class's responsibility to know about the persistence layer or how to fetch the information from the DB. So let's start by fixing that.

We would have to create another repository class that would have a method which takes care of fetching the data from the database and returns the results.

namespace App\Repository;

use Database;

class ProductRepository
{
    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function onSale()
    {
        return $this->db->table('products')->where('sales', '=', 'yes')->all();
    }
}

We moved the getProductsOnSale() to the ProductRepository class and rename it to simply onSale(). Now we update the Product class to be like the following.

namespace App;

use App\Repository\ProductRepository;

class Product
{
    public function __construct(ProductRepository $repo)
    {
        $this->repo = $repo;
    }

    public function onSale()
    {
        // Get the products that are on sales
        $products = $this->repo->onSale();

        // Return formatted results
        return $this->format($products);
    }

    private function format($products)
    {
        return json_encode($products);
    }
}

It is much cleaner and more maintainable. We can change the repository implementation of the onSales() method without having to change anything in the Product class.

Next on the list is the format method. It is not the Product class's responsibility to format the results. Moreover, what if we needed to change the format to be XML representation or even a plain PHP array? Let's fix this.

First, we would create an interface that will be the contract for any class that wants to format a set of data.

namespace App;

interface Formatter
{
    public function format($data);
}

Then, we could create as many classes as we need to define formatting mechanisms; JSON, XML or what have you.

namespace App;

use Formatter;

class JSONProductsFormatter implements Formatter
{
    public class format($products)
    {
        return json_encode($products);
    }
}

The class is basic and straight forward at this stage, yet we could have XMLProductsFormatter class and such. Now the Product class can accept the interface and apply the format method with the result leaving the representation of the output to the consumer.

namespace App;

use App\Repository\ProductRepository;
use App\Formatter;

class Product
{
    public function __construct(ProductRepository $repo)
    {
        $this->repo = $repo;
    }

    public function onSale(Formatter $formatter)
    {
        // Get the products that are on sales
        $products = $this->repo->onSale();

        // Return formatted results
        return $formatter->format($products);
    }
}

Now the onSale method would not care what format the cosumer request as long as it implements the Formatter interface.

Open/Closed Principle

Software entities should be open for extension, but closed for modification.

The OCP states that classes, modules, functions and such should be extensible without the need to modify their code. It is not that simple to grasp without an example.

In an example when discussing the difference between the Procedural and Object-Oriented Programming, we had three classes representing geometrical shapes and a fourth class, Geometry, which calculates the area depending on the shape.

class Circle  
{
    public $center;
    public $radius;
}

class Square  
{
    public $topLeft;
    public $side;
}

class Rectangle  
{
    public $topLeft;
    public $height;
    public $width;
}

class Geometry  
{
    const PI = 3.14;

    public function area($shape)
    {
        if ($shape instanceof Circle) {
            return self::PI * $shape->radius * c.radius;
        } else if ($shape instanceof Square) {
            return $shape->side * $shape->side;
        } else if ($shape instanceof Rectangle) {
            return $shape->height * $shape->width;
        }
        throw new Exception("No Such Shape");
    }
}

As noticeable, and mentioned before, the Geometry class is checking the $shape against the defined shapes, Circle, Square, and Rectangle, and calculate the area according to it.

Now if we are required to add a new shape, say triangle, for instance, we would have to go, and modify the area method on the Geometry class. This would violate the OCP because we would have to change the code.

Fixing that was intoduced later as the OO approach.

interface Shape
{
    public function area();
}

class Circle implements Shape  
{
    const PI = 3.14;

    private $center;
    private $radius;

    public function area()
    {
        return self::PI * $this->radius * $this->radius;
    }
}

class Square implements Shape  
{
    private $topLeft;
    private $side;

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

class Rectangle implements Shape  
{
    private $topLeft;
    private $height;
    private $width;

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

What we did is placing the extensible behavior behind an interface, in this case, is the Shape, and inverse the dependency. This is basically the OCP.

Liskov Substitution Principle

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

This principle initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It's a wordy definition, and I didn't grasp that at first.

Uncle Bob gave it the next definition.

Subtypes must be substitutable for their base types.

To break it down, LSP states that the subclasses of a class should be substitutable for their base/original class. That means if we have a class Father, and a class Child that extends the class Father, then we should be able to substitute Child for Father anywhere Father is being used. As simple as that sounds, it might be a bit difficult to apply it. The following example would illustrate that.

Let's assume that have a gallery application, and we need to return a list of the images saved either on Dropbox or in a database (regardless why we shouldn't save our images in DB). We have the ImageDropbox class extends ImageDB and re-implement the getAll() method.

class ImageDB
{
    public function getAll()
    {
        // return collections (Laravel's Eloquent) from Database
    }
}

class ImageDropbox extends ImageDB
{
    public function getAll()
    {
        // return array of images that are stored on Dropbox
    }
}

function zipImages($images)
{
    // Zip images
}

As LSP states, we should be able to use getAll() on both ImageDropbox and ImageDB interchangeable on the zipImages($images). However, the getAll() is returning Laravel's Eloquent collections on ImageDB, while returning an array
on ImageDropbox. This is a violation of the "Liskov Substitution Principle" because we cannot substitute the results of getAll() on ImageDB and ImageDropbox.

To fix this, once again, we are going to create a repository interface, ImageRepository, with a getAll() signature, and the two classes, ImageDropbox and ImageDB, implement getAll().

interface ImageRepository
{
    public function getAll(): array;
}

class ImageDropbox implements ImageRepository
{
    public function getAll(): array
    {
        // returns an array of images stored on Dropbox
    }
}

class ImageDB implements ImageRepository
{
    public function getAll(): array
    {
        // returns an array of images stored in the Database
    }
}

function zipImages(array $images)
{
    // Zip images
}

Using PHP7's "scalar type declarations," and "return type declarations", we would enforce the returned type to be of an array.

Interface Segregation Principle

A client should never be forced to implement an interface that it doesn't use or clients shouldn't be forced to depend on methods they do not use. This is a relatively straightforward principle to understand.

It is noticeable by now that we rely a lot on our interfaces, hence the say "program to an interface". Interfaces almost have the same scope of responsibility, whether they are interface typed entities or objects implementing design patterns like Facades. That scope is acting as contracts to the client code on using an individual module. But enough of that, let's jump to the example.

interface Worker {
	public function work();
	public function eat();
}

class NormalWorker implements Wroker{
	public function work()
    {
		// working
	}
	public function eat()
    {
		// eating
	}
}

class SuperWorker implements Wroker{
	public function work()
    {
		// working much more
	}

	public function eat()
    {
		// eating
	}
}

class Manager {
	Wroker worker;

	public function setWorker(Worker w)
    {
		worker = w;
	}

	public function manage() {
		worker.work();
	}
}

This is a classical example. We have a Manager class which manages Workers. Workers are two types; NormalWorker, and SuperWorker. Both of those types work and eat. Later on, a need for a new type of workers has been introduced; the RobotWroker. As one might assume, that type doesn't eat, it just works. Implementing the eat method on the RobotWorker will violate the IS Principle. To fix that, we split the Worker interface into two interfaces Workable and Feedable which means that the RobotWorker class won't have to implement the Feedable interface.

interface Worker extends Feedable, Workable {
}

interface Workable {
	public function work();
}

interface Feedable{
	public function eat();
}

class NormalWorker implements Workable, Feedable{
	public function work() {
		// working
	}

	public function eat() {
		// eating
	}
}

class RobotWorker implements Workable{
	public function work() {
		// working
	}
}

class SuperWorker implements Workable, Feedable{
	public function work() {
		// working much more
	}

	public function eat() {
		// eating
	}
}

class Manager {
	Workable worker;

	public function setWorker(Workable w) {
		worker = w;
	}

	public function manage() {
		worker.work();
	}
}

Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

The core of this principle is decoupling code because this what differentiate clean code from bad one. Dependency Injection is what this principle depends on. However, it is merely the only thing that we need to understand here. Straight to the example.

class ProductRepository
{
    private $db;

    public function __construct(MongoDBConnection $db)
    {
        $this->db = $db;
    }
}

The ProductRepository knows too much about the connection method to the database; it depends on it to perform an action. "High-level modules should not depend on low-level modules. Abstractions should not depend upon details." In this case ProductRepository (high-level module) depends on MongoDBConnection (low-level module, and details).
But wait! Isn't that the Dependency Injection that we just mentioned it'll help us? Well, as I mentioned, it is merely the only thing we need to take care of.

So once again, we would use the help of an interface.

interface DBConnectionInterface
{
    public function connect();
}

class ProductRepository
{
    private $db;

    public function __construct(DBConnectionInterface $db)
    {
        $this->db = $db;
    }
}

Now the high-level module depends on abstraction and not detailed low-level module. But we still have this part: "Both (high-level, and low-level modules) should depend on abstractions."

interface DBConnectionInterface
{
    public function connect();
}

class MongoDBConnection implements DBConnectionInterface 
{
    public function connect()
    {
        // Connect to MongoDB
    }
}

class ProductRepository
{
    private $db;

    public function __construct(DBConnectionInterface $db)
    {
        $this->db = $db;
    }
}

Now we have both the high-level module (ProductRepository) and the low-level module (MongoDBConnection) depend on abstraction and not details. ProductRepository by injecting DBConnectionInterface into __construct and MongoDBConnection by implementing DBConnectionInterface.