Solid Principles and PHP

Let’s begin with.. What are the SOLID principles?

“In computer programming, SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) is a mnemonic acronym introduced by Michael Feathers for the “first five principles” named by Robert C. Martin in the early 2000s that stands for five basic principles of object-oriented programming.

The principles, when applied together, intend to make it more likely that a programmer will create a system that is easy to maintainand extend over time.” Wikipedia

The creator of the “first five principles” is Robert C. Martin (aka Uncle Bob). He is a well recognized software engineer since the 1970s, and co-author of the Agile Manifesto. He is a must follow! www.cleancoder.com

The 5 Solid Principles and PHP

In this post, I’m going to introduce the SOLID principles. Five agile principles that should guide you every time you write code. And I’ll show you how to apply them with PHP. I’ll try to be the most practical as possible, and providing examples for each casuistic.

  • 1. S-RP : Single Responsibility Principle
  • 2. O-CP : Open Closed Principle
  • 3. L-SP : Liskov Substitution Principle
  • 4. I-SP : Interface Segregation Principle
  • 5. D-IP : Dependency Inversion Principle

1. S-RP

Single Responsibility Principle.  SOLID principles and PHP
Single Responsibility Principle. SOLID principles and PHP

Single Responsibility Principle. This principle specifies that a class (or method) must have a specific purpose, IE, a unique responsibility or a reason to change.

Show me code!

Imagine that we have an interface for users like this one :

interface UserInterface
{
    public function setId($id);
    public function getId();
    public function setName($name);
    public function getName();
    public function findById($id);
    public function insert();
    public function update();
    public function delete();
}

In this case, it’s clear to see that the CRUD methods should be placed in the data access layer completely insulated from the accessors. This implies that there are two overlapped responsibilities coexisting here which makes the class change in response to different requirements.

So this could be solved by creating two interfaces, UserInterface and UserMapperInterface.

interface UserMapperInterface
{
    public function findById($id);
    public function insert(UserInterface $user);
    public function update(UserInterface $user);
    public function delete($id);
}

interface UserInterface
{
    public function setId($id);
    public function getId();
    public function setName($name);
    public function getName();
    public function setEmail($email);
    public function getEmail();
}

Conclusion

This principle is often ignored for reasons of practicality and ease of development, but if you work on large-scale projects, it’s extremely important to respect the pillars of good OOP design, or we will lose the battle against complexity.

2. O-CP

Open Closed Principle.  SOLID principles and PHP
Open Closed Principle. SOLID principles and PHP

Open Closed Principle. This principle is based on delegating responsibility to the class. If we have actions that depend on the subtype of a class, it is easier to provide that feature in the parent class, and then the subclasses can be re-implement that feature by polymorphism (OOP). The problem here is that the classes should be open to extension and closed to change. Let’s see it clearer with the example.

Show me code!

Imagine we have a Board class that contains Rectangles and can calculate the area of the Rectangles.

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

class Board
{
    public $rectangles[];
    public function calculateArea() {
        $area = 0;
        foreach ($this->rectangles as $rectangle) {
            $area += $rectangle->width * $rectangle->height;
        }
    }
}

But now, our client needs to add circles to the board.

We have to make our Board to know about Rectangles and Circles, but if we would respect OCP, we should not need to touch Board or Rectangle. We should reuse the existing Board and apply it to Circle.

interface Shape {
    public function area();
}

class Rectangle implements Shape 
{
    public function area() {
        return $this->width * $this->height;
    }
}
class Circle implements Shape 
{
    public function area() {
        return $this->radius * $this->radius * pi();
    }
}
class Board
{
    public function calculateArea() {
        $area = 0;
        foreach ($this->shapes as $shape) {
            $area+= $shape->area();
        }
    }
}

Conclusion

This is one of the most criticized SOLID principles. On this case I’m going to attach the Conclusion of Robert Martin about OCP (link) with which I agree and says :
“I’ve heard it said that the OCP is wrong, unworkable, impractical, and not for real programmers with real work to do. The rise of plugin architectures makes it plain that these views are utter nonsense. On the contrary, a strong plugin architecture is likely to be the most important aspect of future software systems.”

3. L-SP

Liskov Substitution Principle.  SOLID principles and PHP
Liskov Substitution Principle. SOLID principles and PHP

Liskov Substitution Principle. This principle says that every class that inherit from a parent class, must not replicate functionality already implemented in the parent class. Then the parent class should be able to be replaced by any of its sub-classes in any region of the code.

Show me code!

In this case, we have our class Rectangle, and now we want to create a class Square that extends from Rectangle.

class Rectangle
{
    public function setWidth($w) { $this->width = $w; }
    public function setHeight($h) { $this->height = $h; }
    public function getArea() { return $this->height * $this->width; }
}

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

and we create our function to calculate classes :

