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


