Модули

Модули - это законченные программные блоки, состоящие из моделей, представлений, контроллеров и других вспомогательных компонентов. При установке модулей в приложение, конечный пользователь получает доступ к их контроллерам. По этой причине модули часто рассматриваются как миниатюрные приложения. В отличие от приложений, модули нельзя развертывать отдельно. Модули должны находиться внутри приложений.

Создание модулей

Модуль помещается в директорию, которая называется yii\base\Module::basePath модуля. Так же как и в директории приложения, в этой директории существуют поддиректории controllers, models, views и другие, в которых размещаются контроллеры, модели, представления и другие элементы. В следующем примере показано примерное содержимое модуля:

forum/
    Module.php                   файл класса модуля
    controllers/                 содержит файлы классов контроллеров
        DefaultController.php    файл класса контроллера по умолчанию
    models/                      содержит файлы классов моделей
    views/                       содержит файлы представлений контроллеров и шаблонов
        layouts/                 содержит файлы представлений шаблонов
        default/                 содержит файлы представления контроллера DefaultController
            index.php            файл основного представления

Классы модулей

Каждый модуль объявляется с помощью уникального класса, который наследуется от yii\base\Module. Этот класс должен быть помещен в корне yii\base\Module::basePath модуля и поддерживать автозагрузку. Во время доступа к модулю будет создан один экземпляр соответствующего класса модуля. Как и экземпляры приложения, экземпляры модулей нужны, чтобы код модулей мог получить общий доступ к данным и компонентам.

Приведем пример того, как может выглядеть класс модуля:

namespace app\modules\forum;

class Module extends \yii\base\Module
{
    public function init()
    {
        parent::init();

        $this->params['foo'] = 'bar';
        // ... остальной инициализирующий код ...
    }
}

Если метод init() стал слишком громоздким из-за кода, который задает свойства модуля, эти свойства можно сохранить в виде конфигурации, а затем загрузить в методе init() следующим образом:

public function init()
{
    parent::init();
    // инициализация модуля с помощью конфигурации, загруженной из config.php
    \Yii::configure($this, require __DIR__ . '/config.php');
}

При этом в конфигурационном файле config.php может быть код следующего вида, аналогичный конфигурации приложения:

<?php
return [
    'components' => [
        // список конфигураций компонентов
    ],
    'params' => [
        // список параметров
    ],
];

Контроллеры в модулях

При создании контроллеров модуля принято помещать классы контроллеров в подпространство controllers пространства имён класса модуля. Это также подразумевает, что файлы классов контроллеров должны располагаться в директории controllers yii\base\Module::basePath модуля. Например, чтобы описать контроллер post в модуле forum из предыдущего примера, класс контроллера объявляется следующим образом:

namespace app\modules\forum\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    // ...
}

Изменить пространство имен классов контроллеров можно задав свойство yii\base\Module::$controllerNamespace. Если какие-либо контроллеры выпадают из этого пространства имен, доступ к ним можно осуществить, настроив свойство yii\base\Module::$controllerMap, аналогично тому, как это делается в приложении.

Представления в модулях

Представления модуля также следует поместить в поддиректорию views yii\base\Module::basePath модуля. Виды, которые рендерит контроллер модуля, должны располагаться в директории views/ControllerID, где ControllerID соответствует идентификатору контроллера. Например, если контроллер реализуется классом PostController, представления следует разместить в поддиректории views/post yii\base\Module::basePath модуля.

В модуле можно задать шаблон, который будет использоваться для рендеринга всех представлений контроллерами модуля. По умолчанию шаблон помещается в директорию views/layouts, а свойство yii\base\Module::$layout должно указывать на имя этого шаблона. Если не задать свойство layout, модуль будет использовать шаблон, заданный в приложении.

Консольные команды в модулях

Ваш модуль также может объявлять команды, которые будут доступны через консоль.

Для того, чтобы команда стала доступна, надо изменить свойство yii\base\Module::$controllerNamespace для консольного режима так, чтобы оно содержало пространство имён ваших команд.

Этого можно добиться проверяя класс экземпляра приложения Yii в методе init модуля:

public function init()
{
    parent::init();
    if (Yii::$app instanceof \yii\console\Application) {
        $this->controllerNamespace = 'app\modules\forum\commands';
    }
}

Ваши команды будут доступны из командной строки как:

yii <module_id>/<command>/<sub_command>

Использование модулей

Чтобы задействовать модуль в приложении, достаточно включить его в свойство yii\base\Application::modules в конфигурации приложения. Следующий код в конфигурации приложения задействует модуль forum:

