第16章PHP的模板技术Smarty如果你正在设计一个交互式的网站,你一定会关注两个主要的问题,就是界面美工和应用程序。在大多数的项目组中,开发一个Web程序都会出现这样的流程:计划文档提交之后,界面设计者(美工)制作了网站的外观模型,然后把它交给后台程序员。程序员使用PHP实现程序逻辑,同时使用外观模型做成基本架构,然后工程被返回到HTML页面设计者继续完善。就这样工程可能在后台程序员和页面设计者之间来来回回好几次。由于后台程序员不喜欢干预任何有关HTML标签,同时也不需要美工们和PHP代码鬼混在一起。美工设计者只需要配置文件,动态区块和其他的界面部分,不必要去接触那些错综复杂的PHP代码。因此,这时候有一个很好的模板支持就显得很重要了。16.1什么是模板引擎PHP是一种HTML内嵌式的在服务器端执行的脚本语言,所以大部分PHP开发出来的Web应用,初始的开发模板就是混合层的数据编程。虽然通过MVC设计模式可以把程序应用逻辑与网页呈现逻辑强制性分离,但也只是将应用程序的输入、处理和输出分开,网页呈现逻辑(视图)还会有HTML代码和PHP程序强耦合在一起。PHP脚本的编写者必须既是网页设计者,又是PHP开发者。但实际情况是,多数Web开发人员要么是精通网页设计,能够设计出漂亮的网页外观,但是编写的PHP代码很糟糕;要么仅熟悉PHP编程,能够写出健壮的PHP代码,但是设计的网页外观很难看。具备两种才能的开发人员很少见。现在已经有很多解决方案,可以将网站的页面设计和PHP应用程序几乎完全分离。这些解决方案称为“模板引擎”,它们正在逐步消除由于缺乏层次分离而带来的难题。模板引擎的目的,就是要达到上述提到的逻辑分离的功能。它能让程序开发者专注于资料的控制或是功能的达成;而网页设计师则可专注于网页排版,让网页看起来更具有专业感。因此,模化引擎很适合公司的Web开发团队使用,使每个人都能发挥其专长。模板引擎技术的核心比较简单。只要将美工页面(不包含任何的PHP代码)指定为模板文件,并将这个模板文件中有活动的内容,如数据库输出、用户交互等部分,定义成使用特殊“定界符”包含的“变量”,然后放在模板文件中相应的位置。当用户浏览时,由PHP脚本程序打开该模板文件,并将模板文件中定义的变量进行替换。这样,模板中的特殊变量被替换为不同的动态内容时,就会输出需要的无兄弟,不编程页面,如图16-1所示。图16-1一般的模版引擎的示意图通过图16-1中展示的内容,我们可以打个比方。例如,玩橡皮泥时,用不同的模子按上去,就可以做出需要的形状。如果我们假设PHP中的动态数据就是一块大橡皮泥,模板文件就像是一个模子,玩家就好比是PHP程序,模板引擎比作成使用模子的工具。当玩家创建了一个使用模子的工具,并在工具中将模子安装上,然后用力将橡皮泥按下,这样就做出需要的形状来了。在Web开发中分离应用程序的业务逻辑和表现逻辑,是我们使用模板引擎的主要目的。这是因为有以下两个重要原因:¾美工设计人员可以与应用程序开发人员独立工作,因为应用的表现和逻辑并非密不可分地纠缠在一起。此外,因为大多数模板引擎使用的表现逻辑一般比应用程序所使用编程语言的语法更简单,所以,美工设计人员不需要为完成其工作而在程序语言上花费太多精力。¾可以使用同样的代码基于不同目标生成数据,例如生成打印的数据、生成Web页面或生成电子数据表等。如果不使用模板引擎,则需要针对每种输出目标复制并修改代码,这会带来非常严重的代码冗余,极大地降低了可管理性。目前,可以在PHP中应用的并且比较成熟的模板有很多,例如Smarty、PHPLIB、IPB等几十种。使用这些通过PHP编写的模板引擎,可以让你的代码脉络更加清晰,结构更加合理化。也可以让网站的维护和更新变得更容易,创造一个更加良好的开发环境,让开发和设计工作更容易结合在一起。但是,对于一个PHP程序员来说,没有哪一个PHP模板对他是昀合适、昀完美的。因为PHP模板就是大众化的东西,并不是针对某个人开发的。如果能在对模板的特点、应用有清楚的认识基础上,充分认识到模板的优势劣势,就可以知道是否选择使用模板或选择使用哪个模板。16.2编写自己的模板引擎因为PHP需要继承、创新,做一个自己的PHP模板一步一步地实现,并及时融入昀新的思想和理念,尤其对于公司而言尤为实用。昀重要的是,属于自己的PHP模板引擎永远不是固定不变的,可以根据项目的需要为其量身定制。16.2.1创建自己的模板引擎类在下面的示例中,通过前面介绍的模板引擎概念创建了属于自己的一个简单模板引擎,可以用来完538第16章PHP的模板技术Smarty成处理模板的基本功能。例如,变量替换、分支结构、数组循环遍历,以及模板之间相互嵌套等。在文件MyTpl.php中自定义的模板MyTpl类代码,如下所示:?php/*类名为MyTpl是自定义的模板引擎,在PHP脚本中创建该类对象*//*通过该类对象加载模板文件并解析,将解析后的结果输出*/classMyTpl{/*该类的构造方法,创建对象时初始化成员属性*//*参数template_dir:指定存放模板文件的位置目录*//*参数compile_dir:指定存放编译后的模板文件位置*/function__construct($template_dir='./templates/',$compile_dir='./templates_c/'){$this-template_dir=rtrim($template_dir,'/').'/';//将./templates/目录作为模板存放目录$this-compile_dir=rtrim($compile_dir,'/').'/';//初始化解析后的模板存放目录$this-tpl_vars=array();//为成员属性tpl_vars赋值为空数组}/*调用该方法是用来将值分配给模板中对应的变量*//*参数tpl_val:需要一个字符串参数,要和模板中的变量名对应*//*参数value:需要一个标量类型的值,用来分配给模板中变量的值*/functionassign($tpl_var,$value=null){if($tpl_var!='')//如果第一个参数$tpl_var不是一个空字符串$this-tpl_vars[$tpl_var]=$value;//将第二个参数提供的值添加到数组tpl_var中}/*加载指定目录下的模板文件,并将解析后的内容存放到另一个指定目录下的文件中*//*参数fileName:提供模板文件的文件名*/functiondisplay($fileName){$tplFile=$this-template_dir.$fileName;//到指定的目录中寻找模板文件if(!file_exists($tplFile)){//如果需要处理的模板文件不存在returnfalse;//结果该函数执行返回FALSE}//获取编译过的模板文件,该文件中的内容都是被替换过的$comFileName=$this-compile_dir.com_.basename($tplFile).'.php';//判断替换后的文件是否存在或是存在但有改动,都需要重新创建if(!file_exists($comFileName)||filemtime($comFileName)filemtime($tplFile)){$repContent=$this-tpl_replace(file_get_contents($tplFile));//调用内部替换模板方法$handle=fopen($comFileName,'w+');//打开一个用来保存编译过的文件fwrite($handle,$repContent);//向文件中写入内容fclose($handle);//关闭打开的文件}include($comFileName);//包含处理后的模板文件输出给客户端}/*该方法使用正则表达式将模板文件'{}'中的语句替换为对应的值或PHP代码*//*参数content:提供从模板文件中读入的全部内容字符串*/privatefunctiontpl_replace($content){$pattern=array(//匹配模板中各种标识符的正则表达式的模式数组'/\{\s*\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*\}/i',//匹配模板中变量'/\{\s*if\s*(.+?)\s*\}(.+?)\{\s*\/if\s*\}/ies',//匹配模板中if标识符'/\{\s*else\s*if\s*(.+?)\s*\}/ies',//匹配elseif标识符'/\{\s*else\s*\}/is',//匹配else标识符'/\{\s*loop\s+\$(\S+)\s+\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*\}(.+?)\{\s*\/loop\s*\}/is',//用来匹配模板中的loop标识符,用来遍历数组中的值'/\{\s*loop\s+\$(\S+)\s+\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*=\s*\$(\S+)\s*\}(.+?)\{\s*\/loop\s*\}/is',//用来匹配模板中的loop标识符,用来遍历数组中的键和值'/\{\s*include\s+[\\']?(.+?)[\\']?\s*\}/ie'//匹配include标识符539无兄弟,不编程);$replacement=array(//替换从模板中使用正则表达式匹配到的字符串数组'?phpecho$this-tpl_vars[${1}];?',//替换模板中的变量'$this-stripvtags(\'?phpif(${1}){?\',\'${2}?php}?\')',//替换模板中的if字符串'$this-stripvtags(\'?php}elseif(${1}){?\',)',//替换elseif的字符串'?php}else{?',//替换else的字符串'?phpforeach($this-tpl_vars[${1}]as$this-tpl_vars[${2}]){?${3}?php}?','?phpforeach($this-tpl_vars[${1}]as$this-tpl_vars[${2}]=$this-tpl_vars[${3}]){?${4}?php}?',//这两条用来替换模板中的loop标识符为foreach格式'file_get_contents($this-template_dir.${1})'//替换include的字符串);$repContent=preg_replace($pattern,$replacement,$content);//使用正则替换函数处理if(preg_match('/\{([^(\})]{1,})\}/',$repContent)){//如果还有要替换的标识$repContent=$this-tpl_replace($repContent);//递归调用自己再次替换}return$repContent;//返回替换后的字符串}/*该方法用来将条件语句中使用的变量替换为对应的值*//*参数expr:提供模板中条件语句的开始标记*//*参数statement:提供模板中条件语句的结束标记*/privatefunctionstripvtags($expr,$statement=''){$var_pattern='/\s*\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*/is';//匹配变量的正则$expr=preg_replace($var_pattern,'$this-