<p> 在PHP开发的过程中,我们会接触到很多的模板引擎,包括FastTemplate、SmartTemplate、Smarty、tinybutstrong等,通常都是为了满足MVC开发模式的表现层需要,让显示和逻辑可以更好的分离(当然,现在的模板引擎越做越强大,甚至开始介入M)。有了模板引擎,开发者可以专注于程序代码,设计者可以专注于页面的布局和视觉效果,不必再担心要在模板中嵌入各种程序代码。本篇介绍了ThinkPHP内置的一款模板引擎的设计原理和使用方法,以及和其它主流模板引擎的一些区别。</p><p> 一、ThinkTemplate介绍</p><p> ThinkTemplate是一个使用了XML标签库技术的编译型模板引擎,支持两种类型的模板标签,使用了动态编译和缓存技术,而且支持自定义标签库。ThinkTemplate作为 ThinkPHP框架的一个模板引擎插件提供,也可以独立使用,因为ThinkPHP本身的设计是可以灵活替换模板引擎的。</p><p> ThinkTemplate模板引擎的运作过程如图所示:</p><p> <img src="/content/uploadfile/200805/2008053117444393.jpg" onclick="get_larger(this)" alt="ThinkTemplate模板引擎的设计和使用" /></p><p> ThinkTemplate的使用方法非常简单,直接创建ThinkTemplate对象后进行模板变量赋值然后进行渲染输出就行,然后就是定义模板标签和输出变量,和Smarty的用法比较类似。</p><code>$tpl = new ThinkTemplate(); <br />$tpl -> assign(‘vo’,$vo); <br />$tpl -> display(‘index.htm’);</code></p><p> 如果在ThinkPHP框架中使用的话,无需创建ThinkTemplate对象,Action类会自动创建,只需要赋值并输出就行了。</p><code>$this->assign(‘vo’,$vo); <br />$this->display();</code></p>
<p> </p>
<p> Smarty在渲染模板的时候必须指定模板文件名,在ThinkTemplate中如果不指定模板文件名,会按照系统设置的规则进行自动定位,例如,当前模块的某个操作可以定位成为以模块名称为目录</p><p> 下面的一个操作命名的模板文件。在进行模板变量赋值的时候,可以对任何变量进行赋值,由模板标签来决定输出何种类型的。并且,赋值具有智能化和批量赋值,默认情况下第一个参数是要在模</p><p> 板中输出的变量名称,而第二个参数是变量的值,但是如果没有指定第二个参数,那么会对第一个参数进行判断,如果是索引数组,则自动进行批量赋值。例如:</p><code>$tmpl = array(); <br />$tmpl[‘var1’] = ‘value1’; <br />$tmpl[‘var2’] = ‘value2’; <br />$this->assign($tmpl); <br />$this->display();</code></p><p> 上面代码会自动赋值两个模板变量var1和var2,用来在模板文件中输出。作为安全性考虑,没有赋值的模板变量是不能用于输出的,但是有些特殊的模板标签可以输出系统的常量和系统变量例外,这个我们后面会提到。</p><p> 二、主要特性</p><p> 当然评判模板引擎的优劣并不仅仅在于外表看来的使用方式,更关键的在于内在的功能和效率。ThinkTemplate模板引擎的主要特性包括:</p><p> ?支持XML标签库技术和普通标签定义;</p><p> ?支持混合标签定义;</p><p> ?生成PHP模板缓存文件;</p><p> ?模板文件更新后,自动更新模板缓存;</p><p> ?自动定位当前操作的模板文件,无需指定;</p><p> ?支持编码转换和Content-Type更换;</p><p> ?支持模板变量输出前缀,避免变量名称冲突;</p><p> ?支持特殊模板变量和常量输出;</p><p> ?支持变量组合调节器和格式化功能;</p>
<p> </p>
<p> ?支持标签库扩展模板功能;</p><p> 概括来看,我们可以从执行速度、模板功能和扩展性三个方面来分析。</p><p> 执行速度</p><p> 要准确判断执行速度,首先要了解解释型模板引擎和编译型模板引擎的区别。</p><p> 所谓解释型就是每次将模板内容读入内存,并通过正则等方式分析字符串后对预置的标签进行替换,每次加载模板都需要重复这个过程,tinybutstrong就属于这种类型。而编译型模板引擎是在第一次执行模板文件的时候进行一次编译(相当于一次解释分析过程),然后生成一个编译后的缓存文件,下次执行的时候就可以直接执行缓存文件,无需再次编译。所以,在速度上面,编译型模板引擎在第一次运行模板的时候速度会比解释性模板引擎略微缓慢,是因为有严格的编译(包括生成缓存文件)过程,这个过程根据模板引擎的复杂程度和模板页面标签多少速度有所不同,而当第二次执行的时候,因为最耗时的过程已经跳过了,编译型模板引擎的优势就提现出来了。这也是编译原理中所谓的“空间换时间”例子。目前大多数的模板引擎都采用编译型,但是,是否为编译型和模板功能并没有直接关系,tinybutstrong虽然是解释型但是功能也相当强大。</p><p> ThinkTemplate也一样属于编译型模板引擎,具备动态生成缓存文件的能力,无论是模板文件修改或者是缓存文件被删除,系统都会重新生成缓存文件。你还可以设置模板缓存的有效时间,如每隔10分钟重新编译模板文件。并且在编译模板文件的过程中,如果发现存在很多相同的标签,ThinkTemplate并不会重复解析,而是会读取解析缓存。因此,无论在解析还是执行上面,效率都是比较高的。</p><p> 模板功能</p><p> 模板引擎的一个重要因素就是模板标签的功能。Smarty模板引擎以功能以强大而著称,标签的易用性和完善性也是模板引擎的关键因素之一,最基本的功能包括注释、变量输出、条件控制、包</p>
<p> </p>
<p> 含文件,而这些功能的体现都是借助于一系列的模板标签。这一部分是设计模板引擎的关键也是工作量比较大的地方。目前见到的模板引擎标签大致分为特定标签和XML标签两种类型。</p><p> 特定标签通常是比较常见的类型,由模板引擎自身定义的一系列标签,用来满足变量输出和控制的需要,通常具有私有化规则,标签类型各不相同,在ThinkTemplate里面,我们称之为普通模板标签。例如,Smarty中就是使用 {$varname} 来输出PHP的$varname 变量,TinyButStrong则采用[var.tbl.item1]的方式输出变量,我也曾看到过其它形式的输出标签,例如{:=varname} ,当然有些模板引擎是设计为可以定义起始标签的。ThinkTemplate的普通模板标签以“{”和“}”作为开始和结束标识,和Smarty等大多数模板引擎是一致的,也支持起始标签定义,例如可以在项目配置文件中配置成采用 [$varname] 来输出模板变量。</p><p> ThinkTemplate中的变量输出也具有Smarty的变量组合调节器的功能,例如{$articleTitle|upper|spacify} 。其中调节器方法可以是系统函数或者自定义函数,在ThinkTemplate中还可以设置禁止在模板中使用的函数。也可以支持输出多级对象属性或者数组的输出,例如:{$vo.name.sub} {$array[‘name’][‘sub’]}。</p><p> 除了输出模板变量之外,模板引擎通常都会提供一个特殊的标签输出方式用来输出一些常量和系统变量,在ThinkTemplate采用 $Think 来输出一些无需赋值的特殊或者内建变量,和Smarty的$smarty保留变量类似。</p><p> 和其他的模板引擎不同,ThinkTemplate的特定标签仅仅是用于变量输出功能,而把控制功能和复杂的标签功能放到XML模板标签里面。XML模板引擎在Java领域里面是非常常见的,在PHP模板引擎领域似乎并不多见,但是ThinkTemplate在实现自身的特定标签外,还有效地借鉴了Java的标签库技术,实现了XML模板标签支持,并且允许自定义标签库。两种标签方式的结合使用,可以让模板定义更加灵活,这也正是ThinkTemplate模板引擎的特色。</p>
<p> </p>
<p> 基于某些兼容性考虑,通常在模板文件中可以直接插入php代码,用来完成模板标签所无法完成或者比较困难的功能。这种方式与标签的互补可以满足绝大多数的模板需要。事实上,我在用其他模板引擎的时候,都会经常在模板文件中直接添加一些个别的php代码来辅助。需要注意的是并不是所有的模板引擎都支持在模板中直接写php代码。</p><p> 扩展性</p><p> 任何一个模板引擎的功能都不是为你量身定制的,具有一个良好的可扩展机制也是模板引擎的另外一个考量,Smarty采用的是插件方法来实现扩展,ThinkTemplate由于采用了标签库技术,比Smarty提供了更为强大的定制功能,和Java的TagLibs一样可以支持自定义标签库和标签,每个XML标签都有独立的解析方法,所以可以根据标签库的定义规则来增加和修改标签解析规则。在ThinkTemplate中标签库的体现是采用XML命名空间的方式, </p><p> 例如:<cx:volist id=”list”></cx:volist></p><p> 每个命名空间都有一个对应的标签库XML定义文件,并且还包含有一个用于解析该标签库的类文件。系统默认对cx标签库进行支持,所以在定义cx标签库的标签时候,可以省略XML的命名空间前缀。当系统中存在很多的标签库的时候,每次编译都会加载所有的标签库解析文件,这样会造成一种浪费,因为很多情况,我们可能只是使用其中的一个或者二个标签库。所以,我们还必须在模板页面实现标签库引入功能,来告诉模板引擎当前模板页面需要哪些标签库的支持,从而加载需要的解析类。在ThinkTemplate中,使用tagLib标签来实现这一功能,</p><p> 例如:<tagLib name="html,cx" />,</p><p> 表示导入html和cx两个标签库的支持。如果没有定义,那么默认只是加载cx标签库。</p>
<p> </p>
<p> 利用标签库的特性,我们可以非常方便地扩展自己需要的标签,ThinkTemplate正是采用这种机制来内置集成了一些常用的HTML组件标签,</p><p> 例如:</p><code><html:list id="checkList" name="user" style="list" checkbox="true" action="true" datasource="list" show="id:编号|10%,title:标题:edit,cTime|toDate='Y-m-d h#i#s':评论日期,status|getStatus:状态" actionlist="forbid|resume:禁用|恢复,edit:编辑" /></code></p><p> 使用上面的自定义XML标签定义了一个DataGrid组件,省去了复杂的Html代码,在模板第一次执行的时候,模板引擎会把上面的组件标签解析成PHP和Html结合的代码,生成缓存文件。ThinkTemplate中包含的Html标签库中封装了很多有价值的Html组件。</p><p> 三、标签使用</p><p> 变量输出</p><p> 格式:{$varname|function1|function2=arg1,arg2,### }</p><p> 使用例子:</p><p> {$webTitle|md5| strtoupper | substr=0,3} </p><p> {$number|number_format=2} </p><p> 在模板文件中可以使用{$info['name'] }来输出数组变量。</p><p> 同样,可以使用{$var.key }来输出对象属性变量,并且也支持函数。</p><p> 系统变量</p><p> 依然支持函数使用和大小写、空格,以Think.打头,如</p><p> {$Think.server.script_name} //输出$_SERVER变量 </p><p> {$Think.session.session_id|md5} //输出$_SESSION变量 </p><p> {$Think.get.pageNumber} //输出$_GET变量 </p><p> {$Think.cookie.name} //输出$_COOKIE变量 </p><p> 系统常量</p>
<p> </p>
<p> {$Think.const.__FILE__} </p><p> {$Think.const.MODULE_NAME} </p><p> 或者直接使用</p><p> {$Think.__FILE__} </p><p> {$Think.MODULE_NAME} </p><p> 特殊变量</p><p> 由ThinkPHP系统定义的常量</p><p> {$Think.version} //版本 </p><p> {$Think.now} //现在时间 </p><p> {$Think.template|basename} //模板页面 </p><p> {$Think.LDELIM} //模板标签起始符号 </p><p> {$Think.RDELIM} //模板标签结束符号 </p><p> 模板注释</p><p> 模板支持注释功能,该注释文字在最终页面不会显示,仅供模板制作人员参考和识别。</p><p> 格式:{/* 注释内容 */ } 或 {// 注释内容 }</p><p> 说明:在显示页面的时候不会显示模板注释,仅供模板制作的时候参考。</p><p> 注意:“{”和注释标记之间不能有空格。</p><p> 包含模板</p><p> 当页面需要包含公共文件的时候,可以通过下面的模板标签,</p><p> 格式:{include:Filename }</p><p> 说明:Filename表示公共文件的名称(不包含后缀,因为模板文件后缀为可配置),Filename</p><p> 默认在当前目录下寻找,但是完全支持相对路径访问。</p><p> 例如,下面的格式都是正确的。{include:header } 和 {include:../public/header }。加载公共模板文件后,模板引擎会重新对该页面中的模板标签进行解析,有意思的是你还可以在公共模板中再次包含公共文件,但是一定要注意不能循环包含。</p><p> 例如,在header.html文件中包含了menu文件{include:menu },在index.html文件中则包含了header和footer,{include:header }, 这里是首页的内容{include:footer }</p>
<p> </p>
<p> 在访问index操作方法的时候,模板首先读取index文件,并开始解析include:header,在解析header文件的过程中又遇到include:menu标签,又开始解析,解析完成后再解析include:footer标签,在经过几层的嵌套包含解析后index文件最终被解析成一个缓存模板文件。而且,包含文件可以设置为变量,通过下面方式定义:</p><p> {include:$include } 其中$include为一个模板变量。</p><p> XML标签</p><p> 基本上,XML标签技术包含了普通模板有的功能,并且有了一些方面的增强和补充,更重要的一点是新的标签库技术更加具有扩展性。ThinkTemplate模板引擎内置了两个标签库实现:CX和Html。</p><p> 要在模板页面中使用TagLib标签库功能,需要在开始时候使用taglib 标签导入需要使用的标签,防止以后标签库大量扩展后增加解析工作量,用法如下:</p><p> <tagLib name='cx,html' /></p><p> 引入标签库后,就可以使用标签库定义的标签来定义模板了,例如:</p><code>//可以使用下面的模板标签定义。<br /><cx:vo name='user' property='name' /> <br /><cx:volist id='user' name='userList' > <br /><cx:vo name='user' property='name' /> <br /></cx:volist> <br /><html:select options='name' selected='value' /></code></p><p> 标签库使用的时候忽略大小写,因此下面的方式一样有效:</p><p> <CX:VO NAME='user' property='name' /></p><p> 实际上,ThinkPHP框架模板引擎会默认加载CX标签库,所以下面的方式效果相同:</p>
<p> </p>
<p> <tagLib name='html' /></p><p> 并且,默认加载的CX库可以不使用CX命名空间前缀,也就是说,</p><p> <vo name='user' property='name' /> 等效于 <cx:vo name='user' property='name' /></p><p> 对于多重循环的定义也非常方便:</p><code><volist name=”list” id=”vo”> <br /><sublist name=”vo.sub” id=”sub”> <br />{$sub.name} <br /></sublist> <br /></volist></code></p><p> 混合标签结合使用例子:</p><code><volist id=”user” name=”userList”> <br />{$user.name} <br />{$use.age} <br />{$user.email} <br /></volist></code></p><p> 对于不太复杂的变量输出,建议多采用{$var} 方式,因为单纯从易用性方面而言,这种方式最简洁,而且功能也比较完善。</p><p> 标签库标签定义</p><p> CX标签库</p><p> CX标签库主要用于输出ThinkPHP框架的变量、包含文件和实现一定控制判断。主要有:</p><p> include 包含文件支持的标签属性有 file;</p><p> comment 模板注释 无标签属性;</p><p> iterate 迭代因子输出,循环内可以结合 write 标签,支持的标签属性有 id | name | offset | length | empty;</p><p> write 复杂变量输出,包括数组、对象,有函数支持,支持的标签属性有 name | property | key | format | function;</p><p> volist 数据对象列表输出,循环内可以结合 vo 标签,支持的标签属性有 id | name | offset | length | empty;</p></p><p> vo 数据对象输出,支持的标签属性有 name | property | format | function;</p><p> var 变量输出,用于普通变量,支持的标签属性有 name | format | function;</p><p> equal 判断是否相同,支持的标签属性有 name | property | key | value | function;</p><p> notequal 判断是否不同;</p><p> present 判断是否定义 支持的标签属性有 name | property | key;</p><p> notpresent 判断是否没有定义;</p><p> Html标签库</p><p> Html标签库主要用于实现一些Html标记的动态生成和变量封装,主要有:</p><p> select 动态生成select列表;</p><p> checkbox 动态生成checkbox;</p><p> radio 动态生成radio;</p><p> link 动态加载js或者css文件;</p><p> imageLink 带有链接的图片;</p><p> imageBtn 图片按钮;</p><p> mulitSelect 多选组件;</p><p> list DataGrid组件;</p><p> 四、总结</p><p> 作为一个模板引擎的设计,本文只是做一个概要的原理描述和简单的使用,其中还有很多细节方面,例如优化解析效率、包含机制、安全考虑、特殊变量解析、异常处理等。更多关于ThinkTemplate的内容和标签用法可以参考ThinkPHP的在线手册中的相关部分( http://thinkphp.cn ) 。</p></p>