SOLID stands for the first five principles of object-oriented design (OOD). It is a set of guidelines that developers can use to create software that is easy to maintain and expand.
Let’s understand the concept behind this to make yourself a better developer and avoid code smells.
SOLID stands for:
S: Single-responsibility principle
O: Open-closed principle
L: Liskov substitution principle
I: Interface segregation principle
D: Dependency Inversion Principle
Let’s deeply understand these principles!
Single-responsibility principle
A class must have one reason to be changed.
We will experience high coupling if our class takes on more than one responsibility. Our code will be vulnerable to any changes.
Let’s say we have a User class like this:
<?php
class User {
private $email;
// Getter and setter
public function store() {
// Store attributes into a database...
}
}
It means that the method store is not within the scope of the class that manages the databank. It is why you need to create two classes with the appropriate responsibilities.
<?php
class User {
private $email;
// Getter and setter
}
<?php
class UserDB {
public function store(User $user) {
// Store the user into a database...
}
}
Open-closed principle
Open objects or entities should be available for modification but not for extension. This principle states that software entities must be extensible easily with new features without modifying their existing code.
Let’s say we want to calculate the area of some objects. To do this, we will need an AreaCalculator Class that only adds up each shape area.
The problem is each Shape uses a different method of calculating its area.
<?php
class Rectangle {
public $width;
public $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
}
class Square {
public $length;
public function __construct($length) {
$this->length = $length;
}
}
class AreaCalculator {
protected $shapes;
public function __construct($shapes = array()) {
$this->shapes = $shapes;
}
public function sum() {
$area = [];
foreach($this->shapes as $shape) {
if($shape instanceof Square) {
$area[] = pow($shape->length, 2);
} else if($shape instanceof Rectangle) {
$area[] = $shape->width * $shape->height;
}
}
return array_sum($area);
}
}
To add another shape, such as a Circle, to the AreaCalculator, we need to adjust it to calculate the new area. This is not sustainable. It is why we can create a simple Shape interface using the area method. All other shapes will then be able to use it.
This way, there will be only one method for calculating the sum. If we need to add a shape, it will simply implement the Shape interface.
<?php
interface Shape {
public function area();
}
class Rectangle implements Shape {
private $width;
private $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
public function area() {
return $this->width * $this->height;
}
}
class Square implements Shape {
private $length;
public function __construct($length) {
$this->length = $length;
}
public function area() {
return pow($this->length, 2);
}
}
class AreaCalculator {
protected $shapes;
public function __construct($shapes = array()) {
$this->shapes = $shapes;
}
public function sum() {
$area = [];
foreach($this->shapes as $shape) {
$area[] = $shape->area();
}
return array_sum($area);
}
}
Liskov Substitution Principle
Let q (x) be a property that can prove objects of type T. Then, q(y) should be provable for objects of type S. S is a subtype T.
This principle states that objects should be replaced by instances of their subtypes without affecting the proper functioning of our system.
Imagine two coffee machines. The user plan will dictate whether we use a standard or premium coffee machine. However, the premium machine can make better vanilla coffee than the basic one. Both machines must have the same main program behavior.
<?php
interface CoffeeMachineInterface {
public function brewCoffee($selection);
}
class BasicCoffeeMachine implements CoffeeMachineInterface {
public function brewCoffee($selection) {
switch ($selection) {
case 'ESPRESSO':
return $this->brewEspresso();
default:
throw new CoffeeException('Selection not supported');
}
}
protected function brewEspresso() {
// Brew an espresso...
}
}
class PremiumCoffeeMachine extends BasicCoffeeMachine {
public function brewCoffee($selection) {
switch ($selection) {
case 'ESPRESSO':
return $this->brewEspresso();
case 'VANILLA':
return $this->brewVanillaCoffee();
default:
throw new CoffeeException('Selection not supported');
}
}
protected function brewVanillaCoffee() {
// Brew a vanilla coffee...
}
}
function getCoffeeMachine(User $user) {
switch ($user->getPlan()) {
case 'PREMIUM':
return new PremiumCoffeeMachine();
case 'BASIC':
default:
return new BasicCoffeeMachine();
}
}
function prepareCoffee(User $user, $selection) {
$coffeeMachine = getCoffeeMachine($user);
return $coffeeMachine->brewCoffee($selection);
}
Interface Segregation Principle
Clients shouldn’t have to use interfaces that they don’t like, and clients shouldn’t have to use methods that they don’t like.
This principle states that a class shouldn’t implement interfaces that are not needed. It means we won’t have the methods we need in our implementations. Instead of developing general-purpose interfaces, the solution is to create specific interfaces.
Imagine a Future Car that can fly and drive.
<?php
interface VehicleInterface {
public function drive();
public function fly();
}
class FutureCar implements VehicleInterface {
public function drive() {
echo 'Driving a future car!';
}
public function fly() {
echo 'Flying a future car!';
}
}
class Car implements VehicleInterface {
public function drive() {
echo 'Driving a car!';
}
public function fly() {
throw new Exception('Not implemented method');
}
}
class Aeroplane implements VehicleInterface {
public function drive() {
throw new Exception('Not implemented method');
}
public function fly() {
echo 'Flying an aeroplane!';
}
}
As you can see, the main issue is that the Car and Aeroplane have methods that they don’t use. The solution is to split the Vehicle Interface into two more specific interfaces that are used when it’s necessary, like the following:
<?php
interface CarInterface {
public function drive();
}
interface AirplaneInterface {
public function fly();
}
class FutureCar implements CarInterface, AirplaneInterface {
public function drive() {
echo 'Driving a future car!';
}
public function fly() {
echo 'Flying a future car!';
}
}
class Car implements CarInterface {
public function drive() {
echo 'Driving a car!';
}
}
class Aeroplane implements AirplaneInterface {
public function fly() {
echo 'Flying an aeroplane!';
}
}
Dependency Inversion Principle
Entities should not depend on concretions but abstractions. The high-level module cannot depend on the lower-level module but must rely on abstractions.
This principle says that a class should not depend on another class directly but on abstraction. This principle allows for more code reuse and decoupling.
Let’s look at the UserDB class in its first instance. This class could be dependent on a DB connection.
<?php
class UserDB {
private $dbConnection;
public function __construct(MySQLConnection $dbConnection) {
$this->$dbConnection = $dbConnection;
}
public function store(User $user) {
// Store the user into a database...
}
}
In this example, the UserDB class is dependent on the MySQL database. We must change the UserDB class if the database engine is changed in use. We also need to violate the Open-Close principle.
Database abstraction is the solution.
<?php
interface DBConnectionInterface {
public function connect();
}
class MySQLConnection implements DBConnectionInterface {
public function connect() {
// Return the MySQL connection...
}
}
class UserDB {
private $dbConnection;
public function __construct(DBConnectionInterface $dbConnection) {
$this->dbConnection = $dbConnection;
}
public function store(User $user) {
// Store the user into a database...
}
}
These principles represent the state of the art of code quality, and following them permits you to write software that will easily extend, reusable, and refactored.
I hope this article helps you better understand what code quality is and maybe improve your coding ability! Happy coding!
If you have any php programming language project, you can choose ABC. ABC has been helping businesses get ahead of the competition for over 20 years. They provide innovative web solutions that help businesses grow and thrive online. ABC is the best PHP website development company, since they have a proven track record of delivering high-quality, cutting-edge solutions to their clients
One response to “What are SOLID Principles? A Complete Guide About It”
You’ve addressed all the potential issues someone might have about this subject.