← Назад к вопросам

Используешь ли SOLID принципы на Yii2?

1.7 Middle🔥 201 комментариев
#Архитектура и паттерны#ООП#Фреймворки

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование SOLID принципов в проектах на Yii2

Да, я активно использую SOLID принципы при разработке на Yii2. Это не просто рекомендация, а фундаментальная практика, которая позволяет создавать устойчивый, гибкий и легко поддерживаемый код, даже в рамках специфической архитектуры фреймворка. Yii2, как и многие современные PHP-фреймворки, предоставляет структуры, которые естественно способствуют применению этих принципов, хотя иногда требуется дополнительная дисциплина, чтобы не нарушать их под давлением «быстрой разработки», которую Yii2 также поддерживает.

Как каждый принцип реализуется в контексте Yii2

S (Single Responsibility Principle — Принцип единственной ответственности)

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

  • Контроллеры: Их ответственность — обработка HTTP запроса, координация работы моделей, сервисов и возврат ответа. Любую бизнес-логику я немедленно вынося из контроллеров.
  • Модели (ActiveRecord): Их основная ответственность — представление данных, их валидация и взаимодействие с базой данных. Они не должны содержать сложную бизнес-логику или код, связанный с отправкой email.
  • Сервисные классы: Для бизнес-логики я создаю отдельные классы в директории services. Например, класс OrderService будет отвечать исключительно за процесс создания и обработки заказа.
// Пример нарушения SRP в контроллере (плохо)
class OrderController extends Controller
{
    public function actionCreate($data)
    {
        // 1. Валидация данных (ответственность валидатора)
        // 2. Сохранение в БД (ответственность модели/репозитория)
        // 3. Вызов платежной системы (ответственность платежного сервиса)
        // 4. Отправка email (ответственность почтового сервиса)
        // 5. Генерация отчета (ответственность сервиса отчетов)
        // ... Все в одном методе.
    }
}

// Пример соблюдения SRP (хорошо)
class OrderController extends Controller
{
    private $orderService;

    public function actionCreate($data)
    {
        try {
            $result = $this->orderService->createOrder($data);
            return $this->asJson($result);
        } catch (ValidationException $e) {
            // Обработка только ошибки валидации
        }
        // Контроллер отвечает только за поток запроса-ответа.
    }
}

O (Open/Closed Principle — Принцип открытости/закрытости)

Я добиваюсь этого через использование интерфейсов (interface) и компонентов (component), которые можно конфигурировать и заменять, а также через наследование и паттерн «Стратегия» (Strategy).

  • Компоненты приложения: Yii позволяет объявить компонент в конфигурации и затем легко переопределить его в config/web.php.
  • Поведения (Behaviors) и события (Events): Они позволяют расширять функциональность классов (например, моделей), не меняя их исходный код.
  • Интерфейсы для сервисов: Я определяю интерфейс для критических сервисов (например, PaymentGatewayInterface), что позволяет закрыть систему от изменений в реализации, но открыть для добавления новых платежных шлюзов.
// Интерфейс для соблюдения OCP
interface NotificationSenderInterface
{
    public function send(User $user, Message $message);
}

class EmailNotificationSender implements NotificationSenderInterface
{
    public function send(User $user, Message $message) { /* ... */ }
}

class SmsNotificationSender implements NotificationSenderInterface
{
    public function send(User $user, Message $message) { /* ... */ }
}

// В сервисе используется интерфейс, система закрыта для изменений сервиса,
// но открыта для добавления новых реализаций отправки.
class UserService
{
    private $notificationSender;

    public function __construct(NotificationSenderInterface $sender)
    {
        $this->notificationSender = $sender;
    }

    public function notifyUser(User $user)
    {
        $this->notificationSender->send($user, new Message('Hello!'));
    }
}

L (Liskov Substitution Principle — Принцип подстановки Лисков)

При работе с наследованием в Yii, особенно при создании собственных классов, расширяющих базовые (например, yii\db\ActiveRecord или yii\web\Controller), я тщательно проверяю, что:

  • Переопределенные методы не усиливают предварительные условия или ослабляют постусловия родительского метода.
  • Подкласс не выбрасывает исключения, которые не ожидаются от родительского класса.
  • Наиболее часто это контролируется при создании специализированных ActiveQuery или собственных базовых моделей.

I (Interface Segregation Principle — Принцип разделения интерфейсов)

Вместо создания монолитных интерфейсов я разрабатываю небольшие, специфичные интерфейсы. В Yii2 это особенно полезно для:

  • Сервисов, которые могут иметь разные клиенты (контроллеры, команды, другие сервисы).
  • Репозиториев, где интерфейс для чтения данных (UserReaderInterface) может быть отделен от интерфейса для записи (UserWriterInterface).
  • Это снижает зависимость клиентов от методов, которые они не используют.

D (Dependency Injection Principle — Принцип инверсии зависимостей)

Yii2 имеет превосходную поддержку Dependency Injection (DI) через свой контейнер зависимостей. Это один из самых сильных инструментов для соблюдения SOLID.

  • Я никогда не создаю зависимости напрямую внутри класса с помощью new.
  • Вместо этого я объявляю зависимости в конструкторе или через сеттер, а Yii Container автоматически их разрешает при создании объекта (особенно для контроллеров, действий, сервисов).
  • Это позволяет легко управлять зависимостями, заменять реализации (например, мок для тестирования) и соблюдать принципы O и I.
// Соблюдение DIP через DI контейнер Yii2
class ReportGeneratorService
{
    private $dataProvider;
    private $formatter;

    // Зависимости инвертированы: сервис зависит от абстракций (интерфейсов),
    // конкретные реализации внедряются из внешнего мира (контейнера).
    public function __construct(ReportDataProviderInterface $dataProvider, ReportFormatterInterface $formatter)
    {
        $this->dataProvider = $dataProvider;
        $this->formatter = $formatter;
    }

    public function generate($reportId)
    {
        $data = $this->dataProvider->getData($reportId);
        return $this->formatter->format($data);
    }
}

// Конфигурация в config/di.php или прямо в конфигурации компонента
Yii::$container->set('ReportDataProviderInterface', 'SqlReportDataProvider');
Yii::$container->set('ReportFormatterInterface', 'HtmlReportFormatter');

Практические преимущества и выводы

Применение SOLID в Yii2 приводит к:

  • Уменьшению связанности (coupling) между компонентами.
  • Увеличению тестируемости: Классы с четкими зависимостями легко покрывать модульными тестами.
  • Более простой поддержке и расширению: Добавить новую функциональность или изменить существующую становится менее рискованно.
  • Чистой архитектуре: Проект естественно организуется в слои (controllers, services, repositories, domain models).

Конечно, Yii2 с его активным использованием паттерна ActiveRecord иногда создает естественное напряжение с принципом SRP (модель знает о БД и содержит бизнес-правила). Для сложных проектов я часто использую гибридный подход или внедряю репозитории (Repository) и модели предметной области (Domain Model) рядом с ActiveRecord для сложной логики.

Таким образом, использование SOLID — это не вопрос «используешь или не используешь», а вопрос как эффективно адаптировать эти универсальные принципы к экосистеме и возможностям конкретного фреймворка, чтобы писать код, который остается чистым и адаптивным долгие годы. Yii2 предоставляет отличные инструменты (DI Container, Events, Behaviors, Components) для этого, и моя задача — использовать их максимально эффективно.