Yii2学习笔记系列14——Application Structure-Modules(应用结构之模块)

三 11 五月 2016

模块

模块是独立的软件单元,由模型、视图、控制器和其他支持组件组成,终端用户可以访问在应用主体中已安装的模块的控制器,模块被当成小应用主体来看待,但是和应用主体不同的是,模块不能被单独部署,必须属于某个应用主体。

创建模块

模块被组织成一个称为yii\base\Module::basePath的目录,在该目录中有子目录如controllersmodelsviews,分别对应控制器、模型、视图。如下是一个模型的目录结构:

forum/
    Module.php                   模块类文件
    controllers/                 包含控制器类文件
        DefaultController.php    default 控制器类文件
    models/                      包含模型类文件
    views/                       包含控制器视图文件和布局文件
        layouts/                 包含布局文件
        default/                 包含DefaultController控制器视图文件
            index.php            index视图文件
模块类

每个模块都有一个继承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子命名空间中,这也意味着要将控制器的类文件放在模块yii\base\Module::basePath目录中的controllers子目录中,例如,上小节中要在forum模块中创建post控制器,应该像如下声明控制器类一样:

namespace app\modules\forum\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    // ...
}

我们可以通过配置yii\base\Module::$controllerNamespace属性来实现自定义控制器类的命名空间,如果一些控制器不在该命名空间下,可以通过配置yii\base\Module::controllerMap属性让它们能被访问,这类似于我们在应用主体配置中所做的。

模块中的视图

视图应该放在模块的yii\base\Module::basePath对应目录下的views目录中,对于模块中控制器对应的视图文件应该放在views/ControllerID目录下,其中ControllerID对应控制器ID。例如,假设控制器类是PostController,目录对应模块yii\base\Module::basePath目录下的views/post```目录。

模块可以指定在模块控制器视图渲染中所使用的布局,该布局默认存放在view/layouts目录下,我们可以通过配置yii\base\Module::layout属性来指定布局名,如果没有配置该属性,默认使用应用的布局名。

模块中的控制台命令

我们的模块中可能也会声明一些控制台模式下可用的命令。

为了使得命令行实用程序(command line utility)能够识别到我们的命令,在Yii以命令行模式运行时,我们需要修改yii\base\Module::controllerNamespace属性,将其指向我们定义的命令的命名空间。

如下代码就是其中的一个实现方法,在模块的初始化方法中测试Yii应用程序的实例类型:

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',
            // ... 模块其他配置 ...
        ],
    ],
]

yii\base\Application::modules属性使用模块配置数组,每个数组的键为模块ID,它标识该应用中唯一的模块,数组的值为用来创建模块的配置。

路由

和访问应用的控制器类似,路由也被用在模块中对控制器进行寻址。模块中控制器的路由必须以模块ID开始,接下来是控制器ID和操作ID。例如,假设应用使用一个forum模块,路由forum.post/index代表模块中post控制器的index操作,如果路由只包含模块ID,默认为default的yii\base\Module::defaultRoute属性来决定使用哪个控制器/操作,也就是说路由forum可能代表forum模块的default控制器。

访问模块

在模块中,可能经常需要获取模块类的实例来访问模块ID、模块参数、模块组件等,我们可以使用如下的语句来获取module的实例:

$module = MyModuleClass::getInstance();

其中,MyModuleClass对应你想要的模块类,getInstance()方法返回当前请求的模块类实例,如果模块没有被请求,该方法会返回空。需要注意的是,这里不需要手动创建一个模块类的实例,因为手动创建的和Yii处理请求时自动创建的是不一样的。

补充说明:在开发模块时,我们不能假设模块是使用固定的ID的,因为在应用或者其他模块中,模块可能会对应到任意的ID,为了获取模块ID,应该使用上述代码获取模块实例,然后通过$module->id获取模块ID。

也可以使用如下方式访问模块实例:

// 获取ID为 "forum" 的模块
$module = \Yii::$app->getModule('forum');

// 获取处理当前请求控制器所属的模块
$module = \Yii::$app->controller->module;

第一种方式仅在我们明确知道模块ID的情况下有效,第二种方式在我们知道处理请求的控制器的情况下使用。

一旦获取到模块实例,我们就可以访问注册到模块的参数和组件,例如:

$maxPostCount = $module->params['maxPostCount'];
引导启动模块

有些模块在每个请求下都会运行,比如yii\debug\Module模块,为此,我们需要将这种模块加入到主体的yii\base\Application::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',
            ],
        ];
    }
}

在嵌套模块中的控制器,它的路由应该包含它所有的上级模块(Ancestor Module)的ID,例如forum/admin/dashboard/index代表在模块forum中子模块的admindashboard控制器的index操作。

最佳实践

模块在大型项目中常备使用,这些项目的特性可分组,每个组包含一些强相关的特性,每个特性组可以做成一个模块,由特定的开发人员和开发组来进行开发和维护。

在特性组上,使用模块也是重用代码的好方式,一些常用特性,如用户管理,评论管理,可以开发成模块,这样儿在相关项目中非常容易被重用。

Category: PHP Develop

Comments