内容


MVC 模式、类封装还是黑客代码

合理设计 PHP 项目

面临难题

编码对于合格的 PHP 程序员来说并不是什么难事(也许只是花费时间长短的问题),因此系统分析和设计这一阶段就显得尤为重要。对于一个担任 PHP 项目的系统分析员来说,面临着两个难题:

  1. PHP 语言本身的限制。
    这一点在复杂系统的面向对象设计中尤其显著。 PHP 的面向对象特性在现有版本中虽然得到了改善,但是还不甚健全,根本不足以担任面向对象设计的实现语言;即使眼光长远一些,在即将释出的以 Zend Engine 2.0 支持的全新 PHP 中,面向对象特性也不会像现在流行的 Java 或者 C++ 那样(关于这方面的内容可以参见我在 developerWorks 中国网站发表的另一篇文章)。但是如果采用完全面向过程(准确说是面向 Web 页面)的方式,可以想见整个系统的设计会非常复杂,而由此带来的编码复杂和维护困难更加难以应付。
  2. 现有资料的严重缺乏。
    这是众所周知的现象即针对 Web 项目的系统设计资料不足;而在这些有限资料中,关于 PHP 的设计资料又非常匮乏。如果本公司或本人也没有相关的技术积累,系统分析员只能在黑暗中摸索方法(更坏的两种情况,一是照搬其他项目比如 Java 或者 C++ 的设计,二是认为项目简单而不负责任的草草了事)。

认识面对的系统

既然如此,采用何种方法妥善处理 PHP 系统的分析和设计?最初的构想应该需要分清项目承担任务的类型:

  1. 涉及大量客户本身或者客户所在行业的商业逻辑的项目,包括办公系统、订单系统以及其他商业系统。
  2. 简单网站项目,包括一些需要承担高访问量或要求快速响应的项目比如品牌网站或者活动网站以及其他一些网站。
  3. 综合性网站项目。通常包含多个相对独立的子系统比如新闻子系统、论坛子系统、产品陈列子系统等等。

PHP 的设计初衷在于解决后两种项目的迫切需求,语言本身对于这些项目进行了良好的改造。而众多的 PHP 开发者对这些项目也具有或多或少的经验,相关书籍中的范例也大都围绕于此。相对说来第一种系统所有的资料不多,各种出版物对其内容也很少提及。因此在本文中将题所述对第一种类型的项目进行详细讲述(有关 MVC 模式和类封装),同时附带提及第二种项目(有关黑客代码)以及第三种项目的设计方法。当然,并不是被归类的这些项目就只能采用本文描述的方式,系统分析员需要权衡各方面因素加以选择。

方案一:涉及大量商业逻辑项目

如何分离用户界面和后台操作?如何避免将商业逻辑混淆于一般的流程控制中?作为一个严谨的商用项目,就需要考虑很多类似的问题。对于由 PHP 担当的这类项目,贯彻 Model-View-Controller(MVC)模式的设计是一个非常好的方法。

理论描述

在这里我不想多加解释 MVC 模式本身 -- 简单的从字面上以及应用上说,通过将系统的设计分为 Model 模型 / 逻辑、View 视图 / 界面、Controller 控制 / 流程三个逻辑部分达到良好的项目效果,以此便利各部分开发者的工作并降低日后的维护成本。(如果您熟悉 JSP 开发的 Model 2 模式,可以发现它也是 MVC 模式的很好体现。)就现实的项目开发而言,现存的很大问题包括网页设计人员和程序开发人员的工作交错和冲突以及商业逻辑嵌入页面造成不可重用也很难维护等等。引入 MVC 模式一方面可以为系统的总体设计指出明确的方向,对于开发团队的分工也是良好的指导。

既然依照 MVC 模式要求对系统的总体结构在逻辑上分成三部分,那么团队的开发者中也存在着针对各个部分的开发者。

开发者角色相关系统逻辑职责
网页设计人员View 视图 / 界面设计所有用户界面的网页模板。
控制流程开发人员Controller 控制 / 流程编写系统流程中的所有 PHP 页面。
商业逻辑开发人员Model 模型 / 逻辑开发系统设计中规定的各个类(其中的方法)。

