《应用Yii1.1和PHP5进行敏捷Web开发》

第十二章:迭代9:添加管理模块

目前为止我们已经为TrackStar应用程序添加了很多功能。回想一下第八章,我们介绍了如何使用用户的角色等级体系来限制用户访问一些功能。这很好的帮助我们限制了对一些基于单一项目的管理功能的访问。例如,在某个项目中,你并不想每一个人都拥有删除权限。我们为用户添加基于某一项目的特殊角色来实现基于角色的用户控制,然后控制该角色是否拥有访问该功能的权限。

然而,到现在为止我们还没有提到的是整个应用程序的管理需求。类似TrackStar的web应用程序需要针对某一用户设置拥有所有管理权限(的超级管理员)。一个显著的例子就是,对系统的每一个用户拥有CRUD操作,而不仅仅是某个project(项目)。当前应用程序中的系统管理员(同超级管理员)应当拥有如下权限 : 可以登录,删除或者修改用户、project(项目),issue,管理所有评论,等等。同时,也包括一些我们添加的额外功能,例如,给所有用户发布站点公告信息,管理E-mail campaigns,打开/关闭当前应用程序功能,管理角色等级体系,更换站点主题,等等。因为管理员权限和用户权限的巨大不同,将该本次迭代的功能和应用程序分离是一个好想法。我们将通过在Yii的模块里完成这一功能来实现分离。

迭代计划

在本次迭代中,我们将会集中完成如下开发任务 :

  • 建立一个新模块来存放管理功能
  • 为管理员建立系统广播的能力,用户将在项目列表页看到该消息
  • 为模块添加新主题
  • 为系统消息建立新的数据库表
  • 为系统消息生成所有的CRUD操作
  • 只允许管理员用户访问新模块中的功能
  • 在项目列表页显示新系统消息

模块

模块是一个大型应用程序中的小型应用程序。模块拥有与应用程序相似的结构,包含模型,试图,控制器,和其他支持组件。但是,模块不可以单独作为一个应用程序出现,必须嵌入某一应用程序。

模块可以帮助你模块化实现你的应用程序。大型应用程序常划分为离散的应用程序来实现功能,这里离散的应用程序可以通过模块实现。站点功能类似添加用户论坛,用户博客,或者站点管理功能都是一些例子,可以从主功能上剥离,使得可以分离开发,并且可以很容易的在以后的项目中重用。我们将在项目的不同目录建立存放管理功能的模块。

建立一个模块

使用我们的老朋友Gii代码生成工具建立一个新模块是非常容易的。伴随着我们的URL的改变,应该通过http://localhost/trackstar/gii访问该工具。打开以后选择,左边菜单的Module Generator选项。你将会看到下面的画面 :

我们需要给该模块取一个唯一的名字。因为我们在创建一个管理模块,我们可以命名为admin。所以在Module ID内填入 admin,然后点击Preview按钮。如下图所示,它向你展示了所有将会被生成的文件,允许你在新建之前预览他们 :

然后点击Generate按钮,来生成所有文件。因为Web服务器进程需要写入权限,所以确保你的/protected文件夹对于该应用程序是可写入的。模块添加成功后,会看到下图的界面 :

让我们仔细观察一下自动生成的模块。一个模块在Yii中是以一个文件夹来组织的,该文件夹的名字就是模块的名字。默认的,所有模块都存放在 protected/modules目录下。每一个模块的结构都和我们的主应用程序很相似。命令行会为admin模块建立脚手架文件夹结构。因为这是我们第一个模块,顶级文件夹/protected/modules被建立了,所以admin文件夹被放置在里面。下面为我们展示了模块命令行为我们建立的所有文件夹和文件 :

Name of folderUse/contents
admin/ 
  AdminModule.phpthe module class file
  components/containing reusable user components
  controllers/containing controller class files
    DefaultController.phpthe default controller class file
  messages/stores message translations specific to the module
  models/containing model class files
  views/containing controller view and layout files
    default/containing view files for DefaultController
      index.phpthe index view file
    layouts/containing layout view files

