Recent Posts
Archives
Categories
Understanding SOLID using PHP
SOLID is a set of principles in object-oriented programming that, when followed, can lead to more maintainable and scalable software. SOLID is an acronym, and each letter represents one of the principles. Let’s go through each principle with examples in PHP:
1. Single Responsibility Principle (SRP): A class should have only one reason to change. It should only have one responsibility. In the example below, the first User class includes email sending method. That means if we need to make any changes to email sending process (eg. change in email content, template etc), we have to edit this user class which is against SRP. So better way is to create a separate class for EmailService.
// Without SRP
class User {
public function save() {
// Code to save user to database
}
public function sendEmail() {
// Code to send email to the user
}
}
// With SRP
class User {
public function save() {
// Code to save user to database
}
}
class EmailService {
public function sendEmail(User $user) {
// Code to send email to the user
}
}
2. Open/Closed Principle (OCP): Software entities (classes, modules, functions) should be open for extension but closed for modification. In simpler terms, once a module is developed and working correctly, it should be possible to extend its behavior without modifying its source code. Benefits of OCP are maintainability, scalability, code reusability etc.
// Without OCP
class Shape {
public function area() {
// Code to calculate area for a generic shape
}
}
class Circle extends Shape {
public function area() {
// Code to calculate area for a circle
}
}
// With OCP
interface Shape {
public function area();
}
class Circle implements Shape {
public function area() {
// Code to calculate area for a circle
}
}
class Square implements Shape {
public function area() {
// Code to calculate area for a square
}
}
3. Liskov Substitution Principle (LSP): The Liskov Substitution Principle (LSP) is one of the SOLID principles of object-oriented design and is named after Barbara Liskov, who introduced it in a 1987 paper. The principle is a guideline for designing class hierarchies in a way that ensures objects of a derived class can be substituted for objects of the base class without affecting the correctness of the program. In simpler terms, if a class adheres to the LSP, instances of its derived classes can be used interchangeably without causing issues. LSP promotes the use of polymorphism, allowing objects of different classes to be treated as instances of a common base class. This enhances flexibility and adaptability in the design.
// Without LSP
class Bird {
public function fly() {
// Code to make the bird fly
}
}
class Penguin extends Bird {
public function fly() {
// Penguins can't fly, but we're forced to implement the method
}
}
// With LSP
interface Flyable {
public function fly();
}
class Bird implements Flyable {
public function fly() {
// Code to make the bird fly
}
}
class Penguin implements Flyable {
public function fly() {
// This method can be empty or throw an exception since penguins can't fly
}
}
4. Interface Segregation Principle (ISP): A class should not be forced to implement interfaces it does not use. Instead of having large, monolithic interfaces, it’s better to design interfaces that are specific to the needs of the implementing classes. ISP promotes the idea of high cohesion, where each interface or class has a clear and specific purpose. Cohesive interfaces are easier to understand and maintain.
// Without ISP
interface Worker {
public function work();
public function eat();
}
class Human implements Worker {
public function work() {
// Code for working
}
public function eat() {
// Code for eating
}
}
class Robot implements Worker {
public function work() {
// Code for working
}
public function eat() {
// This is not applicable to robots, but they are forced to implement it
}
}
// With ISP
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
class Human implements Workable, Eatable {
public function work() {
// Code for working
}
public function eat() {
// Code for eating
}
}
class Robot implements Workable {
public function work() {
// Code for working
}
}
5. Dependency Inversion Principle (DIP): The Dependency Inversion Principle consists of two key concepts: high-level modules should not depend on low-level modules, but both should depend on abstractions, and abstractions should not depend on details; details should depend on abstractions. By depending on abstractions, modules become less coupled to the specific details of other modules. This reduces the impact of changes in one module on others, promoting a more maintainable and adaptable system.
The Dependency Inversion Principle is closely related to the concept of Inversion of Control. IoC refers to the idea of delegating the control flow or decision-making to an external framework or container. Dependency injection is a common implementation of IoC that helps achieve the goals of DIP.
// Without DIP
class LightBulb {
public function turnOn() {
// Code to turn on the light bulb
}
}
class Switch {
private $bulb;
public function __construct(LightBulb $bulb) {
$this->bulb = $bulb;
}
public function operate() {
// Code to operate the switch
$this->bulb->turnOn();
}
}
// With DIP
interface Switchable {
public function turnOn();
}
class LightBulb implements Switchable {
public function turnOn() {
// Code to turn on the light bulb
}
}
class Switch {
private $device;
public function __construct(Switchable $device) {
$this->device = $device;
}
public function operate() {
// Code to operate the switch
$this->device->turnOn();
}
}
In conclusion, the SOLID principles provide a set of guidelines that aim to enhance the design, maintainability, and scalability of object-oriented software. By adhering to these principles, developers can create software that is modular, scalable, and resistant to the challenges of changing requirements. The SOLID principles contribute to the development of systems that are easier to understand, maintain, and extend over time.