长沙北大青鸟作者:科泰校区王晨
编码对于合格的PHP程序员来说并不是什么难事(也许只是花费时间长短的问题),因此系统分析和设计这一阶段就显得尤为重要。不过本文并不打算讨论和需求分析、获取商业逻辑相关的话题,而是针对系统设计方面进行探讨。
而采用类封装的方法,可能就是这样:
也许您会觉得代码并没有什么区别(甚至看起来采用函数模块的代码由于不需要取得新的对象而显得更简洁一些),而真正的不同是发生在include的文件里面。采用函数模块的方法将相关的函数集合在一个文件中加以组织(有些系统还不能做到这一点,那么就会造成异常混乱的局面),而采用类封装的方法在每一个文件中声明一个和文件名相同的类(比如在Member.inc.php声明一个Member的类,这一点和Java的规定相似);而在使用时,都需要先进行include(如果采用函数模块又没有进行很好的组织,也许有些人就会很"简便"的将所有函数include进每一个页面--PHP可不是Java那样编译执行,光是解析这些函数就会花费一段时间),但是关键就在于采用类封装的方法可以清楚的指明调用的位置--某个类(Member)的某个方法(login):从避免名字冲突的角度来说这一点是非常成功的;而对于代码检查和维护而言,方便程度更是不言而喻。设想一个页面需要完成若干功能,因而需要include数个文件:采用函数模块的方法不能够轻易的从函数调用中找到函数本身所在的文件(如果函数名称或者include文件名称没有什么统一规则,那么这个工作就非常艰巨了),而采用类封装的办法可以根据类名称和类文件名称准确定位类方法代码的位置。(也许您会认为这样一个小小的好处不足挂齿,但是经历一个维护工程之后也许就不会再有什么异议。)
以上是采用类封装方法的原因,决定采用这种方法设计系统只是第一步;完成整个系统的设计还有很多可以借鉴的经验。
在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})。这一步骤完成之后就可以交付网页设计人员进行制作了。
需要指出的是,在编码阶段很可能局部的一些系统设计需要进行修改,这其中也许就包括对网页模板的修改,需要仔细处理。
对于代码组织的补充说明
还有几个文件和目录没有在上文提及:
首先需要关注的是参与项目的人选(虽然也许这是项目经理的职责,但是最熟悉PHP项目特点的系统分析员应该参与)。在PHP开发人员方面,至少应该选择对PHP各种函数较为熟悉的开发者(这类项目不适合作为现实项目以培训参与的开发新人),如果公司中还有能够在源码级别理解PHP的人员就更加理想(不过通常对于一般的PHP开发公司是不可能的)。而在网页设计人员方面,最好可以选择一些略通客户端(比如JavaScript)以及服务器端(最好是PHP)脚本的人员;因为这类项目的一大特点即是单个网页代码量较大且夹杂网页代码(通常是HTML)、客户端脚本(比如JavaScript)和服务器端脚本(比如PHP),加入了解各种脚本语言的网页设计人员的目的不是为了增加团队的PHP开发力量,而是避免在修改网页时影响程序设计人员的工作。
其次就是面向过程,准确说是面向页面的系统设计。相对第一类项目,客户的需求在该类项目中表现得非常清晰,而且一般长期进行Web开发的公司对于这类网站项目也应该有一定的设计经验积累。设计中需要围绕整个系统的流程,包括每个页面的输入参数和输出内容(包括网页中出现的除导航链接之外的功能性链接),以求完全满足客户的需求;另一关键在于确定系统安全策略,在这类项目中主要是用户等级的确定和页面的访问权限,并给出实现的方式。不过还需要指出的是,这类项目中由编码阶段返回设计阶段的情况并不少见,对于局部设计(比如页面传入参数或者输出链接)的更改应该加以及时控制。
最后是针对代码和数据库的优化。在这类项目中需要适当鼓励开发人员的黑客态度。推荐的办法是系统分析员给出每个页面的伪代码(框架代码),而局部的实现则由各个程序开发人员和网页设计人员进行。
对于PHP代码方面,通常可以从如下几方面考虑:
而根据我的经验,通常会在这类项目中撰写的黑客代码如下:
黑客代码在这类项目中值得鼓励,不过最好在每段代码旁附上尽可能详细的注释。
由于该类项目的特殊性,完成项目的关键不仅仅在于系统设计阶段,因此给出项目开始、系统设计、编码以及测试、交付这一过程的简单描述:
方案三:综合性网站项目
已经有一些大型网站使用PHP作为主要的开发语言。对于这类项目,单纯从PHP技术方面值得提出的话题不多,简而言之还是根据网站各部分的实际应用情况(访问强度、操作行为等)选择以上提出的两种项目设计方法或者综合使用。除此之外,根据我个人的经验,项目团队的组织和协调工作以及项目各期完成后的维护工作等等是较之单纯的技术更加关键的因素。
对于这类项目,可以提出的建议是,适当采纳一些开源软件对于快速、优质的完成项目很有好处。项目的某些部分可以直接引入开源软件项目的设计甚至是代码,不过前提是系统设计人员对这些引入的项目需要非常了解,同时需要做好这些孤立的开源项目和整个项目之间的接合(比如安全策略的考虑和全局变量的引用等)。
举例来说,根据客户要求某个综合网站需要以下的功能:
(很明显这是一个企业网站的雏形。)
其中的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--可惜暂时受到时间、精力以及个人能力的限制--也许春节假期是一个好时机:)