SOLID principles in PHP: theory and practice
SOLID is an acronym for five fundamental principles of object-oriented programming formulated by Robert Martin (Uncle Bob). These principles help create flexible, maintainable, and scalable code. In this article, we will analyze each principle using PHP examples and show how to apply them in real projects.
1. Single Responsibility Principle (SRP)
Essence: Each class should have only one reason to change. In other words, a class should be responsible for only one task.
SRP violation:
class Order { public function calculateTotal() { /* ... */ } public function saveToDatabase() { /* ... */ } public function sendEmailNotification() { /* ... */ }}Here the Order class is responsible for calculation, saving, and sending notifications. This violates SRP.
Solution:
class Order { public function calculateTotal() { /* ... */ }}
class OrderRepository { public function save(Order $order) { /* ... */ }}
class EmailNotificationService { public function sendOrderConfirmation(Order $order) { /* ... */ }}Now each class has one clear responsibility, which simplifies testing and modification.
2. Open-Closed Principle (OCP)
Essence: Classes should be open for extension but closed for modification. You should be able to add new functionality without changing existing code.
OCP violation:
class PaymentProcessor { public function process($type) { if ($type == 'credit_card') { // credit card processing } elseif ($type == 'paypal') { // PayPal processing } }}To add a new payment method, you would have to modify the PaymentProcessor class.
Solution using interfaces:
interface PaymentMethod { public function process();}
class CreditCardPayment implements PaymentMethod { public function process() { /* ... */ }}
class PayPalPayment implements PaymentMethod { public function process() { /* ... */ }}
class PaymentProcessor { public function process(PaymentMethod $method) { $method->process(); }}Now to add a new payment method, you only need to create a new class implementing the PaymentMethod interface, without changing existing code.
3. Liskov Substitution Principle (LSP)
Essence: Objects of subclasses should be interchangeable with objects of the base class without breaking program correctness. A subclass should not change the behavior expected from the base class.
LSP violation:
class Rectangle { protected $width; protected $height;
public function setWidth($w) { $this->width = $w; } public function setHeight($h) { $this->height = $h; } public function getArea() { return $this->width * $this->height; }}
class Square extends Rectangle { public function setWidth($w) { $this->width = $w; $this->height = $w; // principle violation } public function setHeight($h) { $this->height = $h; $this->width = $h; // principle violation }}If client code expects that changing the width will not affect the height, then Square will break this expectation.
Solution: Avoid inheritance if the subclass does not fully match the parent's behavior. Better to use composition or separate classes:
interface Shape