第21章 一个简单好用的MVC框架
MVC是一种源远流长的软件设计模式,早在20世纪70年代就已经出现了基于MVC的开发模式。随着Web应用开发的广泛展开,也因为Web应用需求复杂度的提高,MVC这一设计模式也渐渐被Web应用开发所采用。
随着Web应用的快速增加,MVC模式对于Web应用的开发来说无疑是一种非常先进的设计思想,无论选择哪种语言,也无论应用多复杂,它都能为构造产品提供清晰的设计框架。MVC模式使得Web应用更加强壮,更加有弹性,也更加个性化。
21.1 什么是MVC模型
MVC由Trygve Reenskaug提出,首先被应用在SmallTalk-80环境中,使许多交互和界面系统的构成基础,Microsoft的MFC基础类也遵循了MVC的思想。
MVC是Model_View_Control的缩写,简单地讲,Model即程序的数据或数据模型,View是程序视图界面,Control是程序的流程控制处理部分。
模型部件是软件所处理问题逻辑在独立于外在显示内容和形式情况下的内在抽象,封装了问题的核心数据、逻辑和功能的计算关系,它独立于具体的界面表达和I/O操作。
视图部件把表示模型数据及逻辑关系和状态的信息及特定形式展示给用户。它从模型获得显示信息,对于相同的信息可以有多个不同的显示形式或视图。
控制部件是处理用户与软件的交互操作的,其职责是控制提供模型中任何变化的传播,确保用户界面与模型间的对应联系;它接受用户的输入,将输入反馈给模型,进而实现对模型的计算控制,是使模型和视图协调工作的部件。通常一个视图具有一个控制器。
模型、视图与控制器的分离,使得一个模型可以具有多个显示视图。如果用户通过某个视图的控制器改变了模型的数据,所有其他依赖于这些数据的视图都应反映出这些变化。因此,无论何时发生了何种数据变化,控制器都会将变化通知给所有的视图,导致显示的更新。这实际上是一种模型的变化-传播机制。
21.2 MVC模型的组成
MVC是一个设计模式,它使Web应用程序的输入、处理和输出分开进行。MVC Web应用程序被分成3个核心部件:数据模型(Model—M)、视图(View—V)、控制器(Controller—C)。一个好的MVC设计,不仅可以使模型、视图、控制器高效完成各自的任务处理,而且可以让它们完美地结合起来,完成整个Web应用。
21.2.1 数据模型
模型包含了应用问题的核心数据、逻辑关系和计算功能,它封装了所需的数据,提供了完成问题处理的操作过程。控制器依据I/O的需要调用这些操作过程。模型还为视图获取显示数据而提供了访问其数据的操作。
这种变化-传播机制体现在各个相互依赖部件之间的注册关系上。模型数据和状态的变化会激发这种变化-传播机制,它是模型、视图和控制器之间联系的纽带。
21.2.2 视图
视图通过显示的形式,把信息转达给用户。不同视图通过不同的显示,来表达模型的数据和状态信息。每个视图有一个更新操作,它可被变化-传播机制所激活。当调用更新操作时,视图获得来自模型的数据值,并用它们来更新显示。
在初始化时,通过与变化-传播机制的注册关系建立起所有视图与模型间的关联。视图与控制器之间保持着一对一的关系,每个视图创建一个相应的控制器。视图提供给控制器处理显示的操作。因此,控制器可以获得主动激发界面更新的能力。
21.2.3 控制器
控制器通过时间触发的方式,接受用户的输入。控制器如何获得事件依赖于界面的运行平台。控制器通过事件处理过程对输入事件进行处理,并为每个输入事件提供了相应的操作服务,把事件转化成对模型或相关视图的激发操作。
如果控制器的行为依赖于模型的状态,则控制器应该在变化-传播机制中进行注册,并提供一个更新操作。这样,可以由模型的变化来改变控制器的行为,如禁止某些操作。
21.3 实现简单的MVC
在了解了何为MVC的概念之后,本节就来实现一个简单好用的MVC框架。记住,MVC只是一种思想,实现的途径可以有多种。不要拘束于某种现成的框架思想,希望在此讲述的这个MVC实现对读者有一个启蒙的作用。
21.3.1 数据模型层的实现
对于数据模型层的实现这里实现两部分的封装,第一是对数据库直接操作的封装(也就是通常所说的数据库操作类),第二个是基于这个类对数据库的操作。那为什么需要数据库操作类呢?那是因为在开发中对数据库的操作基本是必不可少的,而且需求变化也比较大,所以对其做一个封装便于灵活开发和应用。以下是对数据操作的一个封装类,如代码21-1所示。
代码21-1 数据库封装类
<?php class DB extends PDO { //此类扩展自PHP的PDO数据库操作类 var $sth; //用于存储PDOStatement对象 function execute($sql, $values = array()) { //执行SQL语句 $this->sth = $this->prepare($sql); //预执行SQL语句,可防止SQL注入 return $this->sth->execute($values); //执行SQL语句 } function get_all($sql, $values = array()) { //得到所有SELECT语句执行后的数据集 $this->execute($sql, $values); //执行本类的execute方法 return $this->sth->fetchAll(); //取得所有结果集 } function get_one($sql, $values = array()) { //得到一条SELECT语句执行后的数据集 $this->execute($sql, $values); //执行本类的execute方法 return $this->sth->fetch(); //取得一条结果集 } function get_col($sql, $values = array(), $column_number = 0) {//取得记录中的列值 $this->execute($sql, $values); //执行本类的execute方法 return $this->sth->fetchColumn($column_number); //取得结果中的某一列值 } function insert($table, $data) { //向数据表中插入数据 $fields = array_keys($data); //提取数据中的key值 $marks = array_fill(0, count($fields), '?'); //组成数据 //重新组合成插入的SQL语句 $sql = "INSERT INTO $table (`" . implode('`,`',$fields) . "`) VALUES (".implode(", ",$marks)." )"; return $this->execute($sql, array_values($data)); //执行本类的execute方法,并返回结果 } function update($table, $data, $where) { //更新数据表中的数据 $values = $bits = $wheres = array(); //建立数据 foreach ( $data as $k=>$v ) { //循环构建需要的数据参数 $bits[] = "`$k` = ?"; $values[] = $v; } if ( is_array( $where ) ) foreach ( $where as $c => $v ) { //循环构建需要的条件参数 $wheres[] = "$c = ?"; $values[] = $v; } else return false; //重新组合成更新的SQL语句 $sql = "UPDATE $table SET " . implode( ', ', $bits ) . ' WHERE ' . implode( ' AND ', $wheres ); return $this->execute($sql, $values); //执行本类的execute方法,并返回结果 } function delete($table, $field, $where) { //从数据表中删除结果集 if( empty($where) ) { //条件是否为空 return false; //返回FALSE } //预执行删除语句 $this->sth = $this->prepare("DELETE FROM $table WHERE $field = ?"); if( is_array($where) ) { //如果是数组 foreach($where as $key=>$val) { //循环需要删除的值 $this->sth->execute(array($val)); //执行本类的execute方法 } } else { //只有一个数值 $this->sth->execute(array($where)); //执行本类的execute方法 } } function table2sql($table) { //将数据表导出成SQL语句 $sql = array(); //创建临时数组 $sql[] = "DROP TABLE IF EXISTS `{$table}`;\n"; //如果存在则删除该数据表 $create_table = $this->get_one('SHOW CREATE TABLE '.$table);//返回表结构的SQL语句 $sql[] = $create_table[1].";\n\n"; //将如上语句存入临时数组中 return implode('', $sql); //以SQL形式返回所有的表信息 } function data2sql($table) { //将数据表数据导出成SQL语句 $sql = array(); //创建临时数组 $sql[] = "DROP TABLE IF EXISTS `{$table}`;\n"; //如果存在则删除该数据表 $create_table = $this->get_one('SHOW CREATE TABLE '.$table);//返回表结构的SQL语句 $sql[] = $create_table[1].";\n\n"; //将如上语句存入临时数组中 $rows = $this->get_all("SELECT * FROM $table"); //取得表中的所有数据 $col_count = $this->sth->columnCount(); //取得记录的个数 foreach($rows as $row) { //循环取得的数据 $sql[] = "INSERT INTO $table VALUES("; //创建insert语句 $comma = ''; for($i=0; $i< $col_count; $i++) { //循环记录中所有列 if (!isset($row[$i])) { //如果没有值 $sql[] = $comma."NULL"; //设置为NULL } else { //否则 $sql[] = $comma."'".$row[$i]."'"; //设置为当前值 } $comma = ','; //更改连接符为“,” } $sql[] = ");\n"; } $sql[] = "\n"; return implode('', $sql); //以SQL形式返回所有的数据信息 } function dump_sql() { //将数据库中的所有数据表导出成SQL形式字符串 $sql = array(); //创建临时数组 foreach ($this->query('SHOW TABLES') as $row) { //循环所有的表 $sql[] = $this->data2sql($row[0]); //调用data2sql导出数据表的SQL } return implode('', $sql); //返回集合后的SQL数据 } } ?>
以上的数据库操作类是在原有的PDO类上对数据库操作的一个封装,因为PDO类能支持多种类型的数据库并能有效防止SQL注入。首先来看该类的execute方法,此方法接受两个参数,一个是用来预执行的SQL语句,另外一个是其需要用到的数据。在函数中依次调用PDO的prepare方法和execute方法来执行SQL,类中对数据库操作的函数最终都会调用这个方法,这样就能有效地防止SQL注入。
此类中函数常用的一些SQL语句进行封装,比如insert、update、delete操作等,省去了写SQL语句的麻烦。并提供导出数据SQL的3个函数table2sql、data2sql、dump_sql,在备份数据库数据时有很大的用处。
所谓数据模型,就是对某一类操作进行的封装。当然实现的方法也是多样的,譬如用类的方式,或者使用函数放在一个独立的文件中,再或是使用命名空间标识出来等。这里一般采取的是以类的形式的封装,譬如有很多对用户数据的操作,就可以封装成一个用户名类。此用户类中包括对用户的登录、验证、添加、删除、更新资料等操作。大致可以如下:
<?php require_once('21-1.php'); //导入数据操作类 class User { //用户模型类 var $db; //用户数据库 function __construct() { //构造函数,链接数据库 $this->db = new DB("mysql:dbname=user;host=localhost", 'root', ''); } function login() { //用户登录 ... } function logout() { //用户注销 ... } function add_user() { //添加用户 ... } ... } ?>
这就是一个模型层了,它负责对用户的操作。
21.3.2 视图层的实现
一般实现视图层可以使用现有的模板引擎,来实现代码和数据的分离,比如现在流行的Smarty就非常不错。但是这里介绍一种更简单的方式,就是使用原生PHP做模板引擎。
使用原生PHP做模板显而易见的好处是“快”,不论使用什么模板引擎,到最终都是要还原成原生PHP代码的,这个过程都是需要处理时间的。使用原生PHP做模板引擎时就不会产生这个过程,所以很快。另外一个就是节省了二次学习的时间。现在几乎每个模板引擎都有自己的一套方式,要使用时就得重新学习它的方法。而使用原生PHP作为模板引擎时,只要会PHP就会使用。另外一个就是在使用某些IDE的时候能直接显示出来,比如Dreamweaver之类的编辑器。下面就来详细说明,使用方法如下。
变量输出:
<?php echo $test?>
或者可以更简单一些,如下所示。
<?= $test?>
判断语句:
<?php if ($a == 5):?> A等于5 <?php endif;?>
另外还可以直接使用三元运算符来控制判断输出。
<?= $a?$b:$c?>
循环语句:
<?php foreach($as as $a):?> 循环结果:<?= $a?> <?php endforeach;?>
模板引擎最基本的三种方式都已经有了。那么基本的数据显示方式都已经具备。难道模板就这么简单?是的,模板就可以这么简单,如果让做前台页面的人员看到这个他一定也会马上接受的。就这么三个语句,基本就不用学习!
21.3.3 控制器的实现
本小节来讲视图层和模型层的桥梁——控制器的实现。这里也是使用PHP本身的特性,直接页面接受请求,然后对请求的参数做判断并调用相应的模型。基本上用if条件语句判断即可。同样做一个用户操作的例子,结合以上的那个用户模型层示例,控制器的实现可以如下:
<?php require_once('cls_user.php'); //引入用户模型 $user = new User(); //模型类对象化 if($_REQUEST['act'] == 'login') { //判断请求是否是login if($user->login($_REQUEST['username'], $_REQUEST['password'])) { //调用模型层的login方法 echo '登录成功!'; //成功的结果 } else { echo '登录失败!'; //失败后的结果 } } elseif($_REQUEST['act'] == 'logout') { //如果是注销 $user->logout(); //调用模型层的logout方法 echo '注销成功!'; } elseif($_REQUEST['act'] == 'add_user') { //如果是新增用户 $user->add_user($_REQUEST['username'], $_REQUEST['password']); //调用模型层的 add_user 方法 echo '注册成功!'; //提示注册成果 } else { //如果请求参数错误 echo '参数错误!'; //提示参数错误 } ?>
当然,现在很多的MVC框架都是使用单文档入口的方式来做实现控制器。如果觉得现在的这个方式不好用也可以修改成单文档的方式。但是使用单文档方式有一个不好的地方是,结果输出是需要写在模型层中的。从编程角度上来讲,模型层的结构一般是返回数据或者状态并不直接输出结果。
21.4 MVC应用示例
在以上小节中讲了什么是MVC,以及如何实现一个简单的MVC框架,想必读者已经很想知道怎么使用这个框架。本节就来使用这个MVC框架来实现一个用户注册、登录、注销的例子。
首先是用户的数据模型层,需要实现用户的注册、登录和注销3个功能,需要引入一个数据库操作类,具体的实现如代码21-2所示。
代码21-2 用户的数据模型层
<?php require_once('21-1.php'); //导入数据操作类 session_start(); //启用session class User { //用户模型类 var $db; //用户数据库 function __construct() { //构造函数,链接数据库 $this->db = new DB("mysql:dbname=test;host=localhost", 'root', ''); } function add_user($username, $password) { //添加用户 $_bool = $this->db->get_col("SELECT COUNT(1) FROM user WHERE username = ?", array ($username)); if($_bool) { return -1; } $_result = $this->db->insert('user', array('username'=>$username, 'password'=>$password)); if($_result) { return 1; } else { return 0; } } function login($username, $password) { //用户登录 $_user = $this->db->get_one("SELECT * FROM user WHERE username = ? AND password = ?", array($username, $password)); if($_user) { $_SESSION['user_id'] = $_user['user_id']; $_SESSION['username'] = $_user['username']; return true; } else { return false; } } function logout() { //用户注销 $_SESSION['user_id'] = ''; return 1; } } ?>
然后是注册页面,如代码21-3所示。
代码21-3 注册页面
<span class="kindle-cn-bold">使用MVC框架实现的用户登录系统</span><hr /> 用户注册 <form id="form1" name="form1" method="post" action="21-5.php?act=add_user"> <table> <tr> <td>用户名:</td> <td><input type="text" name="username" id="username" /></td> </tr> <tr> <td>密 码:</td> <td><input type="password" name="password" id="password" /></td> </tr> <tr> <td> </td> <td><input name="" type="submit" value="提交" /></td> </tr> </table> </form>
登录页面基本和注册页面差不多,如代码21-4所示。
代码21-4 登录页面
<span class="kindle-cn-bold">使用MVC框架实现的用户登录系统</span><hr /> 用户登录 <form id="form1" name="form1" method="post" action="21-5.php?act=login"> <table> <tr> <td>用户名:</td> <td><input type="text" name="username" id="username" /></td> </tr> <tr> <td>密 码:</td> <td><input type="password" name="password" id="password" /></td> </tr> <tr> <td> </td> <td><input name="" type="submit" value="提交" /></td> </tr> </table> </form>
最后是实现控制器的页面,如代码21-5所示。
代码21-5 实现控制器作用的页面
<?php require_once('21-2.php'); //引入用户模型 $user = new User(); //模型类对象化 $username = $_REQUEST['username']; //取得提交的username $password = $_REQUEST['password']; //取得提交的password if($_REQUEST['act'] == 'login') { //判断请求是否是login if($user->login($username, $password)) { //调用模型层的login方法 echo '欢迎 '.$_SESSION['username'].'<br />'; //欢迎消息 echo '登录成功!'; //成功的结果 echo '<a href="21-5.php?act=logout">注销</a>'; //生成注销按钮 } else { echo '登录失败!'; //失败后的结果 } } elseif($_REQUEST['act'] == 'logout') { //如果是注销 $user->logout(); //调用模型层的logout方法 echo '注销成功!'; } elseif($_REQUEST['act'] == 'add_user') { //如果是新增用户 $result = $user->add_user($username, $password); //调用模型层的add_user方法 if($result == -1) { echo '已存在该用户'; } elseif($result == 1) { echo '注册成功!'; } else { echo '注册失败!'; } //提示注册成果 } else { //如果请求参数错误 echo '参数错误!'; //提示参数错误 } ?>
以上代码的原因已经在上一节中有详细讲解,这里就不再赘述。不同的是在这个例子中,视图页面没有用到模板引擎。运行代码21-3后的页面结果如图21.1所示。出现的是一个用户注册页面,输入相应的用户名和密码后单击“提交”按钮,出现如图21.2所示的结果。如果已经有用户注册,则会出现如图21.3所示的结果。
图21.1 用户注册 | 图21.2 注册成功 | 图21.3 已存在用户 |
注册成果后,运行代码21-4所得到的页面结果如图21.4所示。在此页面输入刚才注册成功的用户名和密码,单击“提交”按钮后得到如图21.5所示的结果,表示登录成功,并显示登录者的用户名。单击其中的“注销”按钮后,得到的结果如图21.6所示。
图21.4 登录页面 | 图21.5 登录成功 | 图21.6 注销功能 |
至此要实现的用户登录过程基本完成。
21.5 小结
本章主要介绍了什么是MVC模型、MVC模型的组成和实现简单的MVC,并在这个基础上实现使用MVC的一个例子。通过本章的学习让读者对MVC这种设计模式有了进一步的了解,使读者懂得编程思想是程序开发的核心,并需要应用在合适的地方。
21.6 习题
1. 简述你对MVC这个程序设计模式的理解。
2. 简单说说你对本章使用的框架的优缺点的理解。
共有条评论 网友评论