如何写MVC基础框架



转老逆原创帖!

VC模式下,客户直接发送请求到控制器,控制器根据用户请求的资源分发到相对应的模型来处理,模型完成了业务逻辑后,把所要的数据发送到视图,视图显示返回给客户。这就是web 或是说B/S架构的MVC工作流程。

控制器:

用户的所有请求会发送到控制器,由控制器来根据需要调用模型和视图。比如用户请求index.php 控制器文件,index.php里面不会设计到任何的数据库操作、逻辑操作。它只会寻找执行用户请求的业务模型,把所有的业务逻辑操作交给模型也就是 MVC中的M。把控制器独立出来,形成单入口访问模式,方便做全局管理,比如:日志记录等。

模型:

模型是业务逻辑数据的集合,比如数据库操作,复杂的逻辑运算等。按照功能或项目模块来分成一个个模型,模型间的耦合性很小有利于项目以后的扩展和修改。

视图:

Web技术中的MVC的C层。其主要是由 HTML 、XML语言组成的界面。以前的web界面是视图和模型混杂在一起使用,形成了杂乱的代码,这样使日后程序的维护十分艰难。PHP中知名的模板引擎 smarty 就是为了实现模型和视图分离的一种技术。现在smarty 在PHP行业中被开发者广泛使用。

MVC思想不是为了某种语言而设计的,它适用于所有的面向对象的语言。比如知名的实现MVC思想的JAVA语言的 Struts 框架。当然PHP 框架也是百花齐放如 :Zend Framework 、 Fleaphp 、Thinkphp 、Cakephp 等,都能很好的实现MVC思想而且他们大量应用了GOF 设计模式,开发人员如果基于以上几种MVC框架来进行项目开发的话,开发的效率和代码质量都会大幅度提升,特别是多人协作开发的项目。那PHP怎么实现 MVC的呢?下面给大家开发一个简单的MVC基础框架来说明这一点,完整代码附光盘09/20。

类驱动

在php5中可以使用__autoload 函数来实现类自动加载。但单纯这样的方式不够灵活的。比如类文件存放在不同的目录里面,而此时又需要自动加载的情况下,我们就需要在__autoload函数里进行复杂的逻辑判断来实现自动加载。
比如需要实例化两个类:Myblog 、Mybook。Mylog类在根目录下的Lib/test.php 文件里,Mybook类在根目录下的App/command.php 文件里。

__autoload

函数里实现加载:


[php]php
function __autoload($class){

if($class =='Myblog') include 'Lib/test.php';

if($class =='Mybook') include 'App/command.php';



if(!include_once($classpath)){//加栽类

throw newException("加载类库失败");

}
}
[size=10.5pt]?>

这只是实例化两个不同目录下的两个类而已。如果项目中使用面向对象开发的话,类不会那么少,大家可以想像一下。如果要加载数个不同目录下的类,在__autoload函数里实现会是多么的麻烦和不灵活。

在这里给出个比较简单的解决方案,而且这个解决方案在很多MVC框架中都得以很好的应用

我们只需要在类的命名方式上做些改变,以类的目录路径为类名:

/Yhmphp/ App.php 里的Mysession类,命名为:Yhmphp_App_Mysession。用‘ _ ’下划线来替换

路径分割符,以相对路径下的目录路径做类名。

又比如根目录下的Lib目录下test.php 文件里面(/Lib/test.php) 有个Myblog 类,可以这样给Myblog类命名:

class Lib_Myblog{}
?>
实例化Lib_Myblog类:

$myblog = newLib_Myblog;
[size=10.5pt]?>

__autoload函数里用str_replace函数把路径分割符替换类名中的‘ _ ’下划线,这样就可以准确的找到Lib_Myblog类的所在文件的路径然后准确的加载了:

function __autoload($class){

$classpath =str_replace('_','/',$class).'.php';

if(!include_once($classpath)){//加栽类

throw newException("加载类库失败");

}
}
[size=10.5pt]?>

模型的路由:

当我们给单入口文件(控制器)index.php
加上了模型选参m(index.php?m=myblog),控制器就会去寻找 myblog模型类,并实例化,然后执行myblog模型类中的 model 方法,最后执行show方法
来显示视图。

class App_Run
{

publicfunction routing(){

$model =MOBILE_MODEL.'_'.MODEL_SWITCHING.'_'.$_REQUEST['m'];

if(class_exists($model)){

$cake= new $model;

method_exists($cake,'model')&& $cake->model(); //执行模型里面的 model方法

method_exists($cake,’show’)&& $cake->show(); //视图层

}else{

thrownew Exception("数据模型不存在");

}
}
}
[size=10.5pt]?>

