MVC设计模式
MVC模式下,客户直接发送请求到控制器,控制器根据用户的请求的资源分发到相对应的模型来处理,模型完成了业务逻辑后,把要数据发送到视图,视图显示返回给客户。这就是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 function __autoload($class){ if($class == ‘Myblog’) include ‘Lib/test.php’; if($class == ‘Mybook’) include ‘App/command.php’; if(!include_once($classpath)){ //加栽类 throw new Exception(”加载类库失败“); } } ?> |
这只是实例化两个不同目录下的两个类而已。如果项目中使用面向对象开发的话,类不会那么少,可以想下。如果要加载数个不同目录下的类,__autoload函数里实现会是多麻烦和不灵活。
在这里给出个比较简单的解决方案,而且这个解决方案在很多MVC框架中得以很好的应用
我们只需要在类的命名方式上做些改变,以类的目录路径为类名:
/Yhmphp/ App.php 里的Mysession类,命名为:Yhmphp_App_Mysession。用’_’下划线来替换
路径分割符,以相对路径下的目录路径做类名。
又比如根目录下的Lib目录下test.php 文件里面(/Lib/test.php) 有个Myblog 类,可以这样给Myblog类命名:
<?php class Lib_Myblog{} ?> 实例化Lib_Myblog类: <?php $myblog = new Lib_Myblog; ?> |
__autoload函数里用str_replace函数把路径分割符替换类名中的’_’下划线,这样就可以准确的找到Lib_Myblog类的所在文件的路径然后准确的加载了:
<?php function __autoload($class){ $classpath = str_replace(’_',’/',$class).’.php’; if(!include_once($classpath)){ //加栽类 throw new Exception(”加载类库失败“); } } ?> |
模型的路由:
当我们给单入口文件(控制器)index.php 加上了模型选参m (index.php?m=myblog),控制器就会去寻找 myblog模型类,并实例化,然后执行myblog模型类中的 model 方法,最后执行show方法
来显示视图。
<?php class App_Run { public function 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{ throw new Exception(”数据模型不存在“); } } } ?> |
常量MBILE_MODEL定义了模型存放的目录名,在这里做了定义方便日后模型目录的需求更改。这是个良好的习惯。能统一定义的信息就该统一。能模块化的业务逻辑就应该模块化。为日后的项目维护和项目扩展做好铺垫。
MBILE_MODEL在Config/ __Active.php文件里面这样定义:
<?php /** 系统配置 */ define(’MOBILE_ROOT’, ”); //站根目录 define(’MOBILE_MODEL’,'Modules’); //模型目录名 ?> |
class_exists() 函数来判断客户请求的($_REQUEST['m']) 模型类是否存在。Class_exists() 方法依据__autoload() 函数来加载判断。所以__autoload()函数必须在class_exists() 方法之前先加载。
模型类如果存在,就使用method_exists() 方法来判断执行模型类里面的model 方法里面的业务逻辑,然后再执行show() 方法显示视图。这样就完成了一个 MVC 流程。
存放模型的目录是 Modules 目录。这个在上面的MOBILE_MODEL 常量中已经定义。难道所有的业务模型都存在在一个Modules目录里面吗?这样的设计的确有点问题。文件夹里面的文件过多,损耗程序寻找模型加载的时间,而且模型过多存在一个目录之中,会让这个项目变得很杂乱。比如前台和后台的模型目录和视图目录就应该分开存放。
<?php /** 多模型目录 */ if(empty($_GET['c']) && ) $_GET['c'] = ‘Default’; if(empty($_GET['m'])) $_GET['m'] = ‘Index’; define (’MODEL_SWITCHING’,$_GET['c']) ; //前台后台目录切换 ?> |
常量MODEL_SWITCHING 就是为解决这个问题设计的:
我们先看最后一句代码:
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 控制器集中控制。然后按需进行分发:
<?php /** 入口文件 */ try{ error_reporting(E_ALL); //关闭错误输出 require ‘Config/__Homeswitching.php’; require ‘Config/__Active.php’; require ‘App/Auto.php’; $set = new App_Run; $set->routing(); }catch (Exception $e){ echo($e->getMessage()); } ?> |
多模型设置文件:__Homeswitching.php,定义基础模型目录文件:__Active.php,类驱动文件:Auto.php
加载完后,再实例化模型路由App_Run类,执行其 routing() 方法 寻找完成客户请求。
业务模型
业务模型类是整个项目中使用最多的。它就是MVC 中的M (模型)。类里面封装了大量的业务逻辑。比如:客户向index.php 控制器 请求 (index.php?m=newbook) newbook业务模型,来查询最新出的图书。 那么newbook业务模型类所需要完成的任务就是查询数据库,从数据库中提取最新的图书资料,然后发送到 视图层(View)显示给客户。显然,MVC 模式可以把 模型与模型,功能与功能之间的耦合度变得很小,扩展性很强。
<?php /** * 模型类 * */ class Modules_Default_Index extends App_Manage { private newbook=’’; public function __construct(){ parent::__construct(); } public function show(){ $this->tpl->assign(‘newbook’,$this->newbook); $this->tpl->display(’Index’); } public function model(){ /**实现业务逻辑*/ $this->newbook = ‘PHP MVC’; } } ?> |
业务模型类里面有两个主要方法: model() 、show() 方法。Model() 方法主要实现业务逻辑,比如:读数据库,写数据库等。Show() 方法主要和model() 方法联系获取业务逻辑的数据,然后输出给视图(php模板)。
读者请思考下以下几个问题:
模型类 Modules_Default_Index 为什么要起这个名字?前面__autoload讲解中已讲解。Show() 方法是否能去掉只实现model() 模型?模型的路由小节中讲解。
全局类
在项目开发里,有很多我们自己封装好的类:数据库操作类、模板类、email发送类、分页类、文本缓存类、内存缓存类memcache等等。这些类中有些是每个模型都必须加载使用的,比如 数据库操作类、分页类。难道我们每个模型里都要显式的实例化一次?那真是太麻烦了。所以设计一个全局管理类App_Manage :
<?php class App_Manage{ protected $tpl; protected $mem; protected $email; public function __construct(){ $this->tpl = new Lib_Tpl; /* $this->mem = new Lib_Memcached; $this->email = new Lib_Mailer();*/ } } ?> |
在App_Manage中。我们简单的在其构造方法中实例化了几个常用的类。每个模型都继承App_Manage类,这样模型类里面就可以使用父类的方法和属性,以此实现全局类:
public function __construct(){
parent::__con