读书频道 > 网站 > 网页设计 > Android和PHP开发最佳实践(第2版)
3.6.4 框架MVC实例分析
15-09-06    下载编辑
收藏    我要投稿   
本书是国内第一本同时讲述Android客户端和PHP服务端开发的经典著作。本书以一个完整的微博应用项目实例为主线,由浅入深地讲解了Android客户端开发和PHP服务端开发的思路和技巧。从前期的产品设计、架构设计,到立即去当当网订购
通过前面两节的学习,大家应该对Hush Framework框架的理论基础有了一定的认识,但是理论还是需要通过实践来证明,要真正学会如何运用该框架进行开发,光靠理论知识是远远不够的,所以在本节中我们将会围绕着框架实例中与MVC分层开发相关的实例代码为大家做进一步的讲解。下面我们会使用框架实例中的“后台登录”这个界面的完整逻辑,来给大家讲解一下在Hush Framework中我们是如何使用MVC的分层思路来进行编程的,我们先来看一下这个界面的截图,如图3-17所示。
 
 
以上是在浏览器中打开框架实例的后台站点时看到的界面,也就是后台的登录界面。这里我们可以看到浏览器中的地址是“http://hf-be/auth/”,按照前面所介绍的Hush Framework的应用目录中所提及的,对应的控制器类应位于lib/Ihush/App/Backend/Page/AuthPage.php文件中。至此,既然已经找到分析框架MVC用法的“突破口”,那么我们就从这里开始分析吧。
 
我们先来看看AuthPage.php文件中的AuthPage类,此类继承自Ihush_App_Backend_Page类(即整个实例应用的后台控制器基类),其中包含多个以Action为后缀的方法,分别对应于“后台登录”界面的几个逻辑,整个类的写法都是面向对象的,大家可以对照前面3.1.4节的内容理解一下,关于其中涉及的MVC分层思路,下面我们会把这三层的相关代码提取出来,分别给大家讲解一下。
 
1. 控制器(Controller)
 
控制器简单来说就是页面的逻辑,在Hush Framework中我们通常使用Page(页面)来表示通常意义的Controller,因为对于网络应用来说Page比Controller更好理解;此外,我们还需要知道Hush Framework使用的是REST格式的URL路径结构,自域名之后的URL路径第一个表示的是控制器的名称,第二个则是控制器的动作,也就是我们通常所称的Action,比如登录界面的路径为“/auth/”,其对应的逻辑就可以在AuthPage.php文件中AuthPage类的indexAction方法中找到,这里需要注意的是当前路径如果为空,我们则会用index来代替。代码清单3-21就是AuthPage类的完整实现,大家可以参考注释来阅读代码。
 
代码清单 3-21
 
/**
 * @package Ihush_App_Backend
 */
class AuthPage extends Ihush_App_Backend_Page
{
public function indexAction () 
{
// TODO : 默认使用index.tpl作为Action的模板
}

public function loginAction () 
{
// 用户名/密码/验证码均不能为空
if (!$this->param('username') ||
!$this->param('password') ||
!$this->param('securitycode')) {
$this->addError('login.notempty');
}
// 验证码必须是正确的
elseif (strcasecmp($this->param('securitycode'),$this->session('securitycode'))) {
$this->addError('common.scodeerr');
}

// 通过参数验证
if ($this->noError()) {
// 使用DAO类的验证方法
$aclUserDao = $this->dao->load('Core_User');
$admin = $aclUserDao->authenticate($this->param('username'),
 $this->param('password'));
// 登录失败(找不到用户)
if (!$admin) {
$this->addError('login.nouser');
}
// 登录失败
elseif (is_int($admin)) {
$this->addError('login.failed');
} 
// 登录成功
else {
// 是否是超级用户
$admin['sa'] = strcasecmp($admin['name'], $this->sa)  false : true;
// 保存登录用户信息到会话
$this->session('admin', $admin);
// 跳转至首页
$this->forward($this->root);
}
}

// 登录失败则显示登录界面
$this->render('auth/index.tpl');
}

public function logoutAction ()
{
if ($this->session('admin')) {
// 用户登出,清除用户会话信息
$this->session('admin', '');
}

$this->forward($this->root);
}
}

 