常量MBILE_MODEL定义了模型存放的目录名,在这里做了定义方便日后模型目录的更改。这是个良好的习惯。能统一定义的信息就该统一。能模块化的业务逻辑就应该模块化。为日后的项目维护和项目扩展做好铺垫。

MBILE_MODEL

在Config/__Active.php文件里面这样定义:

[php]php
/**
系统配置
*/
define('MOBILE_ROOT', ''); //站根目录
define('MOBILE_MODEL','Modules'); //模型目录名
[size=10.5pt]?>

class_exists() 函数来判断客户请求的($_REQUEST['m']) 模型类是否存在。Class_exists() 方法依据__autoload() 函数来加载判断。所以__autoload()函数必须在class_exists() 方法之前先加载。

模型类如果存在,就使用method_exists() 方法来判断执行模型类里面的model 方法里面的业务逻辑,然后再执行show() 方法显示视图。这样就完成了一个 MVC 流程。

存放模型的目录是 Modules 目录。这个在上面的MOBILE_MODEL常量中已经定义。难道所有的业务模型都存在一个Modules目录里面吗?这样的设计的确有点问题。文件夹里 面的文件过多,损耗程序寻找模型加载的时间,而且模型过多存在一个目录之中,会让这个项目变得很杂乱。比如前台和后台的模型目录和视图目录就应该分开存 放。

/**
多模型目录
*/
if(empty($_GET['c']) && ) $_GET['c'] ='Default';
if(empty($_GET['m'])) $_GET['m'] = 'Index';

define ('MODEL_SWITCHING',$_GET['c']) ; //前台后台目录切换
[size=10.5pt]?>

常量MODEL_SWITCHING 就是为解决这个问题设计的:
我们先看最后一句代码:

[size=10.5pt]define('MODEL_SWITCHING',$_GET['c']) ; //前台后台目录切换


它定义了常量MODEL_SWITCHING 以$_GET ['c']变量为值。App_Run 类中,常量MOBILE_MODEL 、MODEL_SWITCHING 和$_GET['m'] 组成了模型的加载路径:
$model = MOBILE_MODEL.'_'.MODEL_SWITCHING.'_'.$_GET['m'];

这样设计以后我们想添加多个模型目录都是很容易的事情了。比如:项目需要添加两个模型目录。前台模型目录:Default 和 后台模型目录:Admin 。只需要在默认的模型目录 Modules 下创建 Default 和Admin 两个目录,然后客户访问的URL 中 添加一个 参数c :
访问Default 目录下的Myblog业务模型: index.php?c=default&m=myblog
访问Admin 目录下的Member业务模型: index.php?c=admin&m=member
我们可以再设计灵活及人性化点,就是当$_GET[‘m’]和$_GET[‘c’] 客户没有设置的时候来给模型目录和业务模型类设置一个默认值。
if(empty($_GET['c'])
) $_GET['c'] = 'Default';
if(empty($_GET['m'])) $_GET['m'] = 'Index';

控制器
PHP MVC典型的设计就是使用单入口php文件来实现MVC中的C:控制器。所有的客户请求全部经过index.php 控制器集中控制。然后按需进行分发:

/**
入口文件
*/
try{

error_reporting(E_ALL);//关闭错误输出

require'Config/__Homeswitching.php';

require'Config/__Active.php';

require'App/Auto.php';


$set = newApp_Run;

$set->routing();


}catch (Exception $e){


echo($e->getMessage());


}
[size=10.5pt]?>

多模型设置文件:__Homeswitching.php,定义基础模型目录文件:__Active.php,类驱动文件:Auto.php
加载完后,再实例化模型路由App_Run类,执行其 routing() 方法 寻找完成客户请求。

业务模型

业务模型类是整个项目中使用最多的。它就是MVC 中的M (模型)。类里面封装了大量的业务逻辑。比如:客户向index.php 控制器 请求 (index.php?m=newbook)newbook业务模型,来查询最新出的图书。 那么newbook业务模型类所需要完成的任务就是查询数据库,从数据库中提取最新的图书资料,然后发送到视图层(View)显示给客户。显然,MVC 模式可以把 模型与模型、功能与功能之间的耦合度变得很小、扩展性很强。