由以上的表格可以看出,传统的网页设计和程序开发的人员分工被打破而取而代之的是根据系统逻辑划定的职责。对于网页设计人员,职责并没有改变,准确说由于这样的划分避免了以往与程序设计人员的纠纷,他们完成的只是网页模板,因此只需关注于纯粹的网页代码(主要是 HTML,也许会有其他客户端的代码比如 WML 之类)而根本不需要被服务器端的 <? … ?> 干扰。程序设计人员则被分为两部分:其中比较容易把握的是商业逻辑开发人员,他们的任务是根据系统分析员给定的模块(准确说是类方法)完成之,在他们手中的 PHP 更像一般的程序设计语言(比如 Java)而与 Web 没有什么关系;一时较难接受的是控制流程开发人员,他们的任务是在实现系统设计时制定的系统流程的同时,根据客户端的输入调用商业逻辑(相应的类方法)以及输出更新的界面(对设计网页模板进行处理),在他们手中 PHP 可以充分发挥 Web 编程语言的优势。

代码组织相关的话题

这样的观念有些抽象,没有实例的演示很难接受。在举例之前先介绍一下我对这类工程的推荐代码结构:

一级目录二级目录三级目录备注
/project_name项目源代码根目录
/Templates网页模板目录(View)
/admin管理控制台目录 /admin 下的网页模板
/Include商业逻辑目录(Model)
/Temp临时代码目录(可选),可供开发者进行一些试验代码的测试
/images图片目录,网页模板采用
/css样式单目录,网页模板采用
/scripts客户端代码目录,网页模板采用
/admin管理控制台目录(可选),包含所有后台管理的功能代码
/other_dir对应与源代码根目录下的 /other_dir,包含管理该类的功能代码
/other_dir其他与相应功能相关的目录,比如与用户相关的 /member 目录或者与从产品相关的 /product 目录等等
/config.inc.php全局配置变量,定义系统中的全局变量
/security.inc.php安全策略控制(可选)
/error.php错误控制返回页面(可选),也可以采用静态页面如 /error.html 或者其他页面名称

看完之后您是不是被唤起了一点使用 Java 进行 Web 开发的记忆?比如 WEB-INF 目录下的 classes 目录和 lib 目录以及 web.xml 都是开发中的规则 -- 虽然支持 PHP 的 Web 服务器不可能像 Java 应用服务器那样自动加载这些目录下的文件,但是规定一个合适的代码组织模式还是非常有利于开发的便利和后续的维护的。(我开始考虑 PHP 项目的代码结构就是由 Tomcat 的开发手册中获得了启发。)

一个用户登录的例子

根据以上的代码目录,前文所说的 MVC 模式的实现可以得到更简单的解释。以最常见的用户登录功能为例,设想 /project_name 目录下有一个 /member 目录包含有关于用户的一切功能,其中包含了 login.php 页面接受用户登录使用的用户名称和密码,index.php 页面是登录完成之后的用户主页,而 /project_name 目录下的 error.php 是登录失败后的错误显示页面。

  1. 用户通过系统的其他部分请求进入用户主页即 /member/index.php 页面,此时该页面判断用户情况:已登录用户则直接显示本页内容(可以采用检查 session 等方法);未登录用户则需要登录(重定向到 /member/login.php);出现了未知错误(重定向到 /error.php)。同时采取相应的反应。
  2. 假如用户被引导至登录页面即 /member/login.php 页面,该页面接受用户的登录信息(用户名称和密码),并判断是否正确登录:正确登录则再次重定向到用户主页 /member/index.php ;登录错误则重定向到 /error.php 。
  3. 假如用户被引导至错误显示页面 /error.php 页面(无论是从以上哪个页面前来),都会显示错误信息。

流程图示如下:

根据以上的文字描述和图示,再结合 MVC 模式的实现,可以非常轻松的写出这几个页面的框架代码:

  1. 先看简单的页面 /error.php:
  2. 然后是 /member/login.php:
  3. 最后是 /member/index.php:

(注意:以上代码只是片断,而且没有考虑项目全局,只起演示作用)

关于 Controller

首先可以明确的是,以上的三个页面代码就是前文所说的 Controller 控制 / 流程代码。很明显,他们的不包含特定的操作,也没有一行网页代码,有的只是与前面流程图一致的流程控制代码(放眼望去,这些页面的共同特点是充满了引用网页模板并输出、取得对象并执行其某个方法或者重定向)。

再选择其中的一个页面 /member/login.php 详细的解释。整个页面通过判断是否提交表单分为两个部分:显示登录表单供用户填写和处理登录信息。作为前者直接引用一个处于网页模板目录 /Templates 下对应该页面的 member_login.dwt 并在解析后输出;作为后者先取得一个 Member 对象(该对象出于商业逻辑目录 /Include 下的 Member.inc.php 中),然后获得登录判断的结果后进行重定向。在这个控制页面的代码中,member_login.dwt 作为 View 视图 / 界面出现,类 Member 作为 Model 模型 / 逻辑出现,而页面代码本身就 Controller 控制 / 流程。下面就是加入标示的 /member/login.php 框架代码:

(关于模板类以及在 MVC 模式中的应用,可以参考我在 developerWorks 中另一篇文章《在 PHP 世界中选择最合适的模板》)

关于 Model

既然谈到了 Model,下面就是另一个重要的话题:类封装在 PHP 项目中的应用。

请注意用词 " 类封装 "-- 这和 " 面向对象 " 或者其他什么 " 采用对象设计 " 的方法有着本质的不同。 " 类封装 " 只是讲述了将商业逻辑采用类方法的方式封装成各个不同的类,因而这里的 " 类 " 并不是因此采用了面向对象设计出现的 " 类 "-- 准确的说,这里的 " 类 " 其实是对一系列相关功能模块进行合并的结果。

为什么不直接采用面向对象的方式而是采用这种看起来不伦不类的办法去设计系统呢? PHP 不是具有面向对象特性吗?不错,PHP 具有这样的特性,但是非常不完全(可以参考我在 developerWorks 的另一篇文章《从 Zend Engine 2.0 的设计蓝图(草稿)看 PHP 的将来》)。举例来说,PHP 是没有接口这一概念和实现方法的,同时也就没有什么多重继承、方法重载之类的典型面向对象特征。如果非要采用面向对象的设计方法,也许在概要设计阶段可以非常轻松,但是详细设计阶段就会比较苦闷,而如果还有幸坚持到编码阶段简直就是苦不堪言了。另一方面,如果不在系统中引入类的概念,而是采用函数来实现模块功能,那么可以想象在一个采用这样 " 纯粹 " 的中大型系统中会有多少的函数,由此带来的麻烦非常明显。

还是回到 PHP 语言本身。虽然 PHP 提供不了什么实际的面向对象支持,但是还是提供了对类以及其中的属性和方法的定义。那么自然而然可以想到的是采用类的方法封装相关函数模块,既可以借鉴一些对象设计的优点,又可以避免完全采用函数模块的一些缺点。

(一些采用函数模块的系统会采用这样一种方式:将相关的函数编写在相同的文件中,这样在引用时可以引入单独的文件。比如 Member.func.php 这个文件中包含了所有与用户相关的操作,在处理用户登录时可以先 require 这个文件,然后调用诸如 member_login() 这样的函数。但是这样的方式仅仅解决了系统中众多函数的代码组织问题,没有解决名字冲突的问题。下面的举例中就会看到。)

比如上文的用户登录实例中,如果采用函数模块的方法,代码也许是这样:

而采用类封装的方法,可能就是这样:

也许您会觉得代码并没有什么区别(甚至看起来采用函数模块的代码由于不需要取得新的对象而显得更简洁一些),而真正的不同是发生在 include 的文件里面。采用函数模块的方法将相关的函数集合在一个文件中加以组织(有些系统还不能做到这一点,那么就会造成异常混乱的局面),而采用类封装的方法在每一个文件中声明一个和文件名相同的类(比如在 Member.inc.php 声明一个 Member 的类,这一点和 Java 的规定相似);而在使用时,都需要先进行 include(如果采用函数模块又没有进行很好的组织,也许有些人就会很 " 简便 " 的将所有函数 include 进每一个页面 --PHP 可不是 Java 那样编译执行,光是解析这些函数就会花费一段时间),但是关键就在于采用类封装的方法可以清楚的指明调用的位置 -- 某个类(Member)的某个方法(login):从避免名字冲突的角度来说这一点是非常成功的;而对于代码检查和维护而言,方便程度更是不言而喻。设想一个页面需要完成若干功能,因而需要 include 数个文件:采用函数模块的方法不能够轻易的从函数调用中找到函数本身所在的文件(如果函数名称或者 include 文件名称没有什么统一规则,那么这个工作就非常艰巨了),而采用类封装的办法可以根据类名称和类文件名称准确定位类方法代码的位置。(也许您会认为这样一个小小的好处不足挂齿,但是经历一个维护工程之后也许就不会再有什么异议。)

