SOLID принципы в PHP: полное руководство с примерами кода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему SOLID — основа качественного PHP-кода

Каждый PHP-разработчик рано или поздно сталкивается с проблемой «спагетти-кода»: классы становятся перегруженными, изменения в одном месте ломают всё приложение, а тестирование превращается в кошмар. Решение этой проблемы — SOLID принципы. Разработанные Робертом Мартином (дядей Бобом), эти пять принципов объектно-ориентированного программирования помогают создавать гибкий, поддерживаемый и масштабируемый код.

В этой статье мы разберём каждый из пяти принципов SOLID на практических примерах PHP. Вы узнаете, как применять их в реальных проектах, и увидите разницу между «плохим» и «хорошим» кодом. Материал подойдёт как новичкам, так и опытным разработчикам, желающим систематизировать знания.

1. Single Responsibility Principle (SRP) — принцип единственной ответственности

Суть принципа

Класс должен иметь только одну причину для изменения. Это означает, что каждый модуль или класс отвечает за одну конкретную задачу.

Плохой пример (нарушение SRP)

<?php
class Report {
    public function generateReport($data) {
        // Формирование данных отчёта
        $report = "Отчёт: " . json_encode($data);

        // Сохранение в файл
        file_put_contents('report.txt', $report);

        // Отправка по email
        mail('admin@example.com', 'Новый отчёт', $report);

        return $report;
    }
}
?>

Здесь класс Report делает три разных дела: генерирует отчёт, сохраняет его и отправляет. Изменение логики отправки email сломает генерацию отчёта.

Хороший пример (соблюдение SRP)

<?php
class ReportGenerator {
    public function generate(array $data): string {
        return "Отчёт: " . json_encode($data);
    }
}

class ReportSaver {
    public function save(string $report, string $filename): void {
        file_put_contents($filename, $report);
    }
}

class ReportMailer {
    public function send(string $report, string $email): void {
        mail($email, 'Новый отчёт', $report);
    }
}

// Использование
$generator = new ReportGenerator();
$report = $generator->generate($data);

$saver = new ReportSaver();
$saver->save($report, 'report.txt');

$mailer = new ReportMailer();
$mailer->send($report, 'admin@example.com');
?>

Теперь каждый класс имеет одну ответственность, и изменение способа сохранения не повлияет на отправку email.

2. Open/Closed Principle (OCP) — принцип открытости/закрытости

Суть принципа

Программные сущности должны быть открыты для расширения, но закрыты для модификации. Другими словами, вы должны иметь возможность добавлять новую функциональность без изменения существующего кода.

Плохой пример (нарушение OCP)

<?php
class PaymentProcessor {
    public function process($type, $amount) {
        if ($type === 'credit_card') {
            // Обработка кредитной карты
        } elseif ($type === 'paypal') {
            // Обработка PayPal
        } elseif ($type === 'bitcoin') {
            // Обработка Bitcoin
        }
        // Каждый новый тип платежа требует изменения этого метода!
    }
}
?>

Хороший пример (соблюдение OCP)

<?php
interface PaymentMethod {
    public function process(float $amount): bool;
}

class CreditCardPayment implements PaymentMethod {
    public function process(float $amount): bool {
        // Логика оплаты кредитной картой
        echo "Оплата картой: $amount\n";
        return true;
    }
}

class PayPalPayment implements PaymentMethod {
    public function process(float $amount): bool {
        // Логика оплаты через PayPal
        echo "Оплата PayPal: $amount\n";
        return true;
    }
}

class BitcoinPayment implements PaymentMethod {
    public function process(float $amount): bool {
        // Логика оплаты Bitcoin
        echo "Оплата Bitcoin: $amount\n";
        return true;
    }
}

class PaymentProcessor {
    public function process(PaymentMethod $method, float $amount): void {
        $method->process($amount);
    }
}

// Использование
$processor = new PaymentProcessor();
$processor->process(new PayPalPayment(), 100.50);
?>

Добавление нового способа оплаты теперь требует только создания нового класса, реализующего интерфейс PaymentMethod — без изменения существующего кода.

3. Liskov Substitution Principle (LSP) — принцип подстановки Барбары Лисков

Суть принципа

Объекты в программе должны быть заменяемы экземплярами их подтипов без изменения правильности выполнения программы. Проще говоря: если у вас есть функция, работающая с базовым классом, она должна работать и с любым его наследником.

Плохой пример (нарушение LSP)

<?php
class Rectangle {
    protected $width;
    protected $height;

    public function setWidth(int $width): void {
        $this->width = $width;
    }

    public function setHeight(int $height): void {
        $this->height = $height;
    }

    public function getArea(): int {
        return $this->width * $this->height;
    }
}

class Square extends Rectangle {
    public function setWidth(int $width): void {
        $this->width = $width;
        $this->height = $width; // Нарушение: квадрат меняет высоту при изменении ширины
    }

    public function setHeight(int $height): void {
        $this->height = $height;
        $this->width = $height;
    }
}

function printArea(Rectangle $rect): void {
    $rect->setWidth(5);
    $rect->setHeight(10);
    echo $rect->getArea(); // Ожидаем 50, но для Square получаем 100!
}
?>

Хороший пример (соблюдение LSP)

<?php
interface Shape {
    public function getArea(): int;
}

class Rectangle implements Shape {
    protected $width;
    protected $height;

    public function __construct(int $width, int $height) {
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea(): int {
        return $this->width * $this->height;
    }
}

class Square implements Shape {
    protected $side;

    public function __construct(int $side) {
        $this->side = $side;
    }

    public function getArea(): int {
        return $this->side * $this->side;
    }
}

function printArea(Shape $shape): void {
    echo $shape->getArea();
}

// Теперь оба класса работают корректно
printArea(new Rectangle(5, 10));  // 50
printArea(new Square(5));         // 25
?>

4. Interface Segregation Principle (ISP) — принцип разделения

Блоги

Рекомендаций по книгам