function areaOfRectangle() {
    $rectangle = new Rectangle();
    $r->setWidth(7); $r->setHeight(3);
    $r->getArea(); // 21
}

as the LSP says, we should be able to change Rectangle by Square

function areaOfRectangle() {
    $rectangle = new Square();
    $r->setWidth(7); $r->setHeight(3);
    $r->getArea(); // 9
}

Remember what we said: “we must make sure that Square classes are extending the Rectangle without changing their behaviour”. But, as you can see, 21 is not equal to 9.

The solution would be to manage the class inheritance hierarchies correctly, for example by introducing the interface Quadrilateral.

interface Quadrilateral
{
    public function setHeight($h);
    public function setWidht($w);
    public function getArea();
}

class Rectangle implements Quadrilateral;

class Square implements Quadrilateral;

Conculsion

Following the Liskov Substitution Principle is a good indicator that you are following a correctly hierarchy schema. And if you don’t follow it, the unit tests for the super-class would never succeed for the sub-classes.

4. I-SP

Interface Segregation Principle.  SOLID principles and PHP
Interface Segregation Principle. SOLID principles and PHP

Interface Segregation Principle. This principle proposes to divide interfaces so they are more specific. A class can implement multiple interfaces simultaneously, we shouldn’t force clients to deploy methods unnecessary.

Show me code!

Let’s think about a digital agency that has workers, so I create the interface Worker

interface Worker {
    public function takeBreak()
    public function code()
    public function callToClient()
    public function attendMeetings()
    public function getPaid()
}

but for example, if we create the class Manager and the class Developer we are going to have problems with unused methods:

class Manager implements Worker
{
    public function code() { return false; }
}

class Developer implements Worker
{
    public function callToClient() { echo $swear_word; }
}

Then we should create more interfaces.

interface Worker
{
    public function takeBreak()
    public function getPaid()
}

interface Coder {
    public function code()
}

interface ClientFacer {
    public function callToClient()
    public function attendMeetings()
}

class Developer implements Worker, Coder {}

class Manager implements Worker, ClientFacer {}

Conclusion

The conclusion here is simple, we can end up with a ‘fat’ class with multitudes of methods specific to a variety of different features. A system may become so coupled at multiple levels that it is no longer possible to make a change in one place without necessitating many additional changes.

5. D-IP

Dependency Inversion Principle.  SOLID principles and PHP
Dependency Inversion Principle. SOLID principles and PHP

Dependency Inversion Principle. This is one the most important SOLID principles if you follow TDD. It refers to a specific form of decoupling software modules. So, if a class usses other classes, the initialization of the objects has to come from outside.

As this principle could be difficult to understand, lets review what the principle states :

  • High level modules should not depend on low-level modules, both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

Then, this principle inverts the way some people may think about OOP, dictating that both classes and subclasses must depend on the same abstraction.

Show me code!

In this case, I’m going to use the Worker example that we used in the previous principle.

We have the Manager class, which is the high level class, and then a class called Worker. The Manager can make Workers to work. Let’s see a traditional OO implementation:

// Bad example
class Worker
{
    public function work() {}
}

class Manager
{
    private $worker;
    public function setWorker($w) {
        $this->worker = $w;
    }

    public function manage() {
        $this->worker->work();
    }
}

Now, we need to add a new kind of specialized workers, we create a new class SpecializedWorker for this.

// Bad example

class SpecializedWorker
{
    public function work() {}
}

Now see the cons:

  • We have to modify the Manager class, and some of the functionality of the Manager might be affected.
  • The unit tests should be redone.

If we would have follow the D-IP, we wouldn’t have had these problems.

// Good example

interface Employee
{
    public function work();
}

class Worker implements Employee
{
    public function work() {}
}

class SpecializedWorker implements Employee
{
    public function work() {}
}

Conclusion

In my opinion, the Dependency Inversion Principle is one that helps us respect all the other principles, because allows you to separate responsibilities, force you to respect O-CP, and offers you the opportunity to segregate your interfaces.

Final Thoughts

Using the SOLID principles implies an increased effort. It will result in more classes and interfaces to maintain. This means more complex but more flexible code. These principles will make our code better and our life as programmers much easier. Well designed code is easier for programmers to understand. A lot of these SOLID principles seem to fall out fairly naturally if you practice TDD (or BDD).

And finally, remember the KISS (Keep It Simple, Stupid), and DRY (Don’t Repeat Yourself) principles. I believe there are no perfect designs, just trade offs, and the SOLID principles can help you evaluate these and achieve a good balance.

Recommended reading:

The principles of OOD

Agile Software Development, Principles, Patterns, and Practices

0 0 votes
Article Rating
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
ANIKET SINGH
ANIKET SINGH
6 years ago

Good post. Thank you.
I guess there should be “Liskov Substitution Principle” instead of “Liskov Subtitution Principle”.

1
0
Would love your thoughts, please comment.x
()
x