一个模块中必须有模块类继承自CWebModule或它的子类。该模块类的名字由模块ID (在这里指admin) 和字符串Module联合生成。模块ID的首字母会大写。因此,在我们的案例中,我们的admin模块类文件被命名为AdminModule.php。模块类扮演了存储来自模块代码共享信息的中转地带。例如,我们可以使用CWebModule的params属性来存储模块的特定参数,使用components属性在模块级分享应用程序组件。该类在模块中的作用类似应用程序中的类对整个应用程序的作用。因此,CWebModule是对模块来说的,CWebApplication是对整个应用程序来说的。

使用一个模块

在看到建立成功的消息后,我们需要在应用程序中设置modules属性,才可以使用它。在添加gii模块到应用程序的时候,我们使用过该方法来允许我们使用Gii代码生成工具。我们对主配置文件 : protected/config/main.php进行配置,如下的高亮代码需要被修改 :

'modules'=>array( 
    'gii'=>array(
        'class'=>'system.gii.GiiModule', 
        'password'=>'iamadmin',
    ),
    'admin',
),

保存以上改变后,我们的新admin模块已经可以使用了。我们可以先通过以下地址访问一下http://localhost/trackstar/admin/default/index。该请求为我们展示的页面类似我们的主应用程序页,唯一的区别是我们应该在路由中添加moduleID(模块ID)。所以我们的路由应该是如下形式/moduleID/controllerID/actionID。我们URL请求/admin/default/index 应该解释为 admin模块的 default控制器的index方法。浏览该页面时,将看到如下画面 :

主题化一个模块

我们立刻能发现该view(视图)没有应用任何layout(布局)。有人可能会想到,控制器渲染view时使用了renderPartial() 而不是 render()方法。然而,打开默认的admin的controller文件,/protected/modules/admin/controllers/DefaultController.php,我们将会发现,实际上,使用了render()方法。因此,我们需要一个layout文件。

问题(Issue)是基本上所有的东西都被独立到模块中,包括默认layout文件路径。默认路径是 /protected/modules/[moduleID]/views/layouts,在这里moduleID应该是admin。我们可以看到该文件夹下没有任何文件,所以默认layout没有被使用。

在这里多讲一点。在上一次迭代中,我们实现了一个叫new的新主题。我们也可以通过新主题管理所有模块的view和layout view文件。如果要这样做,需要在适当的路径下添加主题文件包。文件夹的结构与预期的一样。大致上:/themes/[themeName]/views/[moduleID]/layouts/为layout文件,/themes/[themeName]/views/[moduleID]/[controllerID]/为对应controller的视图文件。

为了更清楚,让我们模拟一次admin模块调用view的过程。下面就是在admin模块的DefaultController.php文件中渲染$this->render('index')的过程 :

  1. 当render()被调用,与renderPartial()不同,它将会使用一个layout文件来修饰index.php view(试图)文件。我们的应用程序被配置为使用叫new的主题,所以将会使用位于该主题文件夹下的layout文件。我们的新模块的DefaultController类继承自应用程序组件Contorller.php,所以使用了column1作为特定$layout属性值。因为该值没有被重写,所以也是DefaultController的layout值。最终,当这些都在admin模块中完成后,Yii会首先查找一下的layout文件 : /themes/new/views/admin/layouts/column1.php。注意这里包含了admin(moduleID)。
  2. 该文件不存在,所以在模块内查找。如前面所说的,每一个模块都有特定的默认layout文件夹。所以,在这里将会查找 : /protected/modules/admin/views/layouts/column1.php。
  3. 该文件也不存在,所以layout将不会被使用。将会简单的渲染单独的index.php文件。但是我们定义了主题new,所以它会查找/themes/new/views/admin/default/index.php。
  4. 该文件还是不存在,所以它会查找该模块(AdminModule)内的DefaultContorller.php(控制器)的 /protected/modules/admin/views/default/index.php。

