Модули
=======

Модули - это законченные программные блоки, состоящие из [моделей](structure-models.md), [представлений](structure-views.md), [контроллеров](structure-controllers.md) и других вспомогательных компонентов. При установке модулей в [приложение](structure-applications.md), конечный пользователь получает доступ к их контроллерам. По этой причины модули часто рассматриваются как миниатюрные приложения. В отличии от [приложений](structure-applications.md), модули нельзя развертывать отдельно. Модули должны находиться внутри приложений.


## Создание модулей <a name="creating-modules"></a>

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

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


### Классы модулей <a name="module-classes"></a>

Каждый модуль объявляется с помощью уникального класса, который наследуется от [[yii\base\Module]]. Этот класс должен быть помещен в корне [[yii\base\Module::basePath|базового пути]] модуля и поддерживать [автозагрузку](concept-autoloading.md). Во время доступа к модулю будет создан один экземпляр соответствующего класса модуля. Как и [экземпляры приложения](structure-applications.md), экземпляры модулей нужны, чтобы код модулей мог получить общий доступ к данным и компонентам.

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

```php
namespace app\modules\forum;

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

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

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

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

При этом в конфигурационном файле `config.php` может быть код следующего вида, аналогичный [конфигурации приложения](structure-applications.md#application-configurations):

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


### Контроллеры в модулях <a name="controllers-in-modules"></a>

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

```php
namespace app\modules\forum\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    // ...
}
```

Изменить пространство имен классов контроллеров можно задав свойство [[yii\base\Module::controllerNamespace]]. Если какие-либо контроллеры выпадают из этого пространства имен, доступ к ним можно осуществить, настроив свойство [[yii\base\Module::controllerMap]], аналогично тому, [как это делается в приложении](structure-applications.md#controller-map).


### Представления в модулях <a name="views-in-modules"></a>

Представления модуля также следует поместить в в поддиректорию `views` [[yii\base\Module::basePath|базового пути]] модуля. Виды, которые рендерит контроллер модуля, должны располагаться в директории `views/ControllerID`, где `ControllerID` соответствует [идентификатору контроллера](structure-controllers.md#routes). Например, если контроллер реализуется классом `PostController`, представления следует разместить в поддиректории `views/post` [[yii\base\Module::basePath|базового пути]] модуля.

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


## Использование модулей <a name="using-modules"></a>

Чтобы задействовать модуль в приложении, достаточно включить его в свойство [[yii\base\Application::modules|modules]] в конфигурации приложения. Следующий код в [конфигурации приложения](structure-applications.md#application-configurations) задействует модуль `forum`:

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

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


### Маршруты <a name="routes"></a>

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


### Получение доступа к модулям <a name="accessing-modules"></a>

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

```php
$module = MyModuleClass::getInstance();
```

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

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

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

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

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

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

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

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


### Предзагрузка модулей <a name="bootstrapping-modules"></a>

Может потребоваться запускать некоторые модули при каждом запросе. Модуль [[yii\debug\Module|debug]] - один из таких модулей. Для этого список идентификаторов таких модулей необходимо указать в свойстве [[yii\base\Application::bootstrap|bootstrap]] приложения.

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

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

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


## Вложенные модули <a name="nested-modules"></a>

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

```php
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`.

> Информация: Метод [[yii\base\Module::getModule()|getModule()]] возвращает только те дочерние модули, которые принадлежат родительскому модулю непосредственно. В свойстве [[yii\base\Application::loadedModules]] содержится список загруженных модулей, в том числе прямых и косвенных потомков, с индексированием по имени класса.


## Лучшие практики<a name="best-practices"></a>

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

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