/**

* 模型类

* */
class Modules_Default_Index
extends App_Manage
{



privatenewbook=’’;

publicfunction __c****truct(){

parent::__c****truct();

}



publicfunction show(){

$this->tpl->assign(‘newbook’,$this->newbook);
 
$this->tpl->display('Index');

}



publicfunction model(){

/**实现业务逻辑*/

$this->newbook = ‘PHP MVC’;

}
}
[size=10.5pt]?>

业务模型类里面有两个主要方法: model() 、show() 方法。Model() 方法主要实现业务逻辑,比如:读数据库、写数据库等。Show() 方法主要和model() 方法联系获取业务逻辑的数据,然后输出给视图(php模板)。

读者请思考下以下几个问题:
模型类 Modules_Default_Index 为什么要起这个名字?前面__autoload讲解中已讲解。Show() 方法是否能去掉只实现model() 模型?模型的路由小节中讲解。

全局类
在项目开发里,有很多我们自己封装好的类:数据库操作类、模板类、email发送类、分页类、文本缓存类、内存缓存类memcache等等。这些 类中有些是每个模型都必须加载使用的,比如 数据库操作类、分页类。难道我们每个模型里都要显式的实例化一次?那真是太麻烦了。所以设计一个全局管理类App_Manage :

class App_Manage{
protected$tpl;
protected$mem;
protected$email;
publicfunction __c****truct(){

$this->tpl = new Lib_Tpl;
/*
$this->mem = new Lib_Memcached;


$this->email= new Lib_Mailer();*/
}
[font=宋体]}[/font]
[font=宋体][size=10.5pt]?>

在App_Manage中。我们简单的在其构造方法中实例化了几个常用的类。每个模型都继承App_Manage类,这样模型类里面就可以使用父类的方法和属性,以此实现全局类:

public function __c****truct(){

parent::__c****truct();

}

 
 

MySQL视图的应用

 

MySQL视图的应用

视图也称虚表,包括执行某个查询返回的一组记录。视图可以简化获取数据的方法,并通过别名来实现抽象查询。

实例258:创建视图

实例说明

为了简化查询,在实际项目开发过程中,通常可以采用为数据库中的某个表建立视图的方式。在建立的视图中只有开发人员所关心的字段。运行本实例,如图9.6所示,分别在图中输入登录用户的用户名和密码,单击“进入”按钮后,如果用户输入错误的用户名或密码,则给出错误提示,反之如果输入的用户名和密码正确,则提示成功登录。由于本实例采用视图建立了虚表,所以应首先在“命令提示符”下建立视图,如图9.7所示。

图9.6 用户身份验证
图9.7 创建视图

技术要点

MySQL中创建视图可以通过create view语句来实现,具体创建格式如下:

$create [ or deplace] [algorithm={merge | temptable | undefined}] view view_name 
[( column_list)] as select_statement [with [cascaded | local] check option]

algorithm={merge | temptable | undefined}属性用于优化MySQL视图的执行,该属性有3个可用的设置。下面将介绍这3个设置的使用方法。

merge:该参数使MySQL执行视图时传入的任何子句合并到视图的查询定义中。
temptable:如果视图低层表中的数据有变化,这些变化将在下次通过表时立即反映出来。
undefined:当查询结果和视图结果为一一对应关系时,MySQL将algorithm设定为temptable。
view_name:新建视图的名称。
select_statement:SQL查询语句用于限定虚表的内容。

实现过程

(1)建立数据库及数据表,本实例中将数据表命名为tb_admin。
(2)建立完数据表后,就可以在命令提示符下建立视图。创建视图的代码如下:

 

create view chkadmin as select name, pwd from tb_admin


创建完视图chkadmin后,该视图中只含有name和pwd两个字段,这样会给密码验证工作带来很大的方便。
(3)建立与MySQL数据库的连接。代码如下:

 

<?php
$conn=mysql_connect("localhost","root","root");
mysql_select_db("db_database09",$conn);
mysql_query("set names gb2312");
?>

(4)判断用户是否单击了“进入”按钮,如果是则通过视图chkadmin对用户身份进行验证。该过程代码如下:

 