这解释了为什么http://localhost/trackstar/admin/default/index渲染没有layout。为了保证完成,和简单,让我们管理一下位于我们模块中default位置的view文件(注意不是new主题内)。让我们为admin模块应用我们为原始应用程序设计的主题,就是在使用新主题前的样子。这样的话我们的admin模块的页面将与应用程序页面不同,这样可以很好的提醒我们处于不同的功能模块,并且无需重新设计。

应用一个主题

首先让我们设置模块的默认layout值。我们在模块类的init()方法中设置模块级的配置,模块类位于:/protected/modules/admin/AdminiModule.php。打开此文件,并添加如下代码:

class AdminModule extends CWebModule 
{
    public function init() 
    {
        // this method is called when the module is being created
        // you may place code here to customize the module or the application
 
        // import the module-level models and components 
        $this->setImport(array(
            'admin.models.*', 
            'admin.components.*',
        ));
 
        $this->layout = 'main';
    } 
    ...

这样,如果我们没有特殊指定layout文件,所有的view都会调用位于/protected/modules/admin/views/layouts/下的 main.php文件。

现在,我们自然是需要创建这一文件。从主应用程序copy 2个layout文件 : /protected/views/layouts/main.php 和 /protected/views/layouts/column1.php,将他们都放到 /protected/modules/admin/views/layouts/文件夹。copy之后我们需要对其做一定修改。

首先修改column1.php文件。在beginContent()中去除对 /layouts/main的引用,修改后代码如下:

<?php $this->beginContent(); ?>
<div class="container"> 
    <div id="content">
        <?php echo $content; ?> 
    </div><!-- content -->
</div> 
<?php $this->endContent(); ?>

在没有指定的情况下调用beginContent(),将会使用模块默认的layout文件,而我们刚刚将其指定到新copy过来的main.php。

现在是时候修改main.php文件了。我们需要应用程序头部添加Admin Console文字,来提示这是应用程序的管理部分。我们也需要对菜单项进行修改,来添加指向管理首页的连接,同时也要添加返回主站点的连接。我们可以去除About和Contact连接,因为在管理部分不需要重复这些资料。修改后代码如下:

... 
<div class="container" id="page"> 
<div id="header">
    <div id="logo">
    <?php echo CHtml::encode(Yii::app()->name) . " Admin Console"; ?>
    </div>
</div><!-- header --> 
 
<div id="mainmenu">
<?php $this->widget('zii.widgets.CMenu',array( 
    'items'=>array(
        array('label'=>'Back To Main Site', 'url'=>array('/proj-ect')),
        array('label'=>'Admin', 'url'=>array('/admin/default/in-dex')),
        array('label'=>'Login', 'url'=>array('/site/login'), 'visible'=>Yii::app()->user->isGuest),
        array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('/site/logout'), 
            'visible'=>!Yii::app()->user->isGuest)), 
)); ?>
</div><!-- mainmenu -->
...

其他的部分无需修改。现在访问admin的模块页面,你会看到如下的画面:

如果我们点击Back To Main Site的连接,我们将被带回新主题的主应用程序。

限制对admin模块的访问

你可能会发现一个问题,所有用户,包括游客都可以访问该模块。但实际上我们建立admin模块只想将该功能提供给拥有管理员权限的用户。所以我们需要解决这一问题。

幸运的是,我们在第8章时已经完成了RBAC访问模型。那么我们只需要是指包含一个新管理员角色和对该角色的新访问许可。

如果你还记得,第8章时我们使用了Yii 脚本命令来实现RBAC结构的。我们需要继续这样来添加。因此,打开包含脚本命令的文件,/protected/commands/shell/RbacCommand.php并且添加如下内容 :