[
    'modules' => [
        'forum' => [
            'class' => 'app\modules\forum\Module',
            // ... другие настройки модуля ...
        ],
    ],
]

Info: Для подключения консольных команд вашего модуля, нужно также включить его в конфигурации консольного приложения

Свойству yii\base\Application::modules присваивается массив, содержащий конфигурацию модуля. Каждый ключ массива представляет собой идентификатор модуля, который однозначно определяет модуль среди других модулей приложения, а соответствующий массив - это конфигурация для создания модуля.

Маршруты

Как маршруты приложения используются для обращения к контроллерам приложения, маршруты модуля используются, чтобы обращаться к контроллерам этого модуля. Маршрут контроллера в модуле должен начинаться с идентификатора модуля, за которым следуют идентификатор контроллера и идентификатор действия. Например, если в приложении задействован модуль forum, то маршрут forum/post/index соответствует действию index контроллера post этого модуля. Если маршрут состоит только из идентификатора модуля, то контроллер и действие определяются исходя из свойства yii\base\Module::$defaultRoute, которое по умолчанию равно default. Таким образом, маршрут forum соответствует контроллеру default модуля forum.

Правила в URL manager для модулей должны быть добавленны перед началом работы yii\web\UrlManager::parseRequest(), что не позволяет размещать код добавления правил модуля в init(), так как инициализация происходит уже после обработки маршрутов. Таким образом, добавление маршрутов необходимо осуществить в предзагрузке модуля. Хорошей практикой, также, будет объединение всех правил модуля при помощи yii\web\GroupUrlRule.

Если же вы используете модуль для версионирования API, URL правила необходимо добавлять непосредственно в конфигурации urlManager приложения.

Получение доступа к модулям

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

$module = MyModuleClass::getInstance();

где MyModuleClass соответствует имени класса модуля, доступ к которому нужно получить. Метод getInstance() возвращает запрошенный в данный момент экземпляр класса модуля. Если модуль не запрошен, метод вернет null. Учтите, что обычно экземпляры класса модуля вручную не создаются, так как созданный вручную экземпляр будет отличаться от экземпляра, созданного Yii в качестве ответа на запрос.

Info: При разработке модуля нельзя исходить из предположения, что модулю будет назначен конкретный идентификатор. Это связано с тем, что идентификатор, назначаемый модулю при использовании в приложении или в другом модуле, может быть выбран совершенно произвольно. Чтобы получить идентификатор модуля, нужно вначале выбрать экземпляр модуля, как это описано выше, а затем получить доступ к идентификатору через свойство $module->id.

Доступ к экземпляру модуля можно получить следующими способами:

// получение дочернего модуля с идентификатором "forum"
$module = \Yii::$app->getModule('forum');

// получение модуля, к которому принадлежит запрошенный в настоящее время контроллер
$module = \Yii::$app->controller->module;

Первый подход годится только если известен идентификатор модуля, а второй подход наиболее полезен, если известно, какой контроллер запрошен.

Имея экземпляр модуля можно получить доступ к параметрам и компонентам, зарегистрированным в модуле. Например,

$maxPostCount = $module->params['maxPostCount'];

Предзагрузка модулей

Может потребоваться запускать некоторые модули при каждом запросе. Модуль debug - один из таких модулей. Для этого список идентификаторов таких модулей необходимо указать в свойстве bootstrap приложения.

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

[
    'bootstrap' => [
        'debug',
    ],

    'modules' => [
        'debug' => 'yii\debug\Module',
    ],
]

Вложенные модули

Модули могут вкладываться друг в друга без ограничений по глубине. Иными словами, в модуле содержится модуль, в который входит еще один модуль, и т. д. Первый модуль называется родительским, остальные - дочерними. Дочерние модули объявляются в свойстве yii\base\Module::modules родительских модулей. Например,

namespace app\modules\forum;

class Module extends \yii\base\Module
{
    public function init()
    {
        parent::init();

        $this->modules = [
            'admin' => [
                // здесь имеет смысл использовать более лаконичное пространство имен
                'class' => 'app\modules\forum\modules\admin\Module',
            ],
        ];
    }
}

Маршрут к контроллеру вложенного модуля должен содержать идентификаторы всех его предков. Например, маршрут forum/admin/dashboard/index соответствует действию index контроллера dashboard модуля admin, который в свою очередь является дочерним модулем модуля forum.

Info: Метод getModule() возвращает только те дочерние модули, которые принадлежат родительскому модулю непосредственно. В свойстве yii\base\Application::$loadedModules содержится список загруженных модулей, в том числе прямых и косвенных потомков, с индексированием по имени класса.

Лучшие практики

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

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