从以上的代码中我们可以看出,在控制器类AuthPage中有三个Action方法,分别是indexAction、loginAction和logoutAction,这些方法对应的三个功能分别是“展示登录界面”、“用户登录逻辑”和“用户登出逻辑”,以上功能的基本逻辑和涉及的PHP语法这里就不做解释了,接下来我们会给大家分析一下这几个Action方法中比较重要的知识点,学习并理解这些知识点后,有助于我们分析AuthPage控制器类的代码。
 
(1)使用render方法展示模板
 
首先我们需要知道的是在Hush Framework中使用模板有两种方式:首先是默认方式,此种方式是按照“模板根目录/Controller名/Action名.tpl”这样的规则来放置的,因此对于indexAction来说我们可以根据这个规则分析出其对应模板是tpl/backend/template/auth目录下的index.tpl;另外一种方式是通过render手动设置模板,这也正是在loginAction中最后的那行代码所做的事情,可参考代码清单3-22中的写法。
 
代码清单 3-22
 
// 登录失败则显示登录界面
$this->render('auth/index.tpl');
 
这行代码的逻辑其实非常容易理解,就如同注释中描述的一样,当用户登录失败后,程序还是应该展示出登录页面给用户重新填写登录名和密码。
 
(2)使用param方法获取URL参数
 
param方法是开发中最常用的方法之一,该方法一般用于获得GET或者POST过来的URL参数;另外,如果这个函数带两个参数,则是设置对应URL参数的值。示例代码如代码清单3-23所示。
 
代码清单 3-23
 
// 获取GET或者POST过来的username参数值
$username = $this->param('username');
// 设置username参数值为james
$this->param('username', 'james');

 

 
(3)使用addError方法处理错误信息
 
大家都知道,在网页表单提交之后,我们会先做一些字段的判断,比如用户名和密码是否为空等,如果这些验证没有通过,就要给页面传递一些错误信息,而addError方法就是做这个事情的,比如前面实例中的代码“$this->addError('login.notempty');”就是用来显示错误信息的,另外对应的错误信息“login.notempty”我们可以在“etc/backend.errors.ini”文件中找到。
 
(4)使用load方法来加载DAO类
 
框架已经帮助我们把Controller层中如何使用Model层的方法封装好了,那就是这里所说的load方法,我们可以使用如下代码获取任意一个DAO类,如代码清单3-24所示。
 
代码清单 3-24
 
$aclUserDao = $this->dao->load('Core_User');
$admin = $aclUserDao->authenticate($this->param('username'), $this->param('password'));

 

 
以上代码取自于loginAction中的部分逻辑:首先,初始化了Core_User的DAO类供我们使用;然后,使用该类中的authenticate方法判断用户是否登录成功,这个地方的逻辑我们会在下面的模型层中做详细分析。
 
小贴士:前面提到的DAO是数据访问对象(Data Access Objects)的缩写,该对象常被用于进行数据库层面的各种数据操作,后面我们会经常提到。
 
(5)使用forward进行页面跳转
 
在一个互联网应用中,页面跳转是再常见不过的事情了,这里我们也能找到相应的示例代码,也就是登录成功之后跳转到首页的逻辑,如代码清单3-25所示。
 
代码清单 3-25
 
$this->session('admin', $admin);
$this->forward($this->root);
 
(6)使用session函数操控会话
 
会话的概念相信有些网络开发基础的朋友都应该清楚,因为HTTP是无状态的,所以我们一般会使用会话(Session)来保存用户相关的信息,在loginAction和logoutAction中我们都可以看到相关的代码,如代码清单3-26所示。
 
代码清单 3-26
 
// 保存登录用户信息到Session
$this->session('admin', $admin);
// 用户登出,清除用户会话信息
$this->session('admin', '');

 

2. 视图层(View)
 