以上是采用类封装方法的原因,决定采用这种方法设计系统只是第一步;完成整个系统的设计还有很多可以借鉴的经验。

  1. 部分设计可以借鉴面向对象的思路。虽然 PHP 中没有接口和抽象类的定义,继承机制也非常不完全,但至少具备了基本的类定义和简单的继承关系。类似 " 公司-雇员 "、" 卖家-商品-买家 " 这类显而易见的关系可以很容易在系统中通过类和类关系定义。既然 PHP 可以做到这一点,就按照实际的逻辑关系去定义即可。
  2. 经常会在系统中出现的另一个情况是关于个体和列表的关系 -- 这样说也许难以理解,想象一个 BBS 系统中的帖子列表和每个帖子之间,就是这样的关系。根据设计经验,这样的关系大量存在于 PHP 或者其他 Web 系统中。对于这类关系,我个人建议可以采用以下 Item 和 Item_List 的类封装方式:
  3. 由于 PHP 对于类的成员变量和方法并没有语法上的访问限制(均为公开),因此会带来对象使用方面的某些混乱。基于此,建议在开发团队的代码规范中加以规定,从代码应用的级别上控制这一情况:
    首先,可以通过对成员变量和方法的注释来说明其属性,由此使用该对象的其他开发人员可以了解自己的使用方法是否触犯了规定的访问限制。(如果采用 phpdoc 等自动文档生成的工具,开发人员甚至可以在不翻阅类源码的情况下通过浏览类文档正确使用它。)
    其次,对于成员变量访问限制的考虑,可以将一些主要的、经常需要被访问或更改的变量(在注释中)声明为公开。这样的作法可以省却大量 get() 和 set() 方法的代码 -- 虽然在其他的面向对象语言中这一点被认为非常丑陋,但是记住 PHP 不是 Java,只要这样的用法合理,就应该大胆使用。
  4. 从上面的示例代码中您也许已经注意到了注释的比重 -- 虽然大家都了解注释的重要性,但是仍然有必要提出。这个示例中采用了 Javadoc 的样式,利用现有工具也可以很容易的直接生成文档(当然您和您的开发团队也可以定义自己的合适注释样式和文档生成工具)。对于系统分析员来说,您在设计阶段完成之后交付给您的开发伙伴的代码部分很可能就是这些注释占绝大部分的框架代码;你们之间交流的工具除了那些没完没了的图表之外就是这些程序员最熟悉的代码和注释了。

在 PHP 系统中进行类的设计虽然不像构建面向对象系统那样需要各种合理的模式介入(也没有这样的 " 本钱 " 为之),但还是需要一番思量的。逻辑上的合理性和操作上的可行性都是检验的标准。

(说到类设计,又想到了适合 PHP 开发的 IDE 问题。据我所知比较专业一些有 Zend 出品的 Zend IDE ;另外还有作为 JBuilder 的 Open Tools 出现的借助 JBuilder 的 PHP 开发工具;不过最常用的还是 PHPEd 或者 UltraEdit 之类的编辑器。如果现有的编辑器可以非常聪明的支持 PHP 的类设计和代码实现就非常理想了。)

关于 View

最后说到的是 View 方面,虽然这部分内容与网页设计人员联系比较紧密,不过 PHP 项目(以及其他 Web 项目)的系统分析员也必须关注这一话题。可以看出 MVC 模式的应用使得网页开发人员和程序设计人员的各自工作成果不会像以前那样互相影响,自然可以提高各自的工作效率(相互关系也许会比以前更加融洽一些)。但是对于系统分析员来说,将用户界面分离为各个独立的网页模板需要进行许多分析工作。

首先是确定整个系统的流程,这一点在系统设计的初期就应该做到。而对于 View 视图 / 界面和 Controller 控制 / 流程来说,所有需要的页面都是围绕此流程产生。不过通常此时能够在流程图上看到的也许只是相关的参数在各个页面之间传递,却不能了解各个页面展示的内容 -- 这就是下一步分析用户界面需要进行的工作。

在分析用户界面的工作中,第一步可以确定各个页面核心、对于完成流程必不可少的用户界面元素(表单和表单域、链接等);第二步是确定页面中需要出现的导航内容;最后还需要依据流程复核。还是以上文的用户登录为例。对于 /member/login.php 这个关键的页面,第一步可以确定的是在用户提交之前应该显示一个表单,表单包含两个文本框供用户输入用户名称和密码;而提交之后根据流程在本页面中不需要有用户界面,取而代之的是利用 Controller 控制 / 流程这一逻辑层进行重定向。而第二步需要制定该页面中(准确说是在显示登录表单时)需要提供的导航链接,在这里可以加上到系统的主页或者其他非注册用户页的起点的链接(方便用户临时决定取消登录)以及一个注销现有用户的链接(针对已登录用户)。之后进行复核,此时也许会发现这一设计似乎没有考虑到在登录前更好的区别是管理员登录还是普通用户登录,那么就可以在表单中增加一个隐藏域表示选择登录的用户是准备以管理员还是普通用户的身份进行登录。