<?php
if($_POST[submit]!="")
{
include_once("conn.php");
$name=$_POST[name];
$pwd=$_POST[pwd];
$sql=mysql_query("select * from chkadmin where name='".$name."' and
pwd='".$pwd."'",$conn);
$info=mysql_fetch_array($sql);
if($info==false){
echo "<script>alert('用户名或密码输入错误!');history.back();</script>";
exit;
}
else
{
echo "<br><div align=center>登录成功!</div>";
}
}
?>

举一反三

根据本实例,读者可以:
利用视图实现用户信息注册。
利用视图实现商品信息的降序输出。

实例259:修改视图
这是一个修改视图的实例
录像位置:光盘\mingrisoft\09\259

实例说明

为了提高工作效率,对不满足条件的视图可以通过修改的方式进行更改。实现本实例,首先应在数据库中创建数据表tb_changeview,该表也可以在phpMyAdmin中创建,如图9.8所示。打开“命令提示符”窗口,创建视图userinfo,如图9.9所示。更改视图userinfo,如图9.10所示。

 

图9.8 创建表changeview
图9.9 创建新视图

 

图9.10 修改视图

技术要点

MySQL中修改视图可以通过alter view语句实现,alter view语句具体使用说明如下:

alter view [algorithm={merge | temptable | undefined} ]view view_name 
[(column_list)] as select_statement[with [cascaded | local] check option]

algorithm:该参数已经在创建视图中作了介绍,这里不再赘述。
view_name:视图的名称。
select_statement:SQL语句用于限定视图。

实现过程

(1)建立数据库及数据表,本实例中将数据表命名为tb_changeview。
(2)创建视图userinfo。代码如下:

 

create view userinfo as select * from tb_changeview

(3)查询视图userinfo。代码如下:

 

select * from userinfo

查看执行结果,可以发现tb_changeview表中的所有的字段都一一列出。
(4)下面开始修改视图,要求修改后只列出username字段。代码如下:

 

alter view userinfo as select * from tb_changeview

(5)重新查询视图userinfo,从结果中可以发现这时只列出username一个字段,从而说明视图修改成功。
(2)建立index.php文件,用于实现多表之间的查询,如果学生表和成绩表中有满足条件的记录,则显示这些记录,否则提示没有相关信息。代码如下:

举一反三

根据本实例,读者可以:

实现修改存储商品信息的视图。
实现修改存储用户注册信息的视图。

实例260:应用视图
这是一个应用视图的实例
实例位置:光盘\mingrisoft\09\260

实例说明

在实际的Web开发过程中,多个数据表中可能有很多的字段,但某个模块可能只需要其中的几个字段。为了提高查询速度和简便操作,可以将该模块需要的字段单独提取出来放在某视图中,例如本实例涉及到学生表和成绩表,在建立的视图中只含有与学生成绩有关的字段,如图9.11所示。运行本实例,如图9.12所示,图中的查询结果显示的字段即为视图中的所有字段。

 

图9.11 创建视图
图9.12 学生成绩列表

技术要点

本实例创建的视图涉及到多表查询,这说明多个表之间可以通过视图来组合为一个整体,这样对视图的操作相当于多表查询。但如果这些表中有相同的字段,必须按如下方式书写重名的字段:

表名1.字段名,表名2.字段名……

实现过程

(1)在“命令提示符”窗口下为数据库建立视图。代码如下:

create view scoreinfo as select sno,sname,
yw,wy,sx from tb_student,tb_score where tb_student.id=tb_score.sid


(2)建立数据库连接文件conn.php。代码如下:

<?php
$conn=new mysqli("localhost","root","root","db_database09");
$conn->query("set names gb2312");
?>


(3)查询视图scoreinfo中的内容,并显示查询结果。代码如下:

<?php
include_once("conn.php");
$sql=$conn->query("select * from scoreinfo");
$info=$sql->fetch_array(MYSQLI_ASSOC);
if($info==NULL)
{
echo "暂无学生信息";
}
else
{
do
{
?>
<tr>
<td height="20" bgcolor="#FFFFFF"><div align="center">
<?php echo $info[sno];?></div></td>
<td bgcolor="#FFFFFF"><div align="center"><?php echo $info[sname];?></div></td>
<td bgcolor="#FFFFFF"><div align="center"><?php echo $info[yw];?></div></td>
<td bgcolor="#FFFFFF"><div align="center"><?php echo $info[wy];?></div></td>
<td bgcolor="#FFFFFF"><div align="center"><?php echo $info[sx];?></div></td>
</tr>
<?php
}
while(  $info=$sql->fetch_array(MYSQLI_ASSOC));
}
?>


举一反三

根据本实例,读者可以:

实现多表之间的嵌套查询。
实现多表之间相同信息的提取