视图层主要负责的是对应控制器逻辑的展示,一般来说是由HTML语法和Smarty变量构成的。根据前面介绍的关于Hush Framework中的两种模板使用方式,我们可以“顺藤摸瓜”找到indexAction对应的模板,也就是tpl/backend/template/auth中的index.tpl模板文件,如代码清单3-27所示。
 
代码清单 3-27
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>用户登录</title>
<link href="{$_root}css/main.css" rel="stylesheet" type="text/css" />
<link href="{$_root}css/login.css" rel="stylesheet" type="text/css" />
{literal}
<script type="text/javascript">
if(self!=top){top.location=self.location;}
</script>
{/literal}
</head>
<body>
<div class="login-body">
<div class="login-con">
<h1><img src="{$_root}img/logo_s.gif" /><span>后台管理系统</span></h1>
<div class="login">
{include file="frame/error.tpl"}
<form action="{$_root}auth/login" method="post">
<input type="hidden" name="go" value=""/>
<input type="hidden" name="do" value="login"/>
<ul>
<li>
<label>用户名:</label>
<input type="text" class="text" name="username"/>
</li>
<li>
<label>密 码:</label>
<input type="password" class="text" name="password"/>
</li>
<li>
<label>验证码:</label>
<input type="text" class="text" style="width: 50px;margin-right:5px;
text-transform: uppercase;" id="securitycode"
name="securitycode" autocomplete="off"/>
<img id="securityimg" src="{$_root}app/scode/image.php"
alt="看不清?单击更换" align="absmiddle"
style="cursor:pointer" onClick="this.src=this.src+''" />
</li>
<li>
<input type="submit" onclick="this.form.submit();"
class="submit" value="登录" name="sm1"/>
</li>
</ul>
</form>  
</div>
</div>
</div>
</body>
</html>

 

 
以上代码大部分是比较简单的HTML语法,穿插了一些Smarty的变量,比如“{$_root}”就是设置好的全局的Smarty的变量,代表项目URL的根路径,默认是“/”。另外,在该模板里我们还看到了登录表单的代码“<form action="{$_root}auth/login" method="post">”,这里我们可以发现该登录表单将会被提交至“/auth/login”路径,其对应逻辑就在前面我们分析过的控制层中AuthPage类的loginAction方法里。
 
3. 模型层(Model)
 
模型层是MVC三层中最接近数据库的一层,里面放的是数据操作的逻辑,也就是说我们常说的CRUD操作,这部分也是我们需要重点了解的。在前面的登录界面的示例中,我们了解到loginAction中使用到了DAO类Core_User里面的authenticate方法,下面我们截取Core_User里面的相关代码给大家讲解一下,见代码清单3-28。
 
小贴士:CRUD操作是添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)的缩写,也就是我们常说的“增删查改”方法,这几个操作基本包含了数据操作类DAO绝大部分的使用方式,后面我们也会经常提到。
 
代码清单 3-28
 
/**
 * @package Ihush_Dao_Core
 */
class Core_User extends Ihush_Dao_Core
{
/**
* 设置表名
* @static
*/
const TABLE_NAME = 'user';

/**
* 设置主键
* @static
*/
const TABLE_PRIM = 'id';

/**
* Initialize
*/
public function __init () 
{
$this->t1 = self::TABLE_NAME;
$this->t2 = Core_Role::TABLE_NAME;
$this->rsh = Core_UserRole::TABLE_NAME;

// 绑定常用CRUD操作
$this->_bindTable($this->t1);
}

/**
* 登录验证方法
* @uses Used by user login process
* @param string $user 用户名
* @param string $pass 密码
* @return bool or array
*/
public function authenticate ($user, $pass)
{
$sql = $this->select()
->from($this->t1, "*")
->where("name = ", $user);

$user = $this->dbr()->fetchRow($sql);

if (!$user['id'] || !$user['pass']) return false;

if (strcmp($user['pass'], Hush_Util::md5($pass))) return $user['id'];

$sql = $this->select()
 ->from($this->t2, "*")
 ->join($this->rsh, "{$this->t2}.id = {$this->rsh}.role_id", null)
 ->where("{$this->rsh}.user_id = ", $user['id']);

$roles = $this->dbr()->fetchAll($sql);

if (!sizeof($roles)) return false;

foreach ($roles as $role) {
 $user['role'][] = $role['id'];
 $user['priv'][] = $role['alias'];
}

return $user;
}
... 
}

 

