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

Как получалось следовать принципам SOLID в Yii2?

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

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

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

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

Принципы SOLID в практике разработки на Yii2

Работа с Yii2 Framework предоставляет как естественные возможности для соблюдения принципов SOLID, так и определенные сложности, связанные с архитектурными особенностями фреймворка. На практике следование этим принципам часто было контекстным и зависело от типа компонента, масштаба проекта и уровня контроля над архитектурой.

Взаимодействие с принципами и реализация

1. Single Responsibility Principle (SRP)

Yii2 в целом способствует соблюдению SRP благодаря своей компонентной архитектуре.

  • Контроллеры: По умолчанию они часто нарушают SRP, так как объединяют обработку HTTP-запросов, бизнес-логику и взаимодействие с моделью. Для соблюдения принципа мы внедряли сервисы (Service Layer) или действия (Actions), выделяя конкретные операции в отдельные классы.

    // Нарушение SRP (в контроллере)
    class UserController extends Controller {
        public function actionRegister($data) {
            // Валидация, сохранение, отправка email...
        }
    }
    
    // Соблюдение SRP (использование отдельного сервиса)
    class UserRegistrationService {
        public function register(UserDto $dto): User {
            // Только бизнес-логика регистрации
        }
    }
    
  • Модели (ActiveRecord): Здесь ситуация сложнее. ActiveRecord по своей сути объединяет ответственность за представление данных (сущность), доступ к данным (DAO) и часто бизнес-логику. Мы старались разделять эти ответственности:

    *   Использование **DTO** или **форм** (Model Forms) для валидации входных данных.
    *   Выделение сложной бизнес-логики в отдельные **сервисы** или **доменные объекты**.
    *   Создание **Repository**-классов для абстрагирования сложных запросов, оставляя в ActiveRecord только базовые CRUD операции.

2. Open/Closed Principle (OCP)

Yii2 предоставляет мощные механизмы для расширения через события (events) и поведения (behaviors).

  • Поведения (Behaviors) — это классический пример OCP. Они позволяют добавлять новую функциональность к классу (например, к ActiveRecord) без его модификации.

    class TimestampBehavior extends Behavior {
        public function events() {
            return [
                ActiveRecord::EVENT_BEFORE_INSERT => 'setTimestamps',
            ];
        }
        public function setTimestamps($event) {
            $this->owner->created_at = time();
        }
    }
    
    // Использование: "открыто для расширения, закрыто для изменения"
    class User extends ActiveRecord {
        public function behaviors() {
            return [
                TimestampBehavior::class,
            ];
        }
    }
    
  • Компоненты и конфигурация: Широкое использование DI-контейнера Yii2 и конфигураций через массивы позволяет заменять или декорировать стандартные компоненты (например, yii\db\Connection) без изменения кода приложения.

3. Liskov Substitution Principle (LSP)

Принцип соблюдается при работе с интерфейсами фреймворка и создании собственных наследников.

  • Стандартные классы: При замене, например, стандартного yii\web\Response на собственный класс, необходимо гарантировать, что все его публичные методы будут работать ожидаемым образом. Yii2 предоставляет четкие контракты базовых классов.
  • Собственные наследники: Главная опасность нарушения LSP возникала при глубоком наследовании от ActiveRecord или Controller и изменении поведения базовых методов (например, save() или runAction()). Мы минимизировали такие переопределения, предпочитая композицию (поведения, сервисы) над наследованием.

4. Interface Segregation Principle (ISP)

Yii2 не активно поощряет использование мелких интерфейсов, но это возможно на уровне собственной архитектуры.

  • Фреймворк часто использует большие интерфейсы (например, yii\db\ConnectionInterface), но они обычно хорошо сфокусированы.
  • На практике мы создавали специфичные интерфейсы для репозиториев, сервисов или стратегий, чтобы клиентские классы зависели только от необходимых им методов.
    // Вместо одного большого интерфейса UserServiceInterface
    interface UserFinderInterface {
        public function findById(int $id): ?User;
    }
    interface UserRegistrationInterface {
        public function register(UserDto $dto): User;
    }
    

5. Dependency Inversion Principle (DIP)

Это принцип, которому Yii2 способствует наиболее сильно благодаря своему мощному Dependency Injection Container.

  • Контроллеры и сервисы: Вместо жесткого создания зависимостей внутри класса, мы объявляли их через конструктор или свойства, и контейнер обеспечивал инъекцию.

    class OrderService {
        private PaymentProcessorInterface $processor;
    
        // DIP: зависимость от абстракции, инъекция через контейнер
        public function __construct(PaymentProcessorInterface $processor) {
            $this->processor = $processor;
        }
    }
    
  • Конфигурация приложения: Определение зависимостей в конфигурационном файле (config/web.php) — это прямое применение DIP, где указываются конкретные реализации для абстрактных типов.

    'container' => [
        'definitions' => [
            PaymentProcessorInterface::class => StripePaymentProcessor::class,
        ],
    ],
    

Сложности и компромиссы

  1. ActiveRecord как противоречивый элемент: Его удобство часто конфликтует с SOLID, особенно с SRP и DIP (жесткая связь с базой данных). В крупных проектах мы постепенно выносили логику в репозитории и сервисы, используя ActiveRecord лишь как простую DAO-обертку.
  2. "Магические" методы и свойства: Широкое использование __get(), __set() в моделях и поведениях иногда затрудняло анализ зависимостей и нарушало явность контрактов, что могло конфликтовать с LSP.
  3. Монолитные контроллеры: Базовый подход Yii2 к контроллерам требовал дисциплины для разделения ответственности. Мы активно использовали отдельные классы Action, сервисы и формные объекты для соблюдения SRP.

Выводы из практики

Следование SOLID в Yii2 не было автоматическим, но фреймворк предоставлял достаточно инструментов (DI-контейнер, поведения, события) для их реализации. Ключевым моментом была архитектурная дисциплина разработчиков. В успешных проектах мы:

  • Максимально использовали DI-контейнер для управления зависимостями (DIP).
  • Выделяли бизнес-логику из контроллеров и моделей в сервисы и доменные объекты (SRP).
  • Использовали поведения и события для расширения функциональности (OCP).
  • Минимизировали глубокое наследование, предпочитая композицию и интерфейсы.

Таким образом, Yii2 позволял строить поддерживаемые системы, соответствующие SOLID, но требовал сознательного выбора архитектурных паттернов над использованием "стандартного" пути фреймворка.