当前位置: 首页 > 图文教程 > 网络编程 > PHP > 《PHP设计模式介绍》第十七章 MVC 模式

PHP
php 多线程上下文中安全写文件实现代码
PHP类的使用 实例代码讲解
用php实现让页面只能被百度gogole蜘蛛访问的方法
php 学习笔记
PHP编程过程中需要了解的this,self,parent的区别
php 操作excel文件的方法小结
使用PHP获取网络文件的实现代码
PHP 巧用数组降低程序的时间复杂度
php下将XML转换为数组
php 文件上传代码(限制jpg文件)
php 无极分类(递归)实现代码
PHP 采集获取指定网址的内容
PHP 将图片按创建时间进行分类存储的实现代码
PHP 存储文本换行实现方法
PHP 批量更新网页内容实现代码
用PHP查询搜索引擎排名位置的代码
用php实现的获取网页中的图片并保存到本地的代码
php实现首页链接查询 友情链接检查的代码
处理php自动反斜杠的函数代码
php实现的遍历文件夹下所有文件,编辑删除

《PHP设计模式介绍》第十七章 MVC 模式


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-09-23   浏览: 177 ::
收藏到网摘: n/a

web应用是十分多样化的,为了实现某种应用,那哪种或哪些是最好的呢?或者说,对于web应用有没有一种最好的体系?

问题

你能配置一个单一的web体系去满足每个普通的web应用吗?包括一般性的界面呈现风格,身份验证,表单数据确认等等?

解决方案:

MVC模式把你的软件组织架构分解成3个清晰的任务

model结合了应用数据,应用流程以及业务逻辑

view从model中提取数据并呈现出来

controller调用应用流程,接收输入并传送给model和view

注:MVC的起源

MVC模式是1970年由trygve reenskaug在施乐公司的研究中心提出的。最早的参考例程代码是用smalltalk80编写的。它最初是用来解决应用软件中的用户图形界面的交互问题的。

你要使用了MVC模式,你就会知道它是多么的有用,尤其是对于图形化的用户界面应用。除此之外,MVC对web应用也非常有用,虽然web服务进行通讯连接,与通讯过程有时会中断,但这也带来了一些独特的挑战和机会。

假如你到这一章来寻找一种真正的对于web应用的MVC方法,我希望你不会对于这里的答案太失望。最完美的解决方案是不存在的,但是这里有很多实例以及相关的模式,帮助你有效的去实施MVC。有希望的是,这里的观点能作为你的编码工作的跳板,并且带领你进行更多的研究。

模块视图控制器

不像其他的设计模式,MVC模式不是一个注重于讲述一种可以直接设计编码的类结构的模式。相反,它不仅仅是概念上的指导或范例。

在概念上,MVC模式的定义的是model,view以及controller三者之间的桥梁。controller以及view都依赖于model,因为view和controller都得向model发送数据请求。数据通过controller输入到你的系统当中,并最终通过view显示出结果。对于你一个php开发者,更具体的来说,controller处理每一个外部的http请求,而view将产生http回应。

下边是MVC模式的定义图

 

在理想的MVC环境中,通讯是直接的,如下面的序列图。

 

当然,具体到细节上就有些差异了。当在web应用里实施MVC时,在单一的类中不会出现model,view以及controller,MVC把各组对象紧密地连接起来,每组对象实现一种特定的任务。controller可以由几类组成,用来分析http请求,以确定应用程序所要进行的处理动作。model一般是由很多类组成。web应用里的view通常是一种摸版体系,可以由一些对象组成。

在接下来的几节,让我们深入挖掘“MVC三和音”的每个部分,了解哪些设计模式存在于或推动每个部分,以及他们如何帮助你进行编码工作。

Model

model包含你的应用逻辑以及应用数据,它可能是你的应用中最主要的逻辑处理部分。model没有任何关于界面风格的表达,对于处理http请求也没有起什么作用。

Domain Model

域model是对象的一层,它将应用程序处理的问题、数据以及业务逻辑抽象化。域model可以归为两大类:简单域model以及富域model

简单域model在业务对象与数据库表单间产生一对一的响应。你已经见过一些模式--active record,table data gateway,以及datamapper,所有数据库相关的设计模式,他们可以帮助你将数据库相关的逻辑组织成一个域model(为了使本书中的例子合理,简洁易懂,选取的材料都没有超过简单域model的一对一响应的范围---同形映射)

一个富域model包括由一堆紧紧交织在一起的对象构成的复杂的web,这些对象使用了继承机制,本书以及gof当中的很多设计模式都覆盖了这些内容。富域model更容易被测试以及反复重构,外加紧密地和商业需求结合在一起。

