Introduction: Why SOLID is the Foundation of Quality PHP Code
Every PHP developer sooner or later encounters a situation where a small project turns into "spaghetti code". Classes become overloaded, changes in one part of the system break another, and testing becomes a nightmare. It is precisely to solve these problems that Robert Martin (Uncle Bob) formulated the five SOLID principles. These principles are not abstract theory, but practical tools that help create flexible, maintainable, and testable code in PHP.
In this article, we will break down each SOLID principle: from theory to real-world examples in PHP 8.x. You will learn how to apply them in everyday development and which antipatterns to avoid.
1. Single Responsibility Principle (SRP)
Theory
The principle states: a class should have only one reason to change. This means that each class should be responsible for one specific functionality or business logic.
Antipattern (SRP Violation)
<?phpclass OrderProcessor { public function process(Order $order): void { // 1. Order validation if (empty($order->getItems())) { throw new \\InvalidArgumentException('Order has no items'); } // 2. Save to DB $db = new \\PDO('mysql:host=localhost;dbname=shop', 'user', 'pass'); $stmt = $db->prepare('INSERT INTO orders ...'); $stmt->execute([...]); // 3. Send email mail($order->getCustomerEmail(), 'Order confirmed', '...'); // 4. Logging file_put_contents('/var/log/orders.log', 'Order processed: '.$order->getId()); }}?>Here, the OrderProcessor class does too much: validates, works with the database, sends email, logs. Violating SRP leads to the situation where changing the email sending logic requires modifying the class responsible for order processing.
Refactoring with SRP Compliance
<?phpclass OrderValidator { public function validate(Order $order): void { if (empty($order->getItems())) { throw new \\InvalidArgumentException('Order has no items'); } }}
class OrderRepository { public function save(Order $order): void { // Database save logic }}
class EmailNotifier { public function sendOrderConfirmation(Order $order): void { // Email sending logic }}
class Logger { public function log(string $message): void { // Logging logic }}
class OrderProcessor { public function __construct( private OrderValidator $validator, private OrderRepository $repository, private EmailNotifier $notifier, private Logger $logger ) {} public function process(Order $order): void { $this->validator->validate($order); $this->repository->save($order); $this->notifier->sendOrderConfirmation($order); $this->logger->log('Order processed: '.$order->getId()); }}?>Now each class has a single responsibility. Changing the email logic does not affect working with the database.
2. Open-Closed Principle (OCP)
Theory
Software entities should be open for extension, but closed for modification. In other words, you should be able to add new functionality without changing existing code.
Antipattern
<?phpclass PaymentProcessor { public function process(string $type, float $amount): void { if ($type === 'credit_card') { // Credit card processing } elseif ($type === 'paypal') { // PayPal processing