//create a general task-level permission for admins 
$this->_authManager->createTask("adminManagement", "access to the application administration functionality"); 
//create the site admin role, and add the appropriate permissions
$role=$this->_authManager->createRole("admin"); 
$role->addChild("owner"; $role->addChild("reader"); 
$role->addChild("member"); 
$role->addChild("adminManagement");
//ensure we have one admin in the system (force it to be user id #1) 
$this->_authManager->assign("admin",1);

当都修改完以后,我们需要通过命令行来更新数据库。打开yiic shell,执行rbac命令:

% cd Webroot/trackstar 
% protected/yiic shell 
>> rbac

当我们对RBAC模型的修改完成之后,我们可以添加访问检查到AdminModule::beforeControllerAction()方法,因此只有admin角色才可以访问admin模块:

public function beforeControllerAction($controller, $action) 
{
    if(parent::beforeControllerAction($controller, $action)) 
    {
        // this method is called before any module controller action is performed
        // you may place customized code here
        if( !Yii::app()->authManager->checkAccess("admin", Yii::app()- >user->id) )
        {
            throw new CHttpException(403,Yii::t('yii','You are not au- thorized to perform this action.'));
        }
        else {
            return true;
        }
    } 
    else
        return false;
}

这些都完成后,如果一个没有admin角色的用户试图访问,将会看到授权错误页面。例如,如果在未登录状态或你试图访问admin页面,你将看到如下结果:

每个用户都将遇到这个问题,因为并没有指派admin角色给任何用户。

现在我们可以在主应用程序菜单中添加到admin部分的连接了。这样,拥有管理权限的用户可以到达管理页面。作为一个提示,我们主应用程序的菜单位于对应主题的默认layout中,/themes/new/views/layouts/main.php。打开该文件,然后做如下修改:

<div id="mainmenu"> 
    <?php $this->widget('zii.widgets.CMenu',array(
        'items'=>array( 
            array('label'=>'Projects', 'url'=>array('/project')), 
            array('label'=>'About', 'url'=>array('/site/page', 'view'=>'about')), 
            array('label'=>'Contact', 'url'=>array('/site/contact')),
            array('label'=>'Admin', 'url'=>array('/admin/default/index'), 
                'visible'=>Yii::app()->authManager->checkAccess("admin", Yii::app()- >user->id)),
            array('label'=>'Login', 'url'=>array('/site/login'),
                'visible'=>Yii::app()->user->isGuest), 
            array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('/site/logout'), 
                'visible'=>!Yii::app()->user->isGuest) ),
    )); ?> 
</div><!-- mainmenu -->

现在,使用admin权限登录应用程序,我们在顶部导航中将看到一个新连接,它会帮我们转到admin部分。

添加系统级信息

因为一个模块可以被认为一个小型应用程序,为模块添加功能和之前为主应用添加功能时相似的。让我们为管理员添加一些功能:当用户第一次登录到应用程序时向用户显示系统级信息。

建立数据库中的表

与平时扩展新功能一样,我们需要一个地方存储数据。我们需要新建一张表来存储我们系统级信息。因为我们一向追求简单。下面是数据表的定义语句 :

CREATE TABLE `tbl_sys_message` 
(
    `id` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, 
    `message` TEXT NOT NULL, 
    `create_time` DATETIME, 
    `create_user_id` INTEGER,
    `update_time` DATETIME, 
    `update_user_id` INTEGER
)

请在trackstar_dev和trackstar_test 两个数据库中建立该表。

建立模型和CRUD脚手架

当表建立好以后,下一步就是使用Gii代码生成器来建立模型类了。我们将首先使用Model Generator来新建模型类,然后使用Crud Generator来完成对应脚手架操作。继续导航至Gii工具表单来添加一个新模型。这一次,我们在对一个模块的内容进行操作,我们需要指明模型的路径(这里应该是说基于模块的模型,先有模块后有模型)。按下图所示填充表单(当然,此处你的Code Template路径值将略有不同):

现在我们将使用相同的方式建立CRUD脚手架。在次指出,与以前唯一的不同就是这里需要指明所属模块为admin。在选择Crud Generator选项后,按下图填写Model Class和Controller ID:

这样就会告诉开发工具,我们的模型类是在admin模块下的,包括控制器类在内的其他相关文件也需要被生成在admin模块内。

通过点击Preview按钮后点击Generate按钮完成文件创建。下图是所有被创建的文件列表:

添加到新功能的连接

让我们在admin部分主导航菜单中添加导航至我们新添加消息功能的连接。打开当前模块包含导航内容的文件,/protected/modules/admin/views/layouts/main.php,并且在menu部件中添加下面的数组:

array('label'=>'System Messages', 'url'=>array('/admin/sysMessage/ index')),

为消息功能自动创建的controller和view文件使用了2列布局方式,所以我们可以在一下2件事中选一件来做:我们可以更新controller文件使之使用我们之前做好的单列layout(布局)文件,或者我们也可以添加一个新的2列布局文件到admin模块。后面的会很容易做到,也会更好看,因为所有的view文件都被设计成拥有一个自菜单项,里面存放了所有crud功能的连接,而该菜单项被放在右边的一列中。我们只需要完成下面列表中的步骤:

  1. 将主应用程序中的2列布局文件copy到admin模块中,即:复制/protected/views/layouts/column2.php到/protected/modules/admin/views/layouts/column2.php。
  2. 去除copy后得到的文件column2.php中,beginContent()方法的输入内容 /layouts/main。
  3. 修改SysMessage模型类,使之继承自TrackstarActiveRecord(如果你还记得,这将添加代码自动更新create_time/user和update_time/user属性)。更新SysMessageController控制器类使用新的位于模块文件夹内的column2.php布局文件,而不是主应用程序的。自动生成的代码中 $layout = ‘application.views.layouts.column2’,但我们只需要简单的改为 $layout=‘column2’。
  4. 因为我们继承自TrackstarActiveRecord,我们可以移除那些不需要的表单项,同时移除与之项目的模型类中的rules。在SysMessage::rules()方法中移除一下内容: array('create_user, update_user', 'numerical', 'integerOnly'=>true) 还有 array('create_time, update_time', 'safe').

最后一步不是必须的,但是一个好习惯是只验证用户输入的内容。

最后一个需要修改的地方是,更新我们的访问规则来实现只有admin角色的用户才可以访问该方法的需求。这也就是为什么我们在AdminModule::beforeControllerAction方法中使用RBAC模型的原因。我们可以移除全部的accessRules。但是,我们在这里通过更新它来实现需求,这是一个访问规则的联系,使用以下的代码替换SysMessageController::accessRules()方法:

public function accessRules() 
{
    return array( 
        array('allow', // allow only users in the 'admin' role access to our actions 
            'actions'=>array('index','view', 'create', 'update', 'admin', 'delete'), 
            'roles'=>array('admin'),
        ), 
        array('deny', // deny all users
            'users'=>array('*'),
        ),
    );
}

好,都改完以后,如果使用http://localhost/trackstar/admin/sysMessage/create访问新消息输入表单,我们将看到类似下图的界面:

在表单中填写 Hellow Users! This is your admin speaking… 后点击Submit按钮。应用程序会将你重定向至类似下图的新消息细节页:

向全体用户显示消息

现在我们已经将消息存入我们的系统中了,让我们将它显示给所有在应用程序主页的用户。

在应用程序级导入新模型类

为了在应用程序中随时访问我们新创建的模型,我们应当将其配置为应用程序的一部分。修改 protected/config/main.php文件内容如下:

// autoloading model and component classes 
'import'=>array(
    'application.models.*',
    'application.components.*',
    'application.modules.admin.models.*',
),

选择最新更新的消息

我们将会只显示一条最新更新的消息,通过数据表的update_time列来判断。因为我们希望将其显示在项目列表页,我们需要修改ProjectController::actionIndex()方法,添加以下高亮代码如下:

public function actionIndex() 
{
    $dataProvider=new CActiveDataProvider('Project');
 
    Yii::app()->clientScript->registerLinkTag( 
        'alternate',
        'application/rss+xml', 
        $this->createUrl('comment/feed'));
 
    //get the latest system message to display based on the update_time column  这行
    $sysMessage = SysMessage::model()->find(array(    //这行
        'order'=>'t.update_time DESC',    //这行
    ));     //这行
 
    if($sysMessage != null)    //这行
        $message = $sysMessage->message;    //这行
    else    //这行
        $message = null;    //这行
 
    $this->render('index',array( 
        'dataProvider'=>$dataProvider,
        'sysMessage'=>$message,    //这行
    ));
}

现在我们需要更新view文件来显示该内容。在views/project/index.php的<h1>Projects</h1>上添加如下代码:

<?php if($sysMessage != null):?> 
    <div class="sys-message">
        <?php echo $sysMessage; ?> 
    </div>
<?php endif; ?>

现在当我们浏览项目列表页(同时也是我们的应用程序首页),我们将会看到下图所示画面:

添加一些设计调整

好,这正是我们想要的,但是对用户来说消息并不突出。让我们修改css文件(/themes/new/css/main.css)来实现该功能 :

div.sys-message 
{
    padding:.8em; 
    margin-bottom:1em; 
    border:3px solid #ddd; 
    background:#9EEFFF; 
    color:#FF330A; 
    border-color:#00849E;
}

修改完之后,我们的消息会在页面上很突出。如下图:

或许有人会觉得这个设计在颜色上有点过了。如果用户整天都不得不盯着这样颜色丰富的信息看会很不舒服。除了修改颜色,我们可以在此处使用JavaScript脚本来实现5秒后消息就淡出。我们只在用户访问首页的时候显示该消息,因此可以防止用户花费过长的时间观看这样的消息。

我们将使这一切都很简单,因为Yii包含了强大的JavaScript框架jQuery.jQuery是一个开源JavaScript库,它使的DOM元素和JavaScript之间的交互变得简单.深入的介绍jQuery并非本书的目的,但是认真读一下相关文档,了解一些相关知识是非常值得的.因为Yii包含了jQuery,你可以直接在Yii的view文件中使用jQuery代码,Yii会为你自动包含jQuery库.

我们将使用应用程序的帮助组件CClientScript在余下的页面中注册jQuery代码.该组件将确保代码被放置在了适当的地方并且被充分的标记和格式化.

下面让我们添加用于淡出的JavaScript脚本, 用下面的代码替换vews/project/index.php中我们刚添加的代码 :

<?php if($sysMessage != null):?> 
    <div class="sys-message">
        <?php echo $sysMessage; ?> 
    </div>
<?php 
    Yii::app()->clientScript->registerScript(
        'fadeAndHideEffect',
        '$(".sys-message").animate({opacity: 1.0}, 5000). fadeOut("slow");'
    ); 
endif; ?>

现在如果我们刷新主程序列表页, 我们将会看到消息会在5秒后淡出. 通过查询jQuery API文本来添加更多的容易使用的jQuery代码到你的页面, API地址 http://api.jquery.com/category/effects/

最终,可以通过添加多一条系统消息来确保一切都正常工作. 因为该条消息获得了最新的update_time属性,所以它将成为唯一现实的那一条.

小结

在本次迭代中,我们介绍了Yii中模块的概念,并且实践了如何添加管理模块到网站.我们了解了如何添加一个新模块,如何使用一个新主题,如何在模块内添加功能,甚至如何使用已经存在的RBAC模型来控制对模块内功能的访问.我们也了解了如何使用jQuery来添加UI效果到应用程序.

在添加完管理接口后, 我们已经完成了应用程序的所有主要功能.尽管应用程序还是非常简单, 是时候将其发布出去了.下一次迭代中我们将聚焦如何将该程序放置到发布环境中.

评论 X

      友荐云推荐
      Copyright 2011-2014. YiiBook.com