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. 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. 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. 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. 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. 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:
Agile Software Development, Principles, Patterns, and Practices
Good post. Thank you.
I guess there should be “Liskov Substitution Principle” instead of “Liskov Subtitution Principle”.