PEAR实践:PHP中MVC机制的实现(一)

PEAR实践:PHP中MVC机制的实现(一)
内容:
1.MVC概述
2. MVC机制的实现
参考资料
关于作者
相关内容:
1、用PEAR来写你的下一个php程序
2、常用模块
3、使用PHPDoc轻松建立你的PEAR文档
4、创建中间的数据库应用层

潘凡 (nightsailer@hotmail.com)
北京赛迪数据有限公司工程师
2001 年 11 月

在以前的几篇文章里面,我们讨论了PEAR的大部份内容,想必你对PEAR也应该有了比较深刻的认识了。那么如何在实际的开发中应用呢,如何应用PEAR的思想来开发一个项目?从本篇开始,我将带领大家进入PEAR的实践篇。今后,我将和大家一起讨论一些实际的应用。

好了,我们现在就以这个发布系统为实例,讨论一下,PHP中如何实现MVC?

1.MVC概述

1.1 什么是MVC
MVC是MODEL_VIEW_CONTROL的缩写。MODEL_VIEW_CONTROL是软件设计的典型结构。在这种设计结构下,一个应用被分为三个部分:model,view和controller,每个部分负责不同的功能。model是指应用程序的数据,以及对这些数据的操作;view是指用户界面;controller负责用户界面和程序数据之间的同步,也就是完成两个方向的动作:一、在根据用户界面(view)的操作完成对程序数据(model)的更新,二、将程序数据(model)的改变及时反应到用户界面(view)上。

1.2 MVC的优点

  • 使程序结构更加清晰,增强代码稳定性
    在MVC机制下,应用被清晰的分为model,view,controller三个部分,这三个部分分别依次对应了业务逻辑和数据、用户界面、用户请求处理和数据同步。我们知道,对于业务逻辑和数据、用户界面、用户请求处理和数据同步这三部分功能来讲,用户界面发生变动的可能性最大,控制部分变动次之,而业务逻辑是最稳定的。所以这种模块功能的划分有利于在代码修改过程中选取重点,而不是把具有不同功能的代码混杂在一起造成混乱。
  • 便于开发小组进行分工
    将应用划分为model,view,controller三个部分,还有利于在项目小组内按照小组成员各自的擅长进行分工,有利于三个部分并行开发、加快项目进度。


2. MVC机制的实现

2.1概述
上一章介绍了MVC的一般概念,这一章将要讲述MVC机制在项目中的具体实现。

2.1.1应用的技术
MVC在某个特定应用上的实现是与该应用的具体技术密切相关的。在网站开发方面,JAVA是比较成熟和典型的,以一个EJB的项目为例,大部分的系统可能采用HTML技术,和J2EE(Java 2,Enterprise Edition)的相关技术,包括:HTML,JSP,JavaBean,Enterprise Java Bean,Servlet。这些技术分布于model,view,controller三个模块之中:view部分用到HTML、JSP和JavaBean技术;controller部分用到Servlet、Stateless Session Bean 、JavaBean 技术;model部分可能用到Entity Bean、Stateless Session Bean、JavaBean技术。

但是,我们在这个项目用的是PHP技术来实现,因此,我们需要作出一些折衷。一方面,要尽量使程序的逻辑实现能够平滑地迁移到另一种技术实现(如J2EE),但同时,我们也要考虑到PHP这种脚本语言的局限性。PHP在对于面向对象的支持上同JAVA这种OO的语言不是同一档次,甚至比PERL都要差得很多。不过,幸运的是,我们的运气还不算太坏,因为PHP里面的PEAR是一个面向对象的扩展。使用PEAR的规范,我们一方面可以使用现有的一些PEAR模块,另一方面,可以使我们的程序是能够按照OO的思想来实现业务逻辑的。因此,我们将废弃过去那种模块化,过程化的编程方式,转变思路,将所有的东西都用类包装起来。

在开始介绍实际的设计之前,我们不妨看看这个项目的需求情况

2.1.2项目需求
这个项目目的很简单,就是要做一个网站的发布系统。发布系统应该能够支持频道,子频道,栏目的划分,特权区的划分,用户角色划分,频道各种模板的定制,文章发布的流程控制……

更为详细的这里就不再一一叙述了。