采用哪种域model就要看你的应用程序的上下文了。如果你只是传送一个相当简单的表单处理web应用,就没有必要建立富域model了。但是,如果你要编写一个库,那是一个数百万美元市值的公司的内部网的底层结构,那就需要建立一个富域model了,它提供了一个精确的表述业务处理以及迅速的数据计算的平台。

martin fowler致力于开发富域model,简要的覆盖了poeaa中的域模型的类型以及在eric evans的书中的域驱动设计

View

view主要处理所有界面呈现的问题。view从model里提取数据,把它格式化成html页面或者xml页面服务,又或者用于email的文本。

鉴定你是否把你的代码分离成明确定义了的任务的一个好方法就是:尝试代替(至少在概念上)另一个wiew,这个view产生完全不同的输出。举个例子,如果你有一个web应用,为了迅速的使你的web程序在php的命令行模式下运行,你要改动什么呢?

直接通过View中的表单调用Model中的方法去修改数据是不合适的,数据的更新修改方法只能借助Controller来调用。View只能以数据的只读方式调用Model中的方法,不能对数据进行修改。一般情况下有两种View设计模式:Template View和Transform View。

Template View

用于web 应用的view中使用的主要模式就是Template View,这个模式使用一个模版文件(通常是html),它包括了一些特殊标记,当Template View被执行时,这些特殊标记就被model里的对应数据替换并显示出来。

