[PHP]模板引擎Smarty深入浅出介绍
2018-09-07 12:21
Smarty介绍
什么是模版引擎
不知道从什么时候开始,有人开始对HTML内嵌入ServerScript觉得不太满意。然而不论是微软的ASP或是开放源码的PHP,都是属于内嵌ServerScript的网页伺服端语言。因此也就有人想到,如果能把程序应用逻辑(或称商业应用逻辑)与网页呈现(Layout)逻辑分离的话,是不是会比较好呢?
其实这个问题早就存在已久,从交互式网页开始风行时,不论是ASP或是PHP的使用者都是身兼程序开发者与视觉设计师两种身份。可是通常这些使用者不是程序强就是美工强,如果要两者同时兼顾,那可得死掉不少脑细胞...
所以模版引擎就应运而生啦!模版引擎的目的,就是要达到上述提到的逻辑分离的功能。它能让程序开发者专注于资料的控制或是功能的达成;而视觉设计师则可专注于网页排版,让网页看起来更具有专业感!因此模版引擎很适合公司的网站开发团队使用,使每个人都能发挥其专长!
就笔者接触过的模版引擎来说,依资料呈现方式大概分成:需搭配程序处理的模版引擎和完全由模版本身自行决定的模版引擎两种形式。
在需搭配程序处理的模版引擎中,程序开发者必须要负责变量的呈现逻辑,也就是说他必须把变量的内容在输出到模版前先处理好,才能做assign的工作。换句话说,程序开发者还是得多写一些程序来决定变量呈现的风貌。而完全由模版本身自行决定的模版引擎,它允许变量直接assign到模版中,让视觉设计师在设计模版时再决定变量要如何呈现。因此它就可能会有另一套属于自己的模版程序语法(如Smarty),以方便控制变量的呈现。但这样一来,视觉设计师也得学习如何使用模版语言。
模版引擎的运作原理,首先我们先看看以下的运行图:
一般的模版引擎(如PHPLib)都是在建立模版对象时取得要解析的模版,然后把变量套入后,透过parse()这个方法来解析模版,最后再将网页输出。
对Smarty的使用者来说,程序里也不需要做任何parse的动作了,这些Smarty自动会帮我们做。而且已经编译过的网页,如果模版没有变动的话,Smarty就自动跳过编译的动作,直接执行编译过的网页,以节省编译的时间。
使用Smarty的一些概念
在一般模版引擎中,我们常看到区域的观念,所谓区块大概都会长成这样:
<!--START:Blockname-->
区域内容
<!--END:Blockname-->
这些区块大部份都会在PHP程序中以if或for,while来控制它们的显示状态,虽然模版看起来简洁多了,但只要一换了显示方式不同的模版,PHP程序势必要再改一次!
在Smarty中,一切以变量为主,所有的呈现逻辑都让模版自行控制。因为Smarty会有自己的模版语言,所以不管是区块是否要显示还是要重复,都是用Smarty的模版语法(if,foreach,section)搭配变量内容作呈现。这样一来感觉上好象模版变得有点复杂,但好处是只要规划得当,PHP程序一行都不必改。
由上面的说明,我们可以知道使用Smarty要掌握一个原则:将程序应用逻辑与网页呈现逻辑明确地分离。就是说PHP程序里不要有太多的HTML码。程序中只要决定好那些变量要塞到模版里,让模版自己决定该如何呈现这些变量(甚至不出现也行)。
Smarty的基础
安装Smarty
首先,我们先决定程序放置的位置。
Windows下可能会类似这样的位置:「d:\appserv\web\demo\」。
Linux下可能会类似这样的位置:「/home/jaceju/public_html/」。
到Smarty的官方网站下载最新的Smarty套件:。
解开Smarty2.6.0后,会看到很多档案,其中有个libs资料夹。在libs中应该会有3个class.php檔+1个debug.tpl+1个plugin资料夹+1个core资料夹。然后直接将libs复制到您的程序主资料夹下,再更名为class就可以了。就这样?没错!这种安装法比较简单,适合一般没有自己主机的使用者。
至于Smarty官方手册中为什么要介绍一些比较复杂的安装方式呢?基本上依照官方的方式安装,可以只在主机安装一次,然后提供给该主机下所有设计者开发不同程序时直接引用,而不会重复安装太多的Smarty复本。而笔者所提供的方式则是适合要把程序带过来移过去的程序开发者使用,这样不用烦恼主机有没有安装Smarty。
程序的资料夹设定
以笔者在Windows安装Appserv为例,程序的主资料夹是「d:\appserv\web\demo\」。安装好Smarty后,我们在主资料夹下再建立这样的资料夹:
在Linux底下,请记得将templates_c的权限变更为777。Windows下则将其只读取消。
第一个用Smarty写的小程序
我们先设定Smarty的路径,请将以下这个档案命名为main.php,并放置到主资料夹下:
main.php:
<?php
includeclass/Smarty.class.php;
define(@#__SITE_ROOT@#,@#d:/appserv/web/demo@#);//最后没有斜线
$tpl=newSmarty();
$tpl->template_dir=__SITE_ROOT./templates/;
$tpl->compile_dir=__SITE_ROOT./templates_c/;
$tpl->config_dir=__SITE_ROOT./configs/;
$tpl->cache_dir=__SITE_ROOT./cache/;
$tpl->left_delimiter=@#<{@#;
$tpl->right_delimiter=@#}>@#;
?>
照上面方式设定的用意在于,程序如果要移植到其它地方,只要改__SITE_ROOT就可以啦。(这里是参考XOOPS的)
Smarty的模版路径设定好后,程序会依照这个路径来抓所有模版的相对位置(范例中是@#d:/appserv/web/demo/templates/@#)。然后我们用display()这个Smarty方法来显示我们的模版。
接下来我们在templates资料夹下放置一个test.htm:(扩展名叫什么都无所谓,但便于视觉设计师开发,笔者都还是以.htm为主。)
templates/test.htm:
<html>
<head>
<metahttp-equiv=Content-Typecontent=text/html;charset=big5>
<title><{$title}></title>
</head>
<body>
<{$content}>
</body>
</html>
现在我们要将上面的模版显示出来,并将网页标题($title)与内容($content)更换,请将以下档案内容命名为test.php,并放置在主资料夹下:
test.php:
<?php
requiremain.php;
$tpl->assign(title,测试用的网页标题);
$tpl->assign(content,测试用的网页内容);
//上面两行也可以用这行代替
//$tpl->assign(array(title=>测试用的网页标题,content=>测试用的网页内容));
$tpl->display(@#test.htm@#);
?>
请打开浏览器,输入试试看(依您的环境决定网址),应该会看到以下的画面:
再到templates_c底下,我们会看到一个奇怪的资料夹(%%179),再点选下去也是一个奇怪的资料夹(%%1798044067),而其中有一个档案:
templates_c/%%179/%%1798044067/test.htm.php:
<?php/*Smartyversion2.6.0,createdon2003-12-1522:19:45compiledfromtest.htm*/?>
<html>
<head>
<metahttp-equiv=Content-Typecontent=text/html;charset=big5>
<title><?phpecho$this->_tpl_vars[@#title@#];?\></title>
</head>
<body>
<?phpecho$this->_tpl_vars[@#content@#];?\>
</body>
</html>
没错,这就是Smarty编译过的档案。它将我们在模版中的变量转换成了PHP的语法来执行,下次再读取同样的内容时,Smarty就会直接抓取这个档案来执行了。
最后我们整理一下整个Smarty程序撰写步骤:
Step1.加载Smarty模版引擎。
Step2.建立Smarty对象。
Step3.设定Smarty对象的参数。
Step4.在程序中处理变量后,再用Smarty的assign方法将变量置入模版里。
Step5.利用Smarty的display方法将网页秀出。
如何安排你的程序架构
上面我们看到除了Smarty所需要的资料夹外(class、configs、templates、templates_c),还有两个资料夹:includes、modules。其实这是笔者模仿XOOPS的架构所建立出来的,因为XOOPS是笔者所接触到的程序中,少数使用Smarty模版引擎的架站程序。所谓西瓜偎大边,笔者这样的程序架构虽没有XOOPS的百分之一强,但至少给人看时还有XOOPS撑腰。
includes这个资料夹主要是用来放置一些function、sql檔,这样在main.php就可以将它们引入了,如下:
main.php:
<?php
includeclass/Smarty.class.php;
define(@#__SITE_ROOT@#,@#d:/appserv/web/demo@#);//最后没有斜线
//以main.php的位置为基准
require_onceincludes/functions.php;
require_onceincludes/include.php;
$tpl=newSmarty();
$tpl->template_dir=__SITE_ROOT./templates/;
$tpl->compile_dir=__SITE_ROOT./templates_c/;
$tpl->config_dir=__SITE_ROOT./configs/;
$tpl->cache_dir=__SITE_ROOT./cache/;
$tpl->left_delimiter=@#<{@#;
$tpl->right_delimiter=@#}>@#;
?>
modules这个资料夹则是用来放置程序模块的,如此一来便不会把程序丢得到处都是,整体架构一目了然。
上面我们也提到main.php,这是整个程序的主要核心,不论是常数定义、外部程序加载、共享变量建立等,都是在这里开始的。所以之后的模块都只要将这个档案包含进来就可以啦。因此在程序流程规划期间,就必须好好构思main.php中应该要放那些东西;当然利用include或require指令,把每个环节清楚分离是再好不过了。
在上节提到的Smarty程序5步骤,main.php就会帮我们先将前3个步骤做好,后面的模块程序只要做后面两个步骤就可以了。
从变量开始
如何使用变量
从上一章范例中,我们可以清楚地看到我们利用<{及}>这两个标示符号将变量包起来。预设的标示符号为{及},但为了中文冲码及javascript的关系,因此笔者还是模仿XOOPS,将标示符号换掉。变量的命名方式和PHP的变量命名方式是一模一样的,前面也有个$字号(这和一般的模版引擎不同)。标示符号就有点像是PHP中的
(事实上它们的确会被替换成这个),所以以下的模版变量写法都是可行的:
1.<{$var}>
2.<{$var}><!--和变量之间有空格-->
3.<{$var
}><!--启始的标示符号和结束的标示符号不在同一行-->
在Smarty里,变量预设是全域的,也就是说你只要指定一次就好了。指定两次以上的话,变量内容会以最后指定的为主。就算我们在主模版中加载了外部的子模版,子模版中同样的变量一样也会被替代,这样我们就不用再针对子模版再做一次解析的动作。
而在PHP程序中,我们用Smarty的assign来将变量置放到模版中。assign的用法官方手册中已经写得很多了,用法就如同上一节的范例所示。不过在重复区块时,我们就必须将变量做一些手脚后,才能将变量assign到模版中,这在下一章再提。
修饰你的变量
上面我们提到Smarty变量呈现的风貌是由模版自行决定的,所以Smarty提供了许多修饰变量的函式。使用的方法如下:
<{变量修饰函式}><!--当修饰函式没有参数时-->
<{变量修饰函式:参数(非必要,视函式而定)}><!--当修饰函式有参数时-->
范例如下:
<{$varnl2br}><!--将变量中的换行字符换成<br/>-->
<{$varstring_format:%02d}><!--将变量格式化-->
好,那为什么要让模版自行决定变量呈现的风貌?先看看底下的HTML,这是某个购物车结帐的部份画面。
<inputname=totaltype=hiddenvalue=21000/>
总金额:21,000元
一般模版引擎的模版可能会这样写:
<inputname=totaltype=hiddenvalue={total}/>
总金额:{format_total}元
它们的PHP程序中要这样写:
<?php
$total=21000;
$tpl->assign(total,$total);
$tpl->assign(format_total,number_format($total));
?>
而Smarty的模版就可以这样写:(number_format修饰函式请到Smarty官方网页下载)
<inputname=totaltype=hiddenvalue=<{$total}>/>
总金额:<{$totalnumber_format:}>元
Smarty的PHP程序中只要这样写:
<?php
$total=21000;
$tpl->assign(total,$total);
?>
所以在Smarty中我们只要指定一次变量,剩下的交给模版自行决定即可。这样了解了吗?这就是让模版自行决定变量呈现风貌的好处!
控制模版的内容
重复的区块
在Smarty样板中,我们要重复一个区块有两种方式:foreach及section。而在程序中我们则要assign一个数组,这个数组中可以包含数组数组。就像下面这个例子:
首先我们来看PHP程序是如何写的:
test2.php:
<?php
requiremain.php;
$array1=array(1=>苹果,2=>菠萝,3=>香蕉,4=>芭乐);
$tpl->assign(array1,$array1);
$array2=array(
array(index1=>data1-1,index2=>data1-2,index3=>data1-3),
array(index1=>data2-1,index2=>data2-2,index3=>data2-3),
array(index1=>data3-1,index2=>data3-2,index3=>data3-3),
array(index1=>data4-1,index2=>data4-2,index3=>data4-3),
array(index1=>data5-1,index2=>data5-2,index3=>data5-3));
$tpl->assign(array2,$array2);
$tpl->display(test2.htm);
?>
而模版的写法如下:
templates/test2.htm:
<html>
<head>
<metahttp-equiv=Content-Typecontent=text/html;charset=big5>
<title>测试重复区块</title>
</head>
<body>
<pre>
利用foreach来呈现array1
<{foreachitem=item1from=$array1}>
<{$item1}>
<{/foreach}>
利用section来呈现array1
<{sectionname=sec1loop=$array1}>
<{$array1[sec1]}>
<{/section}>
利用foreach来呈现array2
<{foreachitem=index2from=$array2}>
<{foreachkey=key2item=item2from=$index2}>
<{$key2}>:<{$item2}>
<{/foreach}>
<{/foreach}>
利用section来呈现array1
<{sectionname=sec2loop=$array2}>
ex1}>
index2:<{$array2[sec2].index2}>
index3:<{$array2[sec2].index3}>
<{/section}>
</pre>
</body>
</html>
执行上例后,我们发现不管是foreach或section两个执行结果是一样的。那么两者到底有何不同呢?
第一个差别很明显,就是foreach要以巢状处理的方式来呈现我们所assign的两层数组变量,而section则以「主数组[循环名称].子数组索引」即可将整个数组呈现出来。由此可知,Smarty在模版中的foreach和PHP中的foreach是一样的;而section则是Smarty为了处理如上列的数组变量所发展出来的叙述。当然section的功能还不只如此,除了下一节所谈到的巢状资料呈现外,官方手册中也提供了好几个section的应用范例。
不过要注意的是,丢给section的数组索引必须是从0开始的正整数,即0,1,2,3,...。如果您的数组索引不是从0开始的正整数,那么就得改用foreach来呈现您的资料。您可以参考官方讨论区中的此篇讨论,其中探讨了section和foreach的用法。
巢状资料的呈现
模版引擎里最令人伤脑筋的大概就是巢状资料的呈现吧,许多著名的模版引擎都会特意强调这点,不过这对Smarty来说却是小儿科。
最常见到的巢状资料,就算论譠程序中的讨论主题区吧。假设要呈现的结果如下:
公告区
站务公告
文学专区
好书介绍
奇文共赏
计算机专区
硬件外围
软件讨论
程序中我们先以静态资料为例:
test3.php:
<?php
requiremain.php;
$forum=array(
array(category_id=>1,category_name=>公告区,
topic=>array(
array(topic_id=>1,topic_name=>站务公告)
)
),
array(category_id=>2,category_name=>文学专区,
topic=>array(
array(topic_id=>2,topic_name=>好书介绍),
array(topic_id=>3,topic_name=>奇文共赏)
)
),
array(category_id=>3,category_name=>计算机专区,
topic=>array(
array(topic_id=>4,topic_name=>硬件外围),
array(topic_id=>5,topic_name=>软件讨论)
)
)
);
$tpl->assign(forum,$forum);
$tpl->display(test3.htm);
?>
模版的写法如下:
templates/test3.htm:
<html>
<head>
<title>巢状循环测试</title>
</head>
<body>
<tablewidth=200border=0align=centercellpadding=3cellspacing=0>
<{sectionname=sec1loop=$forum}>
<tr>
<tdcolspan=2><{$forum[sec1].category_name}></td>
</tr>
c}>
<tr>
<tdwidth=25></td>
<tdwidth=164><{$forum[sec1].topic[sec2].topic_name}></td>
</tr>
<{/section}>
<{/section}>
</table>
</body>
</html>
执行的结果就像笔者举的例子一样。
因此呢,在程序中我们只要想办法把所要重复值一层一层的塞到数组中,再利用<{第一层数组[循环1].第二层数组[循环2].第三层数组[循环3].....数组索引}>这样的方式来显示每一个巢状循环中的值。至于用什么方法呢?下一节使用数据库时我们再提。
转换数据库中的资料
上面提到如何显示巢状循环,而实际上应用时我们的资料可能是从数据库中抓取出来的,所以我们就得想办法把数据库的资料变成上述的多重数组的形式。这里笔者用一个DB类别来抓取数据库中的资料,您可以自行用您喜欢的方法。
我们只修改PHP程序,模版还是上面那个(这就是模版引擎的好处~),其中$db这个对象假设已经在main.php中建立好了,而且抓出来的资料就是上面的例子。
test3.php:
<?php
requiremain.php;
//先建立第一层数组
$category=array();
$db->setSQL($SQL1,@#CATEGORY@#);
if(!$db->query(@#CATEGORY@#))die($db->error());
//抓取第一层循环的资料
while($item_category=$db->fetchAssoc(@#CATEGORY@#))
{
//建立第二层数组
$topic=array();
$db->setSQL(sprintf($SQL2,$item_category[@#category_id@#]),@#TOPIC@#);
if(!$db->query(@#TOPIC@#))die($db->error());
//抓取第二层循环的资料
while($item_topic=$db->fetchAssoc(@#TOPIC@#))
{
//把抓取的数据推入第二层数组中
array_push($topic,$item_topic);
}
//把第二层数组指定为第一层数组所抓取的数据中的一个成员
$item_category[@#topic@#]=$topic;
//把第一层数据推入第一层数组中
array_push($category,$item_category);
}
$tpl->assign(forum,$category);
$tpl->display(test3.htm);
?>
在数据库抓取一笔资料后,我们得到的是一个包含该笔数据的数组。透过while叙述及array_push函式,我们将数据库中的资料一笔一笔塞到数组里。如果您只用到单层循环,就把第二层循环(红色的部份)去掉即可。
决定内容是否显示
要决定是否显示内容,我们可以使用if这个语法来做选择。例如如果使用者已经登入的话,我们的模版就可以这样写:
<{if$is_login==true}>
显示使用者操作选单
<{else}>
显示输入帐号和密码的窗体
<{/if}>
要注意的是,「==」号两边一定要各留至少一个空格符,否则Smarty会无法解析。
if语法一般的应用可以参照官方使用说明,所以笔者在这里就不详加介绍了。不过笔者发现了一个有趣的应用:常常会看到程序里要产生这样的一个表格:(数字代表的是资料集的顺序)
12
34
56
78
这个笔者称之为「横向重复表格」。它的特色和传统的纵向重复不同,前几节我们看到的重复表格都是从上而下,一列只有一笔资料。而横向重复表格则可以横向地在一列中产生n笔资料后,再换下一列,直到整个循环结束。要达到这样的功能,最简单的方式只需要section和if搭配即可。
我们来看看下面这个例子:
test4.php:
<?php
requiremain.php;
$my_array=array(
array(value=>0),
array(value=>1),
array(value=>2),
array(value=>3),
array(value=>4),
array(value=>5),
array(value=>6),
array(value=>7),
array(value=>8),
array(value=>9));
$tpl->assign(my_array,$my_array);
$tpl->display(@#test4.htm@#);
?>
模版的写法如下:
templates/test4.htm:
<html>
<head>
<title>横向重复表格测试</title>
</head>
<body>
<tablewidth=500border=1cellspacing=0cellpadding=3>
<tr>
<{sectionname=sec1loop=$my_array}>
<td><{$my_array[sec1].value}></td>
<{if$smarty.section.sec1.rownumisdivby2}>
</tr>
<tr>
<{/if}>
<{/section}>
</tr>
</table>
</body>
</html>
重点在于$smarty.section.sec1.rownum这个Smarty变量,在section循环中这个变量会取得从1开始的索引值,所以当rownum能被2除尽时,就输出</tr><tr>使表格换列(注意!是</tr>在前面<tr>在后面)。因此数字2就是我们在一列中想要呈现的资料笔数。各位可以由此去变化其它不同的呈现方式。
加载外部内容
我们可以在模版内加载PHP程序代码或是另一个子模版,分别是使用include_php及include这两个Smarty模版语法;include_php笔者较少用,使用方式可以查询官方手册,这里不再叙述。
在使用include时,我们可以预先加载子模版,或是动态加载子模版。预先加载通常使用在有共同的文件标头及版权宣告;而动态加载则可以用在统一的框架页,而进一步达到如Winamp般可换Skin。当然这两种我们也可以混用,视状况而定。
我们来看看下面这个例子:
test5.php:
<?php
requiremain.php;
$tpl->assign(title,Include测试);
$tpl->assign(content,这是模版2中的变量);
$tpl->assign(dyn_page,test5_3.htm);
$tpl->display(@#test5_1.htm@#);
?>
模版1的写法如下:
templates/test5_1.htm:
<html>
<head>
<metahttp-equiv=Content-Typecontent=text/html;charset=big5>
<title><{$title}></title>
</head>
<body>
<{includefile=test5_2.htm}><br/>
<{includefile=$dyn_page}>
<{includefile=test5_4.htmcustom_var=自订变量的内容}>
</body>
</html>
模版2的写法如下:
templates/test5_2.htm:
<{$content}>
模版3的写法如下:
templates/test5_3.htm:
这是模版3的内容
模版4的写法如下:
templates/test5_4.htm:
<{$custom_var}>
这里注意几个重点:1.模版的位置都是以先前定义的template_dir为基准;2.所有include进来的子模版中,其变量也会被解译。;3.include中可以用「变量名称=变量内容」来指定引含进来的模版中所包含的变量,如同上面模版4的做法。
用PHP实现MVC开发模式的逻辑层和表示层有多种模板引擎可供选择,但是官方引擎SMARTY诞生后,选择就有了变化。它的理念和实现都是相当前卫的。本文主要讨论SMARTY之于其他模板引擎的不同特点,简要介绍了该引擎的安装及使用,并用一个小的测试案例对比了SMARTY和PHPLIBtemplate的速度和易用性。
一、MVC需要模板
MVC最早是在SmallTalk语言的开发过程中总结出的一种设计模式,MVC分别代表了模型、视图和控制,目的就是让不同的开发角色在大中型项目中各司其职。在网络应用程序的开发中,可以用下图来表示各概念之间的关系。
该图展示了一个简单的WEB应用程序,用户在浏览器上看到信息是数据库服务器上的内容,但在这之前经过了应用服务器加工。开发人员负责的就是建立数据结构、处理数据的逻辑以及表示数据的方法。
96年CGI在中国开始流行的时候,早期的WEB程序员都是从HTML开始自学成材的,在PERL中print一行行的HTML并不是一件难事,但是随着网络的一步步提速,页面大小也从当初的二、三十K暴涨了十倍。写CGI程序就产生了一个迫切的要求:分开PERL和HTML源码。于是,社会进步体现在开发小组内部的分工上。由于美工和程序员对互相的工作并不是十分熟悉,在进行合作的过程中需要用一种约定的语言进行交流。
这种语言并不是我们的母语或者英语,术语叫做模板,逻辑和表示依靠它联系。它是结合了HTML和脚本语言特征的一种表达方式。通过这种方式,表示层可以按照用户所希望的格式来显示经过逻辑层处理过的数据。如果你有Windows平台下MFC的开发经验,那么一定会很熟悉Document/DocumentTemplate/View的封装,这就是一个很典型的MVC例子。对于Web应用来说,个人认为J2EE中的EJB/servlets/JSP是最强大的,当然还有简洁优美的Structs。另一个很有名的实现就是COM/DCOM+ASP,这个组合在我国是最多人使用的。
通过几种MVC实现在WEB应用程序里的对比,可以得到一个关于模板的概念:一组插入了HTML的脚本或者说是插入了脚本HTML,通过这种插入的内容来表示变化的数据。下面给出一个模板文件的例子,这个模板经过处理后在浏览器里显示Hello,world!
<html>
<head>
<title>$greetings</title>
</head>
<body>
$greetings
<body>
</html>
这里暂且省略处理方式,在后面做专门对比讨论。
二、为什么选SMARTY?
对PHP来说,有很多模板引擎可供选择,比如最早的PHPLIBtemplate和后起之秀Fasttemplate,经过数次升级,已经相当成熟稳定。如果你对目前手中的模板引擎很满意,那么......也请往下看,相信你作为一个自由软件爱好者或者追求效率和优雅的开发者,下面的SMARTY介绍多少会有点意思。
除了个人偏好的影响,我一直倾向于使用官方标准的实现,比如APACHE的XML引擎Axis。好处就是可以获得尽可能好的兼容性(比如早期MFC对于Win3x的兼容性就比其它的应用程序框架好,当然现在各种版本都很完善了)。SMARTY发布之前我一直使用的是PEAR中的IntegratedTemplateeXtension。这个引擎和PHPLIBtemplate、Fasttemplate几乎是兼容的,从模板的语法到对模板的处理同出一辙:都是将模板读入内存然后调用parse()函数,用数据对预置的标记进行替换。
下面看看SMARTY是怎么做的。接到request后,先判断是否第一次请求该url,如果是,将该url所需的模板文件编译成php脚本,然后redirect;如果不是,就是说该url的模板已经被编译过了,检查不需要重编译后可以马上redirect,重编译条件可以自己设定为固定时限,默认的是模板文件被修改。
怎么样,看起来是不是有点眼熟?想起来了──这不就是JSP的原理嘛!的确,这种编译用在PHP这样的解释性脚本引擎上显得匪夷所思,但是仔细想想,JAVA不也是由JVM解释执行的吗?这就叫没有做不到,只有想不到。
既然谈到了JAVA,就再对PHP的未来发表一点看法。PHP官方网站上宣布了要在2003年年底发布PHP5.0版。这个版本拥有很多崭新的特性:比如异常处理,命名空间,更加面向对象等等。可以说越来越向JAVA靠拢,SMARTY也是新特性之一,使得PHP更适用于大中型项目的开发。但是似乎离我当初选择它的原因──灵巧易用──越来越远了。但就一个软件的生存周期来看,PHP正处在成长期,开发者赋予它更多的功能,以期能胜任商业应用是利大于弊的。作为PHP的忠实用户,肯定不希望PHP总是被人指责能力不足吧?
为什么选择SMARTY,仅仅因为它很像JSP?当然有更为充分的理由。首先,除了第一次编译的成本比较高之外,只要不修改模板文件,编译好的cache脚本就随时可用,省去了大量的parse()时间;其次SMARTY像PHP一样有丰富的函数库,从统计字数到自动缩进、文字环绕以及正则表达式都可以直接使用;如果觉得不够,比如需要数据结果集分页显示的功能,SMARTY还有很强的扩展能力,可以通过插件的形式进行扩充。
事实胜于雄辩。我设计了一个测试程序,通过速度和开发难度这两个因素对比了一下SMARTY和PHPLIBtemplate,选PHPLIBtemplate的原因是在patrick的文章《在PHP世界中选择最合适的模板》中有一个PHPLIBtemplate对Fasttemplate的竞赛,结果PHPLIBtemplate大获全胜,这使得SMARTY有了一个很好的对手。在测试之前,先谈一下在安装过程中需要注意的问题。
三、可能遇到的问题
在SMARTY的官方网站上,有详尽的用户手册,可以选择在线HTML和PDF格式的版本。这里就不再涉及手册上已有的内容,只是把初次使用可能遇到的问题做个解释。
第一个问题就很要命:提示说找不到所需文件?并不是每一个人都按照SMARTY默认目录结构来写应用的。这里需要手工指定,假设目录结构如下:
就需要在index.php里指定目录结构:
$smart->template_dir=smarty/templates/;
$smart->compile_dir=smarty/templates_c/;
$smart->config_dir=smarty/configs/;
$smart->cache_dir=smarty/cache/;
第一个问题解决了,紧接着就是第二个:我刚用Dreamweaver生成的漂亮模板怎么不能用?并不是模板文件有什么问题,而是因为SMARTY默认的标记分隔符是{},不巧的是Javascript肯定包含这个标记。好在我们可以用任意字符当作分隔符,再加上这两句:
$smart->left_delimiter={/;
$smart->right_delimiter=/};
这下安装就基本完成,没问题了。
四、反衬和类比
先构思一下对测试的设计。主要的评比因素当然是速度了。为了进行速度测试,采取了算术平均数的作法。在测试页面中重复将页面生成N遍,再对比总页面生成时间。另一个重要因素是易用性(至于扩展性不用比较已经有结果了),所以使用的模板不能太小。我用的是我个人主页的的页面,一个用Firework+Dreamweaver生成的HTML文件,大小约7K。其中的变量设置也采取最常用的区块,在PHPLIBtemplate里叫block,而SMARTY则称section。别小看这称呼的不同,易用性标准分两块:模板文件和脚本文件的语法是否简明易用。
下面就深入到测试中来。先看看两种模板文件的语法:蓝条左边是PHPLIBtemplate的模板,右边属于SMARTY。个人偏好是不一样的,所以这里不作评论。着重对比一下脚本里的处理语句,先看PHPLIBtemplate的:
$tpl->set_file(phplib,bigfile.htm);
$tpl->set_block(phplib,row,rows);
for($j=0;$j<10;$j++){
$tpl->set_var(tag,$j);
$tpl->parse(rows,row,true);
}
$tpl->parse(out,phplib);
$tpl->p(out);
下面是SMARTY的:
$smart->assign(row,$row);
$smart->display(bigfile.htm);
SMARTY只用了tags和row两个变量,而PHPLIBtemplate则多了模板文件的handler,还有一个莫名其妙的out。说实在的这个out我当初学的时候就不知道为什么要存在,现在看起来,还是别扭。为什么SMARTY少那么多处理语句呢?答案是工作由引擎完成了。如果你喜欢钻研源程序,可以发现在Smarty_compiler.class.php里有一个名叫_compile_tag()的函数,由它负责把section这个标签转换成php语句。这不是一个普通的标签,它带有参数和数据,节省了脚本编程的工作量,而模板标签上的工作量相差又不大,可以判定在易用性上SMARTY高出一畴。
下面该轮到我们最关注的速度了,毕竟对于一个熟练的web开发者来说,掌握再困难的工具不过是时间问题,何况模板引擎这种学习曲线平缓的技术。而速度则是web应用程序的生命,尤其是模板引擎使用在并发访问量很大的站点上,这点就更重要了。测试开始前,我觉得PHPLIBtemplate会在这一环节上胜出,因为它经历了很多次升级,已经基本没有什么bug,而且SMARTY的引擎个头太大,不像它的对手只有两个文件。
果然,测试结果如下图,PHPLIBtemplate有25%的速度优势:
但不会一直这样,我又按了一次刷新,这次得到了不一样的结果:
PHPLIB基本没变化,但是SMARTY提高了25%的速度。继续刷新,得到的都是类似于第二次的结果:SMARTY比PHPLIBtemplate快上近10%。我想这就是编译型比解释型快的原理了。SMARTY引擎本身就很大,加上还要把模板编译成php文件,速度当然比不上小巧的PHPLIBtemplate。但这只是第一次的情况。第二次接到请求的时候,SMARTY发现该模板已经被编译过了,于是最耗时的一步被跳过了,而对手还要按部就班地进行查找和替换工作。这是编译原理里讲到的很经典的用空间换时间例子。
五、结论
结论就是如果你已经爱上SMARTY了,那么还等什么呢?当然并不是说它就全能,就如同我用MVC模式来写我的个人网站,非但没有减少工作量,反而总是要为不同层次间的耦合劳神。
SMARTY不适合什么呢?举个手册里的经典例子:天气预报网站。我还想到一个:股市大盘。在这种网站上用SMARTY会由于经常的重编译而效率偏低,还是PHPLIBtemplate更为适合。
本文并不是为了对比两种引擎,而是为了说明SMARTY的优势。使用它最有意义之处在于它是PHP新体系的一部份,作为一支独立的力量,除了JAVAONE这两大体系之外,大中型web开发还有别的选择。这对于GNU项目来说,其意义无异于刘邓大军千里跃进大别山。