确定完用户界面的元素,并不意味着可以将这些分析结果交付网页设计人员进行制作了;还有最关键的一步没有实施 -- 为分析完成的各个页面制定模板所需的变量名称。对于以上的用户登录实例,如果系统有识别曾经登录用户的功能(依据之前访问时在客户端设置的相关 cookie 值)并且把这个用户名称显示在登录表单的用户名称一栏,此时就需要在 member_login.dwt 设计中说明该表单域将被赋值为一个模板变量(比如 {USERNAME})。这一步骤完成之后就可以交付网页设计人员进行制作了。

需要指出的是,在编码阶段很可能局部的一些系统设计需要进行修改,这其中也许就包括对网页模板的修改,需要仔细处理。

对于代码组织的补充说明

还有几个文件和目录没有在上文提及:

  1. config.inc.php -- 如果您熟悉 phpMyAdmin 或者其他 phpWizard.net 释出的项目,就应该非常清楚这个文件的作用:定义本项目范围内的全局变量(在每个页面中被 include)。我个人认为这是一个非常良好的设计,因此也提倡在项目中应用。另外,为了保证与项目中其他的变量冲突,建议在该文件中定义一个多重数组,而各种全局变量都以该数组的某一个值出现。这样方便团队中的其他开发者只需要避免一个变量名的使用,而不是避免所有 config.inc.php 中出现的变量名。使用这个文件的另一个好处是由于将关键的变量(比如与服务器环境相关的变量)集中定义,可以方便的安装和移植整个项目。
  2. security.inc.php -- 顾名思义这个文件控制并实施整个系统的安全策略。关于安全问题,可以想到的是两种控制方案:在每个控制流程页面顶端针对本页面加以控制以及采用一个控制文件整个控制并被加入每个控制流程页面。我个人提倡采用后一种方式,原因也很简单:定义简单而且维护方便。虽然相比每个页面单独定义,也许会损失一点点效率(一些不需要安全控制的页面也需要 include 该文件并进行判别),但是获得的是对系统安全的整体控制以及代码维护的便利(损失一个 if … else …的判别换取这样的结果还是很值得的)。
  3. /Temp -- 很明显存在于这个目录下的都是一个临时文件,并且这个目录其实并不会出现在项目正式发行的版本中。如果开发时对一些函数的使用不甚明了或者试验一段没有相关经验的代码,都可以在此目录下建立文件;因为该目录就位于项目代码之中,可以非常便利的取得项目运行的上下文环境,大大降低了试验代码的成本。
  4. /admin -- 通常对系统的后台管理内容应该放置在一个独立的目录中,我个人比较喜欢 admin 这个简写词(当然也有一些情况系统分析员认为不应该设置一个容易猜测的管理目录名称以增加一重对系统安全的保护)。
  5. /css 和 /scripts -- 都是与网页设计也就是 View 视图 / 界面有关的文件存放处,分别是样式单和客户端脚本。这样做的好处在任何一本讲述网站规划的书籍中都会有所提及。

方案二:简单网站项目

系统性能是这类项目追求的首要目标,而与此同时系统的维护和扩展几乎可以不用多加考虑。(也许这句话听起来有些绝对,但是根据客户的需求和项目的性质判断,尽最大可能以最短时间满足客户的需求并使得系统高效运转就是项目成功的最好检验标准。)因此,也许这类项目就是 PHP 黑客的天堂(曾经我也是一个过分追求 PHP 使用效率的人)。由于这类项目的特殊性,这里讨论的范围不仅仅局限与系统设计而是从组建项目小组开始直到交付项目的过程。

首先需要关注的是参与项目的人选(虽然也许这是项目经理的职责,但是最熟悉 PHP 项目特点的系统分析员应该参与)。在 PHP 开发人员方面,至少应该选择对 PHP 各种函数较为熟悉的开发者(这类项目不适合作为现实项目以培训参与的开发新人),如果公司中还有能够在源码级别理解 PHP 的人员就更加理想(不过通常对于一般的 PHP 开发公司是不可能的)。而在网页设计人员方面,最好可以选择一些略通客户端(比如 JavaScript)以及服务器端(最好是 PHP)脚本的人员;因为这类项目的一大特点即是单个网页代码量较大且夹杂网页代码(通常是 HTML)、客户端脚本(比如 JavaScript)和服务器端脚本(比如 PHP),加入了解各种脚本语言的网页设计人员的目的不是为了增加团队的 PHP 开发力量,而是避免在修改网页时影响程序设计人员的工作。

