2.1.1应用的技术
MVC在某个特定应用上的实现是与该应用的具体技术密切相关的。在网站开发方面,JAVA是比较成熟和典型的,以一个EJB的项目为例,大部分的系统可能采用HTML技术,和J2EE(Java2,Enterprise Edition)的相关技术,包括:HTML,JSP,JavaBean,EnterpriseJavaBean,Servlet。这些技术分布于model,view,controller三个模块之中:view部分用到HTML、JSP和JavaBean技术;controller部分用到Servlet、StatelessSession Bean 、JavaBean 技术;model部分可能用到EntityBean、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是如何响应和实现从数据的操作到页面显示的全过程:
下面分别将分为具体介绍每一个模块的实现方式和在整个应用中的作用
这部分的实现是最为简单的。由模板和使模板实例化的render类来完成。
模板是使用纯HTML来编写,不存在任何的PHP代码和控制逻辑,确切地说,本层不涉及任何商业逻辑,它的用途就是根据选择的模板,和提供的实例的数据,生成一个完整的HTML页面
下图是表现层的一个时序图示意:
简单讲述一下,
- 首先是AsisstantObj,这是Asisstant类的一个实例(关于Asisstant,后面我们会详细讨论),AsistantObj在init方法中,会分析表现层所要用到的2个xml配置文件,template.xml和render.xml,将相应的template和render信息装载到系统中,并且会保存起来,在整个session中都可以使用。
- 其次,由HanlderObj,hanlder类的一个实例,调用Asisstant类的lookupRender来找到某个模板对应的render实例
- 调用render实例的setTemplate和setRenderData方法,前者是载入并初始化对应的模板文件,后者是设置需要实例化的实际数据
- 调用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的源代码,这里是它的一个简单的使用方法说明:
- new IntegratedTemplateExtention($root = "")
构建函数,$root是装载模板文件的目录。注意,这里可能有个BUG,在使用下面的函数装载模板文件的时候,如果装载的文件名中带有路径的话,总是无法找到那个文件,所以我建议你设置$root为模板的目录,然后再使用下面的函数装载模板。 - loadTemplatefile($filename, $removeUnknownVariables = true,$removeEmptyBlocks = true)
从指定的文件中加载模板,$removeUnknownVariables表示是否去掉未使用的变量标识,$removeEmptyBlocks表示是否去掉没有使用的块。 - setRoot($root)
设置模板的目录 - setTemplate($template, $removeUnknownVariables = true,$removeEmptyBlocks = true)
从指定的字符串中加载模板。如果你打算把模板数据存放在数据库中(推荐),这个函数是很有用的。 - setCurrentBlock($blockname)
设置当前要解析的块名 - setVariable($variable, $value = "")
设置变量标识的值 - parseCurrentBlock()
解析当前的块 - touchBlock($block)
设置指定的块,使之即使没有使用,也在输出中显示 - parse($block = "__global__", $flag_recursion = false)
解析指定的块 - getBlocklist()
得到模板中的块的列表 - 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{
........
}
|