2.1.3层次划分
我们的应用不但可以按MVC机制分为model,view,controller三个层次,同时还应该可以按照实现的技术和逻辑关系划分为页面表现模块,业务控制模块,事务管理模块,工具模块4个模块;也可以按照具体的业务划分模块。下面再介绍一下这两种划分方式:

  • 按业务模块划分
    按实际业务分为用户管理、文章发布、模板维护、前台显示,系统管理五个部分。每一个部分都对应着发布系统的一个方面。这样,model、view、controller的实现组件就分布于五个业务模块之中。
  • 按实现技术划分
    由于我们的实现技术采用了HTML技术和PHP的相关技术,按照这些技术,我们又把整个应用分为页面表现层,业务控制层,事务管理层, 工具模块。这样MVC的3层结构又细化为上面这4个逻辑层(参见下图)


2.1.4 典型的一个用户请求的时序图:
如果你现在仍然对于我所说的这些划分感到很空洞和抽象的话,下面我们来看一个序列图,这个图展示了在MVC下面,一个request是如何响应和实现从数据的操作到页面显示的全过程:

下面分别将分为具体介绍每一个模块的实现方式和在整个应用中的作用

2.2页面表现层
这部分的实现是最为简单的。由模板和使模板实例化的render类来完成。

模板是使用纯HTML来编写,不存在任何的PHP代码和控制逻辑,确切地说,本层不涉及任何商业逻辑,它的用途就是根据选择的模板,和提供的实例的数据,生成一个完整的HTML页面

下图是表现层的一个时序图示意:

简单讲述一下,

  1. 首先是AsisstantObj,这是Asisstant类的一个实例(关于Asisstant,后面我们会详细讨论),AsistantObj在init方法中,会分析表现层所要用到的2个xml配置文件,template.xml和render.xml,将相应的template 和render信息装载到系统中,并且会保存起来,在整个session中都可以使用。
  2. 其次,由HanlderObj,hanlder类的一个实例,调用Asisstant类的lookupRender来找到某个模板对应的render实例
  3. 调用render实例的setTemplate和setRenderData方法,前者是载入并初始化对应的模板文件,后者是设置需要实例化的实际数据
  4. 调用render实例的Show方法。在render实例的show方法中,它会调用自身的一个renderIt方法(这个方法在render类里面是一个虚方法,每一个继承render的类都要实现自己的renderIt方法,这个方法里的主要工作就是根据renderdata生成模板的一个实例,具体实现形式参见下面的模板机制),之后,再调用renderObj的show方法将实例化后的页面显示再屏幕上面。


2.2.1模板机制
我们使用PEAR中的IT和ITX模块作为我们的模板引擎,之所以使用它,是因为ITX类是纯PEAR类,在功能上和PHPLIB中的TEMPLATE类似,但是使用上要简单一些。而且ITX有些特性特别适合这种模板的定制,考虑到我们的页面表现没有太特殊的地方,使用ITX能够满足我们的需求。

关于ITX的具体使用细节,可以参考ITX的源代码,这里是它的一个简单的使用方法说明:

  1. new IntegratedTemplateExtention($root = "")
    构建函数,$root是装载模板文件的目录。注意,这里可能有个BUG,在使用下面的函数装载模板文件的时候,如果装载的文件名中带有路径的话,总是无法找到那个文件,所以我建议你设置$root为模板的目录,然后再使用下面的函数装载模板。
  2. loadTemplatefile($filename, $removeUnknownVariables = true, $removeEmptyBlocks = true)
    从指定的文件中加载模板,$removeUnknownVariables表示是否去掉未使用的变量标识,$removeEmptyBlocks表示是否去掉没有使用的块。
  3. setRoot($root)
    设置模板的目录
  4. setTemplate($template, $removeUnknownVariables = true, $removeEmptyBlocks = true)
    从指定的字符串中加载模板。如果你打算把模板数据存放在数据库中(推荐),这个函数是很有用的。
  5. setCurrentBlock($blockname)
    设置当前要解析的块名
  6. setVariable($variable, $value = "")
    设置变量标识的值
  7. parseCurrentBlock()
    解析当前的块
  8. touchBlock($block)
    设置指定的块,使之即使没有使用,也在输出中显示
  9. parse($block = "__global__", $flag_recursion = false)
    解析指定的块
  10. getBlocklist()
    得到模板中的块的列表
  11. blockExists($blockname)
    判断模板中是否存在指定的块


2.2.2 模板的定义
由于使用了ITX,模板的定义方式很简单,使用<! - BEGIN 标签名 --> <! - END 标签名 -->来定义一个需要实例化的HTML块,使用{varname}来定义需要实例化的变量就可以了。对于循环,我们无需在页面上表现,而是放在render类里控制,我们通过选定一个HTML块,然后反复实例化这个块,就可以生成循环的HTML代码了。下面是一个例子:

<! - BEGIN  LIST -- >
<tr><td>{name}</td><td>{age}</td><td>{sex}</td></tr>
<! - END  LIST -- >
<?php
 $obj->setCurrentBlock('LIST');
 for($I=0;$I<3;$I++){
   $obj->setVariable('name','panfan');
   $obj->setVariable('sex','BOY');
   $obj->setVariable('age',$I);
   $obj->parseCurrentBlock();
 }
?>


这将生成如下的HTML代码:
<tr><td>panfan</td><td>0</td><td>BOY</td></tr>
<tr><td>panfan</td><td>1</td><td>BOY</td></tr>
<tr><td>panfan</td><td>2</td><td>BOY</td></tr>

2.2.3 实例化模板
如在上节所看到的,我们通过给render类设置不同的template和不同的实例化数据,将生成不同的页面。那么,如何将模板和它所对应的render联系起来呢?这是个好问题。为了把复杂的问题简单化,我们没有考虑使用一个通用的能够实例化全部的模板和数据的render,因为这样会造成这个render逻辑非常负责,而是采用许多render,每个render可以实例化一个或者几个相近的模板。每一个模板都有一个它相对应的render类的id,这些定义是我们预先定义好的。我们使用了一个xml文件来定义应用中用到的全部的模板。这个xml文件名叫:template.xml,它的基本结构是这样的:

<templateSet>
<template>
 <id>Welcome</id>
 <name>Welcome</name>
 <useRender>true</useRender>
 <fileName>welcome.html</fileName>
 <renderId>common_render</renderId> 
</template>
<template>
 <id>home</id>
 <name>home</name>
 <fileName>home.html</fileName>
 <useRender>false</useRender> 
</template>
</templateSet>



在这个XML文件里面,<templateSet>定义了我们这个项目中所用到的全部的模板,<template>标签则定义了每个模板的一些关键信息:

fileName属性定义了模板文件存放的路径,name是这个模板的名字,id是这个模板的ID号。useRender表示是否是需要使用render来实例化,如果是false,那么将使用php的require直接生成页面,否则调用相应的render的show方法来显示页面。renderId是render的ID.

template.xml由Assitant类的init()载入系统,以后可以使用Assitant->lookupTemplate(templateid)来取得模板的templateInfo对象,之后通过templateInfo对象的getRenderId方法,取得render的ID,然后调用Assistant的lookupRender(renderId)方法,取得这个render的对象实例,然后使用render->loadTemplateFile()来载入模板文件,设置模板相关的数据:

render->setRenderData(data)

最后使用render->show()

就可以生成相应的实例化的html页面了。

2.2.4 Render
Render是表现层里的主要的类。如上节所说,在Assistant对象的lookupTemplate中会返回一个相应模板的render实例,其中从template.xml中我们可以根据templateid获得renderid,那么如何从renderid获得一个render的实例呢?我们通过另一个xml文件,render.xml来获得相应的render的信息:

<?xml version="1.0" ?>
<renderSet>
<render> 
<id>simpleQuery</id>
<name>simpleQuery</name>
<fileName>simpleQuery.php</fileName>
<className>simpleQueryRender</className>
</render>
<render> 
<id>advancedQuery</id>
<name>advancedQuery</name>
<fileName>advancedQuery.php</fileName>
<className>advancedQueryRender</className>
</render>
<render> 
<id>QueryList</id>
<name>advancedQueryList</name>
<fileName>advancedQuery.php</fileName>
<className>advancedQueryRender</className>
</render>
</renderSet>



在这个XML文件里面,<renderSet>定义了我们这个项目中所用到的全部的render,<render>标签则定义了每个render的一些关键信息:
id:是这个RENDER的唯一标识。
name:定义了这个render的名字描述,缺省和ID是一致的。
className:定义了这个render的PHP类名,唯一。
filename:是实现了这个RENDER类的PHP文件名。

为了便于操作,我们对于这里的每一条render信息,定义为一个类:renderInfo

renderInfo继承了info类。info类的定义:

由于render的信息和info类提供的属性是一致的,因此,renderInfo的类定义只是简单的继承了info:

class renderInfo extends info{
........
}


后面会讲到,之所以这样做,只是为了方便Assistant的init的载入。

(待续)

参考资料



关于作者
author潘凡(Night Sailer):北京赛迪数据有限公司工程师。主要研究兴趣是Perl,PHP与XML的应用,PHP的MVC开发模式,PERL-GTK的使用。您可以通过 E-mail:nightsailer@hotmail.com 跟他联系。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值