其次就是面向过程,准确说是面向页面的系统设计。相对第一类项目,客户的需求在该类项目中表现得非常清晰,而且一般长期进行 Web 开发的公司对于这类网站项目也应该有一定的设计经验积累。设计中需要围绕整个系统的流程,包括每个页面的输入参数和输出内容(包括网页中出现的除导航链接之外的功能性链接),以求完全满足客户的需求;另一关键在于确定系统安全策略,在这类项目中主要是用户等级的确定和页面的访问权限,并给出实现的方式。不过还需要指出的是,这类项目中由编码阶段返回设计阶段的情况并不少见,对于局部设计(比如页面传入参数或者输出链接)的更改应该加以及时控制。

最后是针对代码和数据库的优化。在这类项目中需要适当鼓励开发人员的黑客态度。推荐的办法是系统分析员给出每个页面的伪代码(框架代码),而局部的实现则由各个程序开发人员和网页设计人员进行。

对于 PHP 代码方面,通常可以从如下几方面考虑:

  1. 算法的选择和功能实现的方式:模块级别的优化,可以由几名开发人员共同讨论解决;
  2. 函数的使用:代码级别的优化,需要开发人员对各类函数有清楚的认识,至少养成多多参考函数手册的习惯;
  3. 数据库的查询和更改即 SQL 语句的使用:如果公司中有相关数据库系统的管理人员,可以就一些优化问题征询他们的建议;
  4. 其他应该避免的问题:比如拷贝代码、等不良代码情况。

而根据我的经验,通常会在这类项目中撰写的黑客代码如下:

  1. 循环语句的使用特别是在查找时的应用:此时注意 while 和 for 的区别(想必大家在大学课堂中都做过这类的程序),这也是良好的编程习惯;
  2. SQL 语句的优化:首先是尽量避免多余的数据库交互,这是提高效率非常重要的一点;其次是不要害怕长达几行的语句而宁愿使用所谓简单的语句;再次是认真考虑查询语句返回的字段,减少不必要的数据。
  3. 表单提交值的获取,比如复选框和文本域。精巧的表单域名称设计可以减少一定的代码量,而处理提交值时也需要注意处理的方式。

黑客代码在这类项目中值得鼓励,不过最好在每段代码旁附上尽可能详细的注释。

由于该类项目的特殊性,完成项目的关键不仅仅在于系统设计阶段,因此给出项目开始、系统设计、编码以及测试、交付这一过程的简单描述:

  1. 挑选合适人员组成项目小组,可以考虑销售人员和客户代表的加入。
  2. 系统分析员可以简单的从客户的需求以及以往项目经验的结合中总结出系统所需的每个网页并对其功能作出描述,同时确定初步的安全策略。这一步骤中可以加入销售人员和客户代表的加入。(此时网页设计人员正在准备提供给客户的一系列网站形象页面。)
  3. 详细设计中需要为每个页面确定位置和名称,更加关键的是确定输入参数和输出内容以及不同级别用户对于网页的确切访问权限。同时进行数据库设计。该阶段完成后至少应该提供系统的流程图(包括访问权限标识)以及数据库设计资料。
  4. 网页设计人员和程序开发人员拿到相关资料各自进行工作。对于前者,根据客户认可的一套形象设计每个页面;对于后者,开始进行 " 兴奋的 "(因为此时要求的是高效简介的代码 -- 黑客代码)编码工作。此阶段工作中遇到的困难均需要反馈到系统分析员处,可能返回以上的第 3 步甚至第 2 步进行设计修改。
  5. 程序编写和网页设计结束后需要有一段整合的时间,也是程序开发人员对代码进行自我测试的阶段。同时在这一阶段可以进行的是代码(包括网页代码和程序代码)和数据库的优化工作。此阶段结束后应该可以提供一个完整的系统。
  6. 真正的测试阶段通常都比较仓促,这方面的技术和经验公司也应该有一定积累(如果有条件希望采用一些软件工具进行稳定性和抗压能力的测试)。最后是提供一个可 Web 访问的地址供客户测试。此阶段完成后可以提供正式交付客户的系统。

方案三:综合性网站项目

已经有一些大型网站使用 PHP 作为主要的开发语言。对于这类项目,单纯从 PHP 技术方面值得提出的话题不多,简而言之还是根据网站各部分的实际应用情况(访问强度、操作行为等)选择以上提出的两种项目设计方法或者综合使用。除此之外,根据我个人的经验,项目团队的组织和协调工作以及项目各期完成后的维护工作等等是较之单纯的技术更加关键的因素。