接下来,我们来分析一下Core_User类中使用到的几个功能要点,并以此为实例给大家介绍一下Hush Framework中模型层的核心用法,也就是框架DAO基类中已经封装好的数据库常见操作的编码和使用。
 
(1)DAO类的初始化
 
在Hush Framework中使用DAO类,首先需要配置一个和数据表相对应的DAO类,这个过程我们通常称为DAO类的初始化。其实配置一个DAO类是非常方便的,因为框架DAO基类已经帮我们封装好了绝大部分DAO类所需要的逻辑和方法,所以初始化起来非常简单。代码清单3-29就是一个最简单的DAO类的范例模板。
 
代码清单 3-29
 
// DbName为数据库名
// TableName为数据表名
class DbName_TableName extends Dao_DbName {
// 配置表名
const TABLE_NAME = ' TableName ';
// 配置主键名
const TABLE_PRIM = 'PrimaryKey';
// 初始化操作
public function __init () {
// 绑定常用的CRUD操作
$this->_bindTable(TABLE_NAME);
}
}

 

 
我们可以看到,区区几行代码就已经把一个DAO类写好了。以上代码中的DbName表示数据库名,TableName表示表名,PrimaryKey则表示主键名,__init是初始化方法,__bindTable主要用于绑定CRUD方法,也就是说,初始化之后我们就可以直接使用这个DAO类来进行“增删查改”操作了。
 
(2)DAO类中的查询方法
 
查询应该是数据库最主要的用途之一,这里我们会重点讲解在Hush Framework的DAO类中使用查询的要点。从前面提到的Core_User数据操作类中的authenticate方法中我们可以看到在DAO类中经常使用到的查询(select)方法的使用范例,包括普通查询和表关联查询,示例见代码清单3-30。
 
代码清单 3-30
 
... 
// 普通查询
$sql = $this->select()
->from($this->t1, "*")
->where("name = ", $user);

$user = $this->dbr()->fetchRow($sql);
... 
// 表关联查询
$sql = $this->select()
->from($this->t2, "*")
->join($this->rsh, "{$this->t2}.id = {$this->rsh}.role_id", null)
->where("{$this->rsh}.user_id = ", $user['id']);

$roles = $this->dbr()->fetchAll($sql);

 

 
在Hush Framework中,我们可以使用和Zend Framework类似的方式来“拼装”数据库SQL查询语句,其代码语法还是比较容易理解的,我们可以把其中的select方法、from方法、where方法以及join方法分别理解为SQL语句中的SELECT、FROM、WHERE以及JOIN这几个关键词,理解起来会更加清晰。当然除了以上这几个方法之外,框架底层还提供了LIMIT、GROUP BY和ORDER BY等常用SQL语句的对应方法。比如代码清单3-31中列举的就是一些相对复杂的SQL语句所对应的PHP代码的写法。
 
代码清单 3-31
 
// 对应标准SQL:
// SELECT COUNT(id) AS count_id
//     FROM foo
//     GROUP BY bar, baz
//     HAVING count_id > "1"

$select = $db->select()
    ->from('foo', 'COUNT(id) AS count_id')
    ->group('bar, baz')
    ->having('count_id > ', 1);

// 对应标准SQL:
// SELECT * FROM round_table
//     ORDER BY noble_title DESC, first_name ASC

$select = $db->select();
    ->from('round_table', '*')
    ->order('noble_title DESC')
    ->order('first_name');

 

