Введение: почему SOLID — это фундамент качественного PHP-кода
Каждый PHP-разработчик рано или поздно сталкивается с ситуацией, когда небольшой проект превращается в "спагетти-код". Классы становятся перегруженными, изменения в одной части системы ломают другую, а тестирование превращается в кошмар. Именно для решения этих проблем Роберт Мартин (дядя Боб) сформулировал пять принципов SOLID. Эти принципы — не абстрактная теория, а практические инструменты, которые помогают создавать гибкий, поддерживаемый и тестируемый код на PHP.
В этой статье мы разберём каждый принцип SOLID: от теории до реальных примеров на PHP 8.x. Вы узнаете, как применять их в повседневной разработке и каких антипаттернов следует избегать.
1. Single Responsibility Principle (SRP) — принцип единственной ответственности
Теория
Принцип гласит: у класса должна быть только одна причина для изменения. Это означает, что каждый класс должен отвечать за одну конкретную функциональность или бизнес-логику.
Антипаттерн (нарушение SRP)
<?php
class OrderProcessor {
public function process(Order $order): void {
// 1. Валидация заказа
if (empty($order->getItems())) {
throw new \InvalidArgumentException('Order has no items');
}
// 2. Сохранение в БД
$db = new \PDO('mysql:host=localhost;dbname=shop', 'user', 'pass');
$stmt = $db->prepare('INSERT INTO orders ...');
$stmt->execute([...]);
// 3. Отправка email
mail($order->getCustomerEmail(), 'Заказ подтверждён', '...');
// 4. Логирование
file_put_contents('/var/log/orders.log', 'Order processed: '.$order->getId());
}
}
?>
Здесь класс OrderProcessor делает слишком много: валидирует, работает с БД, отправляет email, логирует. Нарушение SRP ведёт к тому, что изменение логики отправки email потребует изменения класса, отвечающего за обработку заказов.
Рефакторинг с соблюдением SRP
<?php
class 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 {
// Логика сохранения в БД
}
}
class EmailNotifier {
public function sendOrderConfirmation(Order $order): void {
// Логика отправки email
}
}
class Logger {
public function log(string $message): void {
// Логирование
}
}
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());
}
}
?>
Теперь каждый класс имеет единственную ответственность. Изменение логики email не затрагивает работу с БД.
2. Open-Closed Principle (OCP) — принцип открытости/закрытости
Теория
Программные сущности должны быть открыты для расширения, но закрыты для модификации. Другими словами, вы должны иметь возможность добавлять новую функциональность без изменения существующего кода.
Антипаттерн
<?php
class PaymentProcessor {
public function process(string $type, float $amount): void {
if ($type === 'credit_card') {
// Обработка кредитной карты
} elseif ($type === 'paypal') {
// Обработка PayPal
} elseif ($type === 'crypto') {
// Обработка криптовалюты
}
// При добавлении нового типа придётся менять этот класс!
}
}
?>
Решение через полиморфизм
<?php
interface PaymentMethod {
public function process(float $amount): void;
}
class CreditCardPayment implements PaymentMethod {
public function process(float $amount): void {
// Логика для кредитной карты
}
}
class PayPalPayment implements PaymentMethod {
public function process(float $amount): void {
// Логика для PayPal
}
}
class CryptoPayment implements PaymentMethod {
public function process(float $amount): void {
// Логика для криптовалюты
}
}
class PaymentProcessor {
public function process(PaymentMethod $method, float $amount): void {
$method->process($amount);
}
}
// Использование:
$processor = new PaymentProcessor();
$processor->process(new CreditCardPayment(), 100.0);
$processor->process(new CryptoPayment(), 0.5);
?>
Теперь добавление нового способа оплаты требует лишь создания нового класса, реализующего интерфейс PaymentMethod.
3. Liskov Substitution Principle (LSP) — принцип подстановки Барбары Лисков
Теория
Объекты в программе должны быть заменимы экземплярами их подтипов без нарушения корректности программы. Проще говоря: если у вас есть функция, работающая с родительским классом, она должна корректно работать и с любым его наследником.
Нарушение LSP
<?php
class Rectangle {
public function __construct(
protected int $width,
protected int $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!
}
?>