对于这类项目,可以提出的建议是,适当采纳一些开源软件对于快速、优质的完成项目很有好处。项目的某些部分可以直接引入开源软件项目的设计甚至是代码,不过前提是系统设计人员对这些引入的项目需要非常了解,同时需要做好这些孤立的开源项目和整个项目之间的接合(比如安全策略的考虑和全局变量的引用等)。

举例来说,根据客户要求某个综合网站需要以下的功能:

  1. 复杂的新闻发布;
  2. 需要不多管理功能的在线论坛;
  3. 简单的产品陈列;
  4. 需要用户管理。

(很明显这是一个企业网站的雏形。)

其中的 1、2 项很明显可以借用一些成熟的开源软件项目,而 3 项由于客户需求简单自主开发比较符合成本。由此看来 4 项则是整个系统中最重要的部分 -- 需要做好与 1、2 项使用的开源软件项目的用户管理集成工作(3 项由于自主开发的原因集成工作非常简单)。(某些技术积累较好的公司甚至对于以上提及的集成部分都有简单的解决方案,那么这样一个网站项目的完成所需成本非常微小。)

几个特殊的功能点

另外还有一些通常项目中都会出现但是必须妥善处理的功能点:

1. 数据列表分页。
关于这个功能,互联网上的中文和英文资料都有许多。具体的技术和实施细节不需要多说,这里只需要指出的是:

A. 建议封装成某一个工具类的方法或者其他可复用的形式 -- 这样的好处不言自明,任何人都不希望系统中只要存在数据列表分页的时候都会出现一堆几乎相同的代码。

B. 如果针对一些效率要求较高的项目(例如上文提到的 " 简单网站项目 " 类型),应该直接使用 PHP 自带的针对特定数据库系统的操作函数以及与该数据库系统相关的结果集截取技术(SQL 语句),比如 MySQL 中的 'LIMIT start, offset' 之类;其他一些需要系统设计工整合理的项目(例如上文提到的 " 设计大量商业逻辑项目 "),如果采用了通用的数据库接口,出于兼容多种数据库系统的考虑,可以采用此接口完成结果集的筛选,以损失的效率换取系统更好的可维护性和可扩展性。也就是说,对于采用特定数据库操作函数还是第三方通用数据库接口来实现数据列表分页,需要考虑系统的性能和扩展两方面因素。

2. 错误控制。
这一点在上文之中也有提及。除了建立属于工具类的错误类之外,最好可以建立专门的错误显示页面。该页面既可以是静态的 HTML 页面(表达一些对用户的歉意和出错之后的处理指导)或者动态的 PHP 页面(可以包含具体的出错原因和地点以及其他更详细的信息,前提是在系统安全策略允许提供这些信息)。而错误类的任务就是接受正常的程序中抛出的错误,进行必要处理之后将信息一起重定向在错误显示页面上。

同时,建立出错页面对于开发阶段也有一定好处,可以弥补现有 PHP 缺少类似 try{ … } catch{ … } 块的违例控制的缺点,将调试中的错误或者输出通过错误类抛出并显示出来。

3. 上载与下载。
对于 PHP 来说,上载的实现并不会像其他流行的 Web 开发语言那样需要第三方程序的支持,内建的机制可以非常简单的处理。不过这里提及的是一些复杂的上载功能实现。考察以下一个处理附加文件的流程:

该功能使得用户在撰写新的消息时可以附加其他文件,而且在消息没有提交之前可以随意的对已经附加的文件进行删除或者继续增加。这种需求会体现在许多办公相关的系统中,作为有经验的系统分析员应该在系统设计阶段制定完成针对该类功能的实施计划。比如在图中所示的流程中,其实是通过一个或者多个表单的互相提交完成(具体设计不再赘述,提供相关的 PHP 文件参考;另外的一个直观的例子就是多数免费邮件系统的添加附件功能,如果有兴趣可以考察一下)。

至于下载,将文件置于服务器 Web 可访问目录下、提供访问者真实文件路径是最简单的解决办法;不过一些系统中对文件的下载基于某些安全策略需要进行身份方面的判别方可予以下载,这样的方式就会带来隐患。通常采用的方式也许是将文件放置在 Web 可访问目录以外的服务器文件系统中或者存储进数据库系统 -- 都需要一个简单的程序取得文件内容并直接返回给发出请求的用户(这其中涉及到一些 HTTP 输出头的问题请注意,提供一个 PHP 文件代替具体叙述)。在系统设计时针对不同的需求可以采用相应的办法。