当然,我们需要理解Hush Framework的这种使用方法来替代SQL语句的做法,因为对于不同的数据库,查询语句区别是比较大的,如果没有一个很好的通用SQL语句的引擎很难做到良好的通用性,然而这却恰恰是本框架的优势所在;正是因为有底层的Zend_Db来提供强大的基础,才能让Hush Framework的模型层运转得更加得心应手。为了说明这点,我们以代码清单3-32为例,可以看到同样的DAO查询语句在不同的数据库中被解释成了不同的SQL;这样我们就不需要关心应用所使用的数据库类型,简便地写出通用型的代码,大大提高了模型层代码的重用性。
 
代码清单 3-32
 
// 在 MySQL/PostgreSQL/SQLite 中,对应 SQL 如下:
// SELECT * FROM foo
//     ORDER BY id ASC
//     LIMIT 10
//
// 在 Microsoft SQL 中,对应 SQL 如下:
// SELECT TOP 10 * FROM FOO
//     ORDER BY id ASC

$select = $db->select()
     ->from('foo', '*')
     ->order('id')
     ->limit(10);

 

 
此外,我们还需要注意一点,Hush Framework中的数据库类都是支持读写分离的,因此这里我们使用“dbr()->fetchRow(...)”方法(dbr是只读数据库db-read的缩写)来表示从“读库”中获取内容,一般来说数据查询操作中的绝大部分情况都会使用此方法;当然与之相对的,如果我们要写入数据,则应该使用dbw方法来操作“写库”,比如“dbw()->delete(...)”就是在“写库”中删除信息的写法。
 
(3)DAO类中的CRUD方法
 
前面我们已经介绍了DAO类中查询操作的用法,以及Hush Framework中对于数据库读写分离用法的使用要点,对于查询操作来说我们应该使用读库,但是对于CRUD中的其他几种操作来说就应该使用写库了,也就是使用“dbw()”方法进行调用,下面我们把除了select之外的几种方法给大家介绍一下。
 
 create方法:此方法用于创建数据,只要传入的是包含数据的散列数组,我们就可以在数据表中添加一条记录。这里需要注意的是,我们在CRUD方法中传递的数据格式经常是类似“array(key1=>value1,key2=>value2...)”格式的数组,key是键名,对应的是数据表的字段名,而value则是数据,代表的是对应键名的数据。
 
 exist方法:此方法用于来检测数据是否存在,一般来说我们可以传入主键值进行判断,当然如果我们需要根据其他字段的值来进行判断也是可以的,只需要在第二个参数传入对应字段名即可。
 
 read方法:此方法也是和主键相关的,用于读取与对应主键相关的数据行。因为此方法不需要组装SQL,使用起来比select方法简单许多,所以在获取与主键有关的数据行的情况下我们常用它来替代select方法。如果不使用主键,我们也可以在第二个参数传入对应字段名。
 
 update方法:此方法用于更新数据行,既可直接传入带主键的数组进行更新(此种情况将会按照主键值更新对应数据行)。当然,我们也可以在第二个参数传入where语句进行更新。
 
 delete方法:此方法用于删除数据行,与前面的update方法类似,我们既可直接传入主键值进行删除(此种情况将会按照主键值删除对应行)。当然,我们也可以在第二个参数传入对应字段名。
 
 replace方法:此方法用于替换数据行,在MySQL数据库中比较常用,一般我们替换的数据行也是和主键有关系的,或者是组合型主键。
 
到这里,我们已经把整个代码示例“登录界面”的逻辑介绍完了,同时也把Hush Framework中如何使用MVC的思路来进行编程的基本方法讲了一遍,现在大家应该对如何使用Hush Framework来进行开发心里有数了吧。由于Hush Framework是完全面向对象的,这里大家还可以学到许多PHP语言中的面向对象编程的技巧。当然最好的学习方法就是动手,我建议大家把框架的实例代码架设起来,然后直接动手边调试边学习,以达到“学以致用”的最佳效果。另外,关于如何获取Hush Framework框架源码以及如何部署源码实例的内容,我们会在附录A中给大家做详细介绍。
点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.3 功能
下一篇:1.5 小结
相关文章
图文推荐
JavaScript网页动画设
1.9 响应式
1.8 登陆页式
1.7 主题式
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站