php 本身就是一种特定类型的实例模版,叫做服务器页。一个模版系统是基于使用php作为模版的,它本身是savant(http://www.phpsavant.com/)

使用savant的实例:

总有一些有复杂模版引擎甚至是"Plain Old PHP Pages"(popp)的模板无法可变换替换,而且嵌入了控制结构和其他逻辑到页面里。然而,给结果到你的应用程序的表述层的业务逻辑,维护就会变得相当困难。

注:写模版引擎

似乎写摸版引擎是php社区里的一种passage权利,搜索模版引擎逐字发现上百的结果。(这方面的实验例子可以看http://www.sitepoint.com/forums/showthread.php?t=123769)如果你不选择用普通的引擎,而是用你自己的,这儿有丰富的实例代码可以看。

地址http://wact.sf.net/index.php/TemplateView很好的概述了什么样式的标记可以被模版视图使用。包括一个属性语言,自定义标签,html备注以及自定义语法。

非常流行的模版引擎smarty(http://smarty.php.net/)是一个使用自定义语法方法的模版引擎的实例。

装载smarty引擎就像:


rainbow.html的自定义语法就像:

<html><head>
<title>{$title}</title>
</head><body>
<h1>{$title}</h1>
<ol>
{section name=rainbow loop=$colors}
<li>{$colors[rainbow]}</li>
{/section}
</ol>
</body></html>

wact(http://wact.sf.net/)效仿了martin fowler在poeaa中概述的那种自定义标签。虽然wact支持一个与smarty相似的自定义语法作为快捷方式,wact的自定义标签列阵如下:

require_once ‘wact/framework/common.inc.php’;
require_once WACT_ROOT.’template/template.inc.php’;
require_once WACT_ROOT.’datasource/dictionary.inc.php’;
require_once WACT_ROOT.’iterator/arraydataset.inc.php’;
// simulate tabular data
$rainbow = array();
foreach (array(‘red’, ‘orange’, ‘yellow’,
‘green’, ‘blue’, ‘indigo’, ‘violet’) as $color) {
$rainbow[] = array(‘color’ => $color);
}
$ds =& new DictionaryDataSource;
$ds->set(‘title’, ‘Colors of the Rainbow’);
$ds->set(‘colors’, new ArrayDataSet($rainbow));
$tpl =& new Template(‘/rainbow.html’);
$tpl->registerDataSource($ds);
$tpl->display();

 
rainbow.html的模版如下:

<html><head>
<title>{$title}</title>
</head><body>
<h1>{$title}</h1>
<list:list id=”rainbow” from=”colors”>
<ol>
<list:item><li>{$color}</li></list:item>
</ol>
</list:list>
</body></html>

在这个wact例子里有相当多的包含的文件。这是因为框架有各种各样的要素来处理网站应用问题的各个部分。只需包含你需要的元素。在上面的例子中,模板就是一个View,dictionary data source 作为model的代理,php脚本本身是作为一个controller.许多自定义标签设计成与表格数据一起运用--像你从数据库中提取的记录集---转换成简单数组以后把它用在模版里。

最后一个样式是拥有一个模版的有效的xml文件,使用各自的要素的属性作为目标替换你的模版。这里有一个是用PHP- TAL的技术实例(http://phptal.motion-twin.com/

// PHP5
require_once ‘PHPTAL.php’;
class RainbowColor {
public $color;
public function __construct($color) {
$this->color = $color;
}
}
// make a collection of colors
$colors = array();
foreach (array(‘red’, ‘orange’, ‘yellow’,
‘green’, ‘blue’, ‘indigo’, ‘violet’) as $color) {
$colors[] = new RainbowColor($color);
}
$tpl = new PHPTAL(‘rainbow.tal.html’);
$tpl->title = ‘Colors of the Rainbow’;
$tpl->colors = $colors;
try {
echo $tpl->execute();
}
catch (Exception $e){
echo $e;
}
 

rainbow.tal.html的模版文件如下

<?xml version=”1.0”?>
<html>
<head>
<title tal:content=”title”>
place for the page title
</title>
</head>
<body>
<h1 tal:content=”title”>sample title</h1>
<ol>
<li tal:repeat=”item colors”>
<span tal:content=”item/color”>color</span>
</li>
</ol>
</body>
</html>

当然,所有的解决方法都是将model数据的显示从model以及应用程序本身分离出来。每个前期的实例都是实质上产生了同样的内容,所以选择哪个是个人喜好的问题。

Transform View

变换视图从你的model中提取数据,然后把数据转换成需要输出的格式。它实际上是使用一种语言逐个遍历你的数据元素,然后集中输出。

模版视图与变换视图之间的差异就是数据流的方向。在Template View中你先拥有一个输出的框架然后向里面插入domain数据。Transform View中则从数据着手,从它之中建立输出。

实施Transform View的主要技术是xslt.

Controller

controller是MVC里的一个角色,很多php MVC框架都讲到了。主要是出于这样的考虑:model对于应用是特定的,而几乎每个开发人员都已经有他们喜爱的模版引擎,它是视图的一个主要要素。那使得解释http回应,控制申请流(采取适当的行动来显示),两个关联的任务合为一个通用框架。

Front Controllers

它常常有助于集中控制应用流于一点。集中化可以帮助你了解一个复杂的系统是怎样运行的,以及提供你一个可以插入全局代码的空间,比如一个Intercepting Filter模式。对于集中化,Front Controllers对于集中控制的系统是很好的选择。

注:intercepting Filter

 intercepting Filter模式是gof书中的Chain of Responsibility模式的一个实例。它考虑了运用普通任务的连续处理请求,譬如记log和安全。

这有两个普通的实例。一是在某个链中连续使用补空格直到到达application controller,另一个类似于一系列的油漆工,有助于前后的补空动作。(考虑移除空白或者一个压缩的filter,你可以在预处理输出缓存,在加工后执行你的filter)

 

作为一个简单的实例,一个Intercepting Filter和一个Front controller联合起来会是怎么样,假设我们有perfilter()以及postfilter()两种方法用于我们的filter接口。然后我们可以使用一种方法把filter加到我们的Front controller.

class FrontController {
var $_filter_chain = array();
function registerFilter(&$filter) {
$this->_filter_chain[] =& $filter;
}
}

在运行实际的Front controller工作之前(产生页面,分派等等),我们可以在序列中使用prefilter()方法,在Front controller完成了它的任务后,postfilter()方法可以在相反的顺序调用。

class FrontController {
//...
function run() {
foreach(array_keys($this->_filter_chain) as $filter) {
$this->_filter_chain[$filter]->preFilter();
}
$this->_process();
foreach(
array_reverse(array_keys($this->_filter_chain)) as $filter) {
$this->_filter_chain[$filter]->postFilter();
}
}
function _process() {
// do the FrontController work
}
}

Application controller

Front controller通常代替了Application controller,而Application controller模式才是MVC controller的核心所在。controller的首要责任就是决定应用程序要做些什么来响应请求。

实现controller的最典型的方法就是使用命令模式。命令模式包含了对象中的一个动作,这样你就能用参数表示一个请求,写入请求队列,记入日志,或者支持操作(例如一个撤销动作)。在web应用的上下文关联中,分派给命令模式并完成一个特殊的http请求作为代码的目标是有用的。本质上,命令模式让你中止你的应用和代码的不连续行为,每个作为一个小的,便于管理的类,用一个相同的api使controller分派到一个明确的具体命令来实现需要的应用功能。

不要让强加的过多关于controller以及分派的叙述混淆你。如果你已经花了甚至几个小时在php上,那你可能已经写了一些Application controller. 比如,一个简单的传递回给它自己的表单,比如.....

 

if (count($_POST)) {
// do form handling code
} else {
// display the form
}

....是一种Application controller形式。稍微有点复杂的Application controller像以下的:

switch ($_POST[‘action’]) {
case ‘del’: $action_class = ‘DeleteBookmark’; break;
case ‘upd’: $action_class = ‘UpdateBookmark’; break;
case ‘add’: $action_class = ‘InsertBookmark’; break;
case ‘show’:
default:
$action_class = ‘DisplayBookmark’;
}
if (!class_defined($action)) {
require_once ‘actions/’.$action_class.’.php’;
}
$action =& new $action_class;
$action->run();

 
另一种可能实现分派的方法就是:用一个配置装载一个联合的数组。你可以如下方式作为结尾:

$action_map = array(
‘del’ => ‘DeleteBookmark’
,’upd’ => ‘UpdateBookmark’
,’add’ => ‘InsertBookmark’
);
$action_class = (array_key_exists($_POST[‘action’], $action_map))
? $action_map[$_POST[‘action’]] : ‘DisplayBookmark’;
if (!class_defined($action)) {
require_once ‘actions/’.$action_class.’.php’;
}
$action =& new $action_class;
$action->run();

根据我在web应用方面的经验显示,一个双分派结构可以成为一个有用的mental map用来比较框架间依赖的分派装置。第一个调度是一个需要用你的model来进行的动作。在一个可见的动作之后,发出一个http跳转指令指示客户端去取得一个特定的View。第二个调度就是选择一个特定的View。(在这种方法的早期程序中,我使用了一个条件语句,但MVC实例本身对使用Command pattern进行调度)

model--view--controller程序表的“真实生活”版本看上去与上面显示的“理想”的程序表非常相似。主要添加的是actionfactory引起每个动作,那是一个具体的命令。

 

在很多我开发的MVC运行程序中,第二个调度以默认ShowViewAction进行。

 

这个图显示了第一个调度创建了一个具体的指令ShowViewAction.这个动作反之会使用ViewFactory来创建一个具体的View类,Martin Fowlerj将的MVC关于View的PoEAA部分叫做View Helper。这个视图可以使用你喜欢的模版引擎来选择和解析一个模版文件,填充模版变量:从model取得数据,进而通过模版呈现结果返回给客户端。

这是一种可以提升MVC名誉的图,但是实际上,这张图的各个要素都被添加了,以满足通过组织代码使得维护变简单的需要。

总之,我发现了使用一个特定的框架最显著的障碍就是了解一个框架是怎样运行的以及怎样添加有特殊用途的功能。实际的组织是典型的一次性简单了解,但是起初在没有上下联系的情况下它似乎又很难理解。

贯穿MVC的相关问题:

似乎有很多“什么到哪里”的问题围绕着MVC,你会从不同的MVC拥护者那里收到很多不同的答案。

$_SESSION属于哪里?一个争议是说sessions是不变的数据存储,通常是以文件形式保存在服务器上,因此最好归入model的范畴。第二种说法就是session和其他的php superglobal一样,session数据是输入系统的,因此是属于controller,另外一些开发员认为,sessions是用cookie执行的,一种工作在http上的html里的技术,所以是和view相关联的。

身份认证是属于哪里?它似乎像是应用逻辑的一部分,因此是属于model。但是如果你要限制只有通过验证的用户才能执行特定动作(controller的部分)呢?好,controller可以进入model,所以身份认证归入这里似乎是个完美的划分。但是http认证呢?它也进入到controller吗?

在整个概念中浏览器适合在哪里?很明显是View,对吗?如若你想通过java语言进行验证呢?验证不属于controller和model码?你怎么让它进入View呢?

这些争论都没有平息的迹象,当你在你的MVC实现中试图找出怎么样看待这些你关心的问题时以上每个都导致了一些挑衅的想法。

不含MVC的框架

很明显不是每个框架都围绕着嵌入在MVC模式的想法的分离上。这里是一个关于无MVC的框架想法的小例子

事件处理

 当你在gui环境下工作时,工具一般设置成响应事件。想想button.click().一些php框架试图采取这种为核心思想。在zend的php5编程比赛中认识了prado,它把事件处理作为核心概念。WACT使用Composite pattern来综合controller的概念,每个都有“监听程序”,它能接近事件处理透视图。

控制容器的反向

在java圈子里的一个热门话题就是控制容器的反向(ioc),亦称Dependency Injection  pattern。一个好的介绍这个模式的文章在 http://www.martin- fowler.com/articles/injection.html.

这个模式结合了一个类似Pico的Dependency Injection容器和类似WACT的MVC框架,用以构建一个“autowires”自身的应用,这一模式是是与MVC正交的──这是一个我非常感兴趣的领域


结束语

这里是一个简短的MVC相关设计模式的导航.如果你想寻找完整的PHP下的MVC开发框架,我推荐你看Mojavi(http://www.mojavi.org/);这是一个很好的MVC示例,并且这个项目的开发很活跃,社区也比较有活力。

这一章也许没有能够解决你在web架构方面的任何具体问题,希望能够给你提供一些参考,能够成为你展开相关研究的一个起点,或者能够为你在编写能为PHP开发带来巨大变革的Magic Web Application Architecture时带来一些灵感。