SOLID Principles in PHP: Advanced Application and Practical Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why SOLID is Not Just a Dogma, But a Tool

Most developers know that SOLID is five principles of object-oriented programming: Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. However, in practice, understanding often boils down to mechanically following rules. In this article, we will go further: we will analyze advanced scenarios for applying SOLID in PHP, examine antipatterns that violate the principles, and show how refactoring with SOLID in mind makes code flexible and testable.

The material is intended for Middle+ and Senior level developers who already write in PHP and want to improve the architecture of their projects.



1. Single Responsibility Principle (SRP): When a Class "Knows Too Much"

The classic formulation: "A class should have only one reason to change." At an advanced level, it is important to understand that "reason to change" is a business requirement. If your class changes due to changes in validation rules, output format, or saving logic, this is an SRP violation.



Violation Example (God Object)

class OrderProcessor {    public function process(array $data): void {        // 1. Data validation        if (empty($data['user_id']) || empty($data['items'])) {            throw new InvalidArgumentException('Invalid order data');        }                // 2. Cost calculation        $total = 0;        foreach ($data['items'] as $item) {            $total += $item['price'] * $item['quantity'];        }                // 3. Saving to DB        $sql = "INSERT INTO orders (user_id, total, status) VALUES (?, ?, 'pending')";        // ... executing query                // 4. Sending email        mail($data['user_email'], 'Order confirmation', "Total: $total");    }}


Refactoring with SRP

We split into three classes with clear areas of responsibility:

class OrderValidator {    public function validate(array $data): void {        if (empty($data['user_id']) || empty($data['items'])) {            throw new InvalidArgumentException('Invalid order data');        }    }}

class OrderCalculator { public function calculateTotal(array $items): float { return array_reduce($items, function ($carry, $item) { return $carry + $item['price'] * $item['quantity']; }, 0.0); }}

class OrderRepository { public function save(int $userId, float $total, string $status = 'pending'): int { // DB saving logic return 42; // Order ID }}

class OrderProcessor { public function __construct( private OrderValidator $validator, private OrderCalculator $calculator, private OrderRepository $repository, private Mailer $mailer ) {} public function process(array $data): void { $this->validator->validate($data); $total = $this->calculator->calculateTotal($data['items']); $orderId = $this->repository->save($data['user_id'], $total); $this->mailer->send($data['user_email'], 'Order #' . $orderId); }}


2. Open/Closed Principle (OCP): Extension Without Modification

The essence of OCP: classes should be open for extension but closed for modification. In practice, this means using polymorphism and abstractions instead of conditional constructs.



OCP Violation: switch-case for each new type

class PaymentProcessor {    public function pay(string $type, float $amount): void {        switch ($type) {            case 'credit_card':                // card processing                break;            case 'paypal':                // PayPal processing                break;            // When adding a new type — we modify the class!        }    }                

Blogs

Book Recommendations