4. 客户会话 session 的保持
PHP 的现有版本已经内置了对 session 的支持,通常项目中都使用这样的方式;一些特殊需要的项目(比如分布系统)也许会采用复杂一些的处理方式。

在客户端,通常采用的是设置 cookie 以识别特定的客户,另一个可以应付不支持 cookie 的客户端的方法是采用 URL 重写加入足够标示特定客户的字符串。从这方面来说,采用 PHP 内置的 session 支持最为理想,因为它可以自动的进行客户端的 FALLBACK:如果客户端支持 cookie,那么就顺其自然;如果 cookie 不被支持,就采用 URL 重写方式 -- 一切都不需要开发者干预。如果采用其他 session 处理方式,或者自己编写适应需要的 session 库,需要注意的就是怎样处理客户端存储数据的问题 --cookie 还是 URL 重写还是两者兼顾。

在服务器端,简单说来只需要针对以字符串标示的每个特定客户存储相关的数据即可 -- 可以采用的方式多种多样,通常的方式是文件和数据库。 PHP 内置的 session 支持中,默认的支持方式是在系统的临时目录或者制定的目录下为每个客户建立一个文件存储其数据;当然也可以修改设置使其支持数据库的方式或者其他方式。如果自己编写 session 库,根据系统的需要选择一种合适的存储方式即可。值得指出的是,对于分布系统,如何共享服务器端的 session 信息是需要极大关注的。

另外,在设置 session 变量的时候,PHP 内置的 session 库支持对象作为变量值(实际上所有的变量值,不论是一般的变量还是数组或是对象,都在经过串行化之后被存储),也就是说,以下代码是可用的:

这一点对于上文提到的一些商用系统是有益的:首先,可以使用关于用户的对象作为一个 session 变量值存储一套信息,而不是割裂的多个 session 变量;其次,如果具有类似购物车的功能,可以以非常符合整个系统设计的方式(即前文所述商用系统的设计方式,强调类封装)将该购物车对象放入 session 中。

5. ……其他和特定项目有关的功能点……
如果能在系统设计阶段就预见并解决这些功能点固然很好,即使有少量未发现的功能点遗留到了编码阶段也并不可怕 -- 通常这样的遗漏并不会影响整个系统的架构,只是需要返回设计阶段加入相应的内容和文档即可。毕竟对于相关项目经验不太丰富的系统分析员来说,做到设计阶段对这类功能点了然于胸是不太现实的。

关于其他

对于 PHP 的争论从前很多,不过自从 Java 在 Web 方面的优势越来越进入人们的视野之后,这样的争论倒偃旗息鼓了 -- 看来大家都达成了共识 --PHP 对于严谨的商用系统还是无能为力。不过基于此就一味否定 PHP 在商用系统中的应用也不大客观,毕竟 PHP 还具有低成本的优势(这里的成本包括开发成本、使用成本和维护成本)。本文的目的除了讲述一些 PHP 系统设计的方法之外,也希望吸引一些开发者或者企业采用 PHP 构建合适的商用系统。

另外,本文仅仅是我自己的一些经验,如果您看到这里时候已经有了自己的一些想法,我非常乐意与您分享 -- 能够推动如 PHP 这样无商业支持的开源软件的发展,毕竟是一件非常令人兴奋的事情。(从这方面来说,我甚至想撰写关于 PHP 开发的文档资料和示范项目,就如同 Sun Microsystems 为 J2EE 发布的 Blueprint 和 Java Pet Store-- 可惜暂时受到时间、精力以及个人能力的限制 -- 也许春节假期是一个好时机 :)


下载资源


相关主题

  • 本文中提及的文章
  • 其他参考资料
    • PHP 官方网站 --http://www.php.net包含软件和文档以及使用情况等(本文成文时的最近动向是 PHP 4.1.0 释出,在某些方面有较大改进)。
    • 为 PHP 提供商业支持的 Zend 公司 --http://www.zend.com包含 PHP 相关的工具和文字资料(可以寻找到一些与本文主题相关的话题)。
    • 著名的开放源码项目网站 SoureForge--http://www.sourceforge.net开放源码项目的聚集地,并提供基于 Web 的各种便利工具(可以寻找到成千上万 PHP 撰写的项目)。
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux, Web development
ArticleID=21393
ArticleTitle=MVC 模式、类封装还是黑客代码
publish-date=12012001