Modules

Modules are self-contained software units that consist of models, views, controllers, and other supporting components. End users can access the controllers of a module when it is installed in application. For these reasons, modules are often viewed as mini-applications. Modules differ from applications in that modules cannot be deployed alone and must reside within applications.

Creating Modules

A module is organized as a directory which is called the yii\base\Module::basePath of the module. Within the directory, there are sub-directories, such as controllers, models, views, which hold controllers, models, views, and other code, just like in an application. The following example shows the content within a module:

forum/
    Module.php                   the module class file
    controllers/                 containing controller class files
        DefaultController.php    the default controller class file
    models/                      containing model class files
    views/                       containing controller view and layout files
        layouts/                 containing layout view files
        default/                 containing view files for DefaultController
            index.php            the index view file

Module Classes

Each module should have a unique module class which extends from yii\base\Module. The class should be located directly under the module's yii\base\Module::basePath and should be autoloadable. When a module is being accessed, a single instance of the corresponding module class will be created. Like application instances, module instances are used to share data and components for code within modules.

The following is an example how a module class may look like:

namespace app\modules\forum;

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

        $this->params['foo'] = 'bar';
        // ...  other initialization code ...
    }
}

If the init() method contains a lot of code initializing the module's properties, you may also save them in terms of a configuration and load it with the following code in init():

public function init()
{
    parent::init();
    // initialize the module with the configuration loaded from config.php
    \Yii::configure($this, require __DIR__ . '/config.php');
}

where the configuration file config.php may contain the following content, similar to that in an application configuration.

<?php
return [
    'components' => [
        // list of component configurations
    ],
    'params' => [
        // list of parameters
    ],
];

Controllers in Modules

When creating controllers in a module, a convention is to put the controller classes under the controllers sub-namespace of the namespace of the module class. This also means the controller class files should be put in the controllers directory within the module's yii\base\Module::basePath. For example, to create a post controller in the forum module shown in the last subsection, you should declare the controller class like the following:

namespace app\modules\forum\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    // ...
}

You may customize the namespace of controller classes by configuring the yii\base\Module::$controllerNamespace property. In case some of the controllers are outside of this namespace, you may make them accessible by configuring the yii\base\Module::$controllerMap property, similar to what you do in an application.

Views in Modules

Views in a module should be put in the views directory within the module's yii\base\Module::basePath. For views rendered by a controller in the module, they should be put under the directory views/ControllerID, where ControllerID refers to the controller ID. For example, if the controller class is PostController, the directory would be views/post within the module's yii\base\Module::basePath.

A module can specify a layout that is applied to the views rendered by the module's controllers. The layout should be put in the views/layouts directory by default, and you should configure the yii\base\Module::$layout property to point to the layout name. If you do not configure the layout property, the application's layout will be used instead.

Console commands in Modules

Your module may also declare commands, that will be available through the Console mode.

In order for the command line utility to see your commands, you will need to change the yii\base\Module::$controllerNamespace property, when Yii is executed in the console mode, and point it to your commands namespace.

One way to achieve that is to test the instance type of the Yii application in the module's init() method:

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

Your commands will then be available from the command line using the following route:

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

Using Modules

To use a module in an application, simply configure the application by listing the module in the yii\base\Application::modules property of the application. The following code in the application configuration uses the forum module:

[
    'modules' => [
        'forum' => [
            'class' => 'app\modules\forum\Module',
            // ... other configurations for the module ...
        ],
    ],
]

Info: To connect console commands of your module, you also need to include it in the console application configuration

The yii\base\Application::modules property takes an array of module configurations. Each array key represents a module ID which uniquely identifies the module among all modules in the application, and the corresponding array value is a configuration for creating the module.

Routes

Like accessing controllers in an application, routes are used to address controllers in a module. A route for a controller within a module must begin with the module ID followed by the controller ID and action ID. For example, if an application uses a module named forum, then the route forum/post/index would represent the index action of the post controller in the module. If the route only contains the module ID, then the yii\base\Module::$defaultRoute property, which defaults to default, will determine which controller/action should be used. This means a route forum would represent the default controller in the forum module.

The URL manager rules for the modules should be added before yii\web\UrlManager::parseRequest() is fired. That means doing it in module's init() won't work because module will be initialized when routes were already processed. Thus, the rules should be added at bootstrap stage. It is a also a good practice to wrap module's URL rules with yii\web\GroupUrlRule.

In case a module is used to version API, its URL rules should be added directly in urlManager section of the application config.

Accessing Modules

Within a module, you may often need to get the instance of the module class so that you can access the module ID, module parameters, module components, etc. You can do so by using the following statement:

$module = MyModuleClass::getInstance();

where MyModuleClass refers to the name of the module class that you are interested in. The getInstance() method will return the currently requested instance of the module class. If the module is not requested, the method will return null. Note that you do not want to manually create a new instance of the module class because it will be different from the one created by Yii in response to a request.

Info: When developing a module, you should not assume the module will use a fixed ID. This is because a module can be associated with an arbitrary ID when used in an application or within another module. In order to get the module ID, you should use the above approach to get the module instance first, and then get the ID via $module->id.

You may also access the instance of a module using the following approaches:

// get the child module whose ID is "forum"
$module = \Yii::$app->getModule('forum');

// get the module to which the currently requested controller belongs
$module = \Yii::$app->controller->module;

The first approach is only useful when you know the module ID, while the second approach is best used when you know about the controllers being requested.

Once you have the module instance, you can access parameters and components registered with the module. For example,

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

Bootstrapping Modules

Some modules may need to be run for every request. The debug module is such an example. To do so, list the IDs of such modules in the bootstrap property of the application.

For example, the following application configuration makes sure the debug module is always loaded:

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

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

Nested Modules

Modules can be nested in unlimited levels. That is, a module can contain another module which can contain yet another module. We call the former parent module while the latter child module. Child modules must be declared in the yii\base\Module::modules property of their parent modules. For example,

namespace app\modules\forum;

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

        $this->modules = [
            'admin' => [
                // you should consider using a shorter namespace here!
                'class' => 'app\modules\forum\modules\admin\Module',
            ],
        ];
    }
}

For a controller within a nested module, its route should include the IDs of all its ancestor modules. For example, the route forum/admin/dashboard/index represents the index action of the dashboard controller in the admin module which is a child module of the forum module.

Info: The getModule() method only returns the child module directly belonging to its parent. The yii\base\Application::$loadedModules property keeps a list of loaded modules, including both direct children and nested ones, indexed by their class names.

Accessing components from within modules

Since version 2.0.13 modules support tree traversal. This allows module developers to reference (application) components via the service locator that is their module. This means that it is preferable to use $module->get('db') over Yii::$app->get('db'). The user of a module is able to specify a specific component to be used for the module in case a different component (configuration) is required.

For example consider partial this application configuration:

'components' => [
    'db' => [
        'tablePrefix' => 'main_',
        'class' => Connection::class,
        'enableQueryCache' => false
    ],
],
'modules' => [
    'mymodule' => [
        'components' => [
            'db' => [
                'tablePrefix' => 'module_',
                'class' => Connection::class
            ],
        ],
    ],
],

The application database tables will be prefixed with main_, while all module tables will be prefixed with module_. Note that configuration above is not merged; the modules' component for example will have the query cache enabled since that is the default value.

Best Practices

Modules are best used in large applications whose features can be divided into several groups, each consisting of a set of closely related features. Each such feature group can be developed as a module which is developed and maintained by a specific developer or team.

Modules are also a good way of reusing code at the feature group level. Some commonly used features, such as user management, comment management, can all be developed in terms of modules so that they can be reused easily in future projects.