Если вы когда-нибудь пытались добавить новую функцию в монолитное Yii2-приложение, вы знаете этот ужас: один контроллер на 2000 строк, вьюхи, которые никто не решается тронуть, и миграции, которые конфликтуют друг с другом. Модульная архитектура — это не серебряная пуля, но она превращает хаос в управляемую систему. В этой статье я покажу, как построить модульное приложение на Yii2, чтобы каждая фича жила своей жизнью, а вы могли спокойно спать.

Digital-студия WNDER

Что такое модуль в Yii2 и зачем он нужен

Модуль в Yii2 — это мини-приложение внутри вашего основного приложения. У него есть свои контроллеры, модели, вьюхи, миграции и даже конфигурация. Представьте, что вы строите дом. Вместо того чтобы лепить всё из одного куска глины, вы собираете его из блоков: кухня, спальня, ванная. Каждый блок можно переставить, заменить или выбросить, не разрушая весь дом.

Главная выгода модулей — изоляция. Если вы пишете интернет-магазин, то корзина, каталог, личный кабинет и админка могут быть отдельными модулями. Вы меняете логику корзины — не трогаете каталог. Вы удаляете модуль отзывов — остальное продолжает работать.

Правило: Если функциональность можно описать одним предложением и она не зависит напрямую от других частей — это кандидат в модуль. Не дробите слишком мелко, но и не пихайте всё в один модуль.

Структура модуля: где что лежит

Стандартная структура модуля в Yii2 выглядит так:

modules/
    shop/
        Module.php
        controllers/
            CartController.php
            ProductController.php
        models/
            Cart.php
            Product.php
        views/
            cart/
                index.php
            product/
                view.php
        migrations/
            m000000_000000_create_cart_table.php
        assets/
            ShopAsset.php

Всё просто: Module.php — это точка входа, где вы настраиваете модуль. Контроллеры, модели и вьюхи — как в обычном приложении, только с учётом пространства имён. Пространство имён для модуля shop будет app\modules\shop.

Пример Module.php:

<?php

namespace app\modules\shop;

use yii\base\Module as BaseModule;

class Module extends BaseModule
{
    public $controllerNamespace = 'app\modules\shop\controllers';

    public function init()
    {
        parent::init();
        // Здесь можно настроить компоненты модуля
        // Например, свой компонент корзины
        $this->components = [
            'cart' => [
                'class' => 'app\modules\shop\components\Cart',
            ],
        ];
    }
}

Теперь, чтобы использовать модуль, его нужно подключить в конфигурации приложения:

'modules' => [
    'shop' => [
        'class' => 'app\modules\shop\Module',
    ],
],

После этого все контроллеры модуля доступны по URL /shop/cart/index и так далее.

Как избежать конфликтов и сделать модули независимыми

Самая большая головная боль — зависимости между модулями. Если модуль заказов напрямую дёргает модели модуля каталога, то при удалении каталога всё сломается. Чтобы этого избежать, используйте события и интерфейсы.

Например, модуль каталога может генерировать событие ProductCreated, а модуль заказов — слушать его и делать что-то своё. Такой подход называется слабой связностью.

Пример генерации события в модели Product:

// в модели Product
const EVENT_PRODUCT_CREATED = 'productCreated';

public function afterSave($insert, $changedAttributes)
{
    parent::afterSave($insert, $changedAttributes);
    if ($insert) {
        $this->trigger(self::EVENT_PRODUCT_CREATED);
    }
}

А в модуле заказов можно подписаться на это событие через конфигурацию:

// в конфиге приложения
'components' => [
    'eventManager' => [
        'class' => 'app\components\EventManager',
    ],
],
// и где-то в bootstrap
Event::on(
    'app\modules\catalog\models\Product',
    'productCreated',
    function ($event) {
        // логика модуля заказов
    }
);
Лайфхак: Если модули должны обмениваться данными, создайте общий интерфейс в отдельной папке common\interfaces. Тогда модули будут зависеть только от интерфейса, а не от конкретной реализации.

Автоматическая регистрация модулей через конфиг

Когда модулей становится больше десяти, вручную прописывать их в конфиге надоедает. Можно сделать автоматическую загрузку: сканировать папку modules и подключать все найденные модули. Но осторожно: это может замедлить приложение, если модулей сотни. Лучше использовать кэширование.

Пример функции для автоматической регистрации:

function getModules()
{
    $modules = [];
    $dir = __DIR__ . '/modules';
    foreach (scandir($dir) as $item) {
        if ($item === '.' || $item === '..') continue;
        $moduleClass = 'app\\modules\\' . $item . '\\Module';
        if (class_exists($moduleClass)) {
            $modules[$item] = ['class' => $moduleClass];
        }
    }
    return $modules;
}

// в конфиге
'modules' => getModules(),

Этот код можно улучшить, добавив кэширование через Yii::$app->cache. Но для начала сойдёт и так.

Практические советы и таблица сравнения

Вот несколько проверенных рекомендаций, которые сэкономят вам нервы:

  • Не делайте модули слишком мелкими. Если модуль содержит один контроллер и одну модель, возможно, это просто часть другого модуля.
  • Используйте миграции внутри модуля. Yii2 поддерживает миграции в модулях, если указать путь в конфиге migrationPath.
  • Для ассетов (CSS, JS) создавайте отдельный AssetBundle в папке assets модуля.
  • Не забывайте про права доступа: настраивайте RBAC для каждого модуля отдельно.

Сравним два подхода к организации кода: монолит и модули.

Критерий Монолит Модульная архитектура
Скорость разработки Быстро на старте, медленно при росте Медленнее на старте, но стабильно быстро потом
Изоляция кода Всё в куче Каждый модуль в своей папке
Тестирование Сложно тестировать отдельные части Можно тестировать модуль отдельно
Повторное использование Практически невозможно Легко перенести в другой проект

Ещё один важный момент: конфигурация модуля. Вы можете передавать параметры в модуль через конфиг приложения. Например, для модуля оплаты можно указать ключи API:

'modules' => [
    'payment' => [
        'class' => 'app\modules\payment\Module',
        'apiKey' => 'your-key',
        'sandbox' => true,
    ],
],

А в самом модуле объявить публичные свойства $apiKey и $sandbox.

Что в итоге

Модульная архитектура в Yii2 — это не догма, а инструмент. Начинайте с малого: выделите один модуль, например, админку или API. Постепенно переносите туда логику. Используйте события для связи между модулями, не бойтесь создавать общие интерфейсы. И помните: главное — не количество модулей, а их независимость и понятность. Если через полгода вы откроете проект и сразу поймёте, где что лежит, — вы всё сделали правильно.

Студия WNDER