刚开始接触模版引擎的 PHP 设计师听到 Smarty 时都会觉得很难其实笔者也不例外碰都不敢碰一下但是后来在剖析 XOOPS 的程序架构时开始发现 Smarty 其实并不难只要将 Smarty 基础功练好在一般应用上就已经相当足够了当然基础能打好后面的进阶应用也就不用怕了
这篇文章的主要用意并非要深入探讨 Smarty 的使用这在官方使用说明中都已经写得很完整了笔者仅在此写下一些自己使用上的心得让想要了解 Smarty 却不得其门而入的朋友可以从中得到一些启示就因为这篇文章的内容不是非常深入会使用 Smarty 的朋友们可能会觉得简单了点
目前本文已经第三次修订了本想多加一些料进来不过碍于时间的关系很多 Smarty 的进阶技巧笔者并没有研究得很透彻所以也不敢拿出来现眼但笔者相信这篇文章应该能够满足大多数想学习 Smarty 的初学者了当然本文有谬误的地方也欢迎告知笔者会在下一次的修订中更正的
Smarty介绍
什么是模版引擎
不知道从什么时候开始有人开始对 HTML 内嵌入 Server Script 觉得不太满意然而不论是微软的 ASP 或是开放源码的 PHP都是属于内嵌 Server Script 的网页伺服端语言因此也就有人想到如果能把程序应用逻辑 (或称商业应用逻辑) 与网页呈现 (Layout) 逻辑分离的话是不是会比较好呢?
其实这个问题早就存在已久从交互式网页开始风行时不论是 ASP 或是 PHP 的使用者都是身兼程序开发者与视觉设计师两种身份可是通常这些使用者不是程序强就是美工强如果要两者同时兼顾那可得死掉不少脑细胞
所以模版引擎就应运而生啦!模版引擎的目的就是要达到上述提到的逻辑分离的功能它能让程序开发者专注于资料的控制或是功能的达成而视觉设计师则可专注于网页排版让网页看起来更具有专业感!因此模版引擎很适合公司的网站开发团队使用使每个人都能发挥其专长!
就笔者接触过的模版引擎来说依资料呈现方式大概分成需搭配程序处理的模版引擎和完全由模版本身自行决定的模版引擎两种形式
在需搭配程序处理的模版引擎中程序开发者必须要负责变量的呈现逻辑也就是说他必须把变量的内容在输出到模版前先处理好才能做 assign 的工作换句话说程序开发者还是得多写一些程序来决定变量呈现的风貌而完全由模版本身自行决定的模版引擎它允许变量直接 assign 到模版中让视觉设计师在设计模版时再决定变量要如何呈现因此它就可能会有另一套属于自己的模版程序语法 (如 Smarty) 以方便控制变量的呈现但这样一来视觉设计师也得学习如何使用模版语言
模版引擎的运作原理首先我们先看看以下的运行图
一般的模版引擎 (如 PHPLib) 都是在建立模版对象时取得要解析的模版然后把变量套入后透过 parse() 这个方法来解析模版最后再将网页输出
对 Smarty 的使用者来说程序里也不需要做任何 parse 的动作了这些 Smarty 自动会帮我们做而且已经编译过的网页如果模版没有变动的话 Smarty 就自动跳过编译的动作直接执行编译过的网页以节省编译的时间
使用Smarty的一些概念
在一般模版引擎中我们常看到区域的观念所谓区块大概都会长成这样
<! START : Block name >
区域内容
<! END : Block name >
这些区块大部份都会在 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套件
解开 Smarty 后会看到很多档案其中有个 libs 资料夹在 libs 中应该会有 个 classphp 档 + 个 debugtpl + 个 plugin 资料夹 + 个 core 资料夹然后直接将 libs 复制到您的程序主资料夹下再更名为 class 就可以了就这样?没错!这种安装法比较简单适合一般没有自己主机的使用者
至于 Smarty 官方手册中为什么要介绍一些比较复杂的安装方式呢?基本上依照官方的方式安装可以只在主机安装一次然后提供给该主机下所有设计者开发不同程序时直接引用而不会重复安装太多的 Smarty 复本而笔者所提供的方式则是适合要把程序带过来移过去的程序开发者使用这样不用烦恼主机有没有安装 Smarty
程序的资料夹设定
以笔者在Windows安装Appserv为例程序的主资料夹是「d:\appserv\web\demo\」安装好Smarty后我们在主资料夹下再建立这样的资料夹
educitycn/img_///gif >在 Linux 底下请记得将 templates_c 的权限变更为 Windows 下则将其只读取消
第一个用Smarty写的小程序
我们先设定 Smarty 的路径请将以下这个档案命名为 mainphp 并放置到主资料夹下
mainphp:
<?php
include class/Smartyclassphp;
define(__SITE_ROOT d:/appserv/web/demo); // 最后没有斜线
$tpl = new Smarty();
$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 资料夹下放置一个 (扩展名叫什么都无所谓但便于视觉设计师开发笔者都还是以 为主)
templates/:
<html>
<head>
<meta httpequiv=ContentType content=text/html; charset=big>
<title><{$title}></title>
</head>
<body>
<{$content}>
</body>
</html>
现在我们要将上面的模版显示出来并将网页标题 ($title) 与内容 ($content) 更换请将以下档案内容命名为 testphp 并放置在主资料夹下
testphp:
<?php
require mainphp;
$tpl>assign(title 测试用的网页标题);
$tpl>assign(content 测试用的网页内容);
// 上面两行也可以用这行代替
// $tpl>assign(array(title => 测试用的网页标题 content => 测试用的网页内容));
$tpl>display();
?>
请打开浏览器输入 试试看(依您的环境决定网址)应该会看到以下的画面
educitycn/img_///gif >再到 templates_c 底下我们会看到一个奇怪的资料夹 (%%) 再点选下去也是一个奇怪的资料夹 (%%) 而其中有一个档案
templates_c/%%/%%/php:
<?php /* Smarty version created on :: compiled from */ ?>
<html>
<head>
<meta httpequiv=ContentType content=text/html; charset=big>
<title><?php echo $this>_tpl_vars[title]; ?></title>
</head>
<body>
<?php echo $this>_tpl_vars[content]; ?>
</body>
</html>
没错这就是 Smarty 编译过的档案它将我们在模版中的变量转换成了 PHP 的语法来执行下次再读取同样的内容时 Smarty 就会直接抓取这个档案来执行了
最后我们整理一下整个 Smarty 程序撰写步骤
Step 加载 Smarty 模版引擎
Step 建立 Smarty 对象
Step 设定 Smarty 对象的参数
Step 在程序中处理变量后再用 Smarty 的 assign 方法将变量置入模版里
Step 利用 Smarty 的 display 方法将网页秀出
如何安排你的程序架构
上面我们看到除了 Smarty 所需要的资料夹外 (class configs templates templates_c) 还有两个资料夹 includes modules 其实这是笔者模仿 XOOPS 的架构所建立出来的因为 XOOPS 是笔者所接触到的程序中少数使用 Smarty 模版引擎的架站程序所谓西瓜偎大边笔者这样的程序架构虽没有 XOOPS 的百分之一强但至少给人看时还有 XOOPS 撑腰
includes 这个资料夹主要是用来放置一些 function sql 档这样在 mainphp 就可以将它们引入了如下
mainphp:
<?php
include class/Smartyclassphp;
define(__SITE_ROOT d:/appserv/web/demo); // 最后没有斜线
// 以 mainphp 的位置为基准
require_once includes/functionsphp;
require_once includes/includephp;
$tpl = new Smarty();
$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 这个资料夹则是用来放置程序模块的如此一来便不会把程序丢得到处都是整体架构一目了然
上面我们也提到 mainphp 这是整个程序的主要核心不论是常数定义外部程序加载共享变量建立等都是在这里开始的所以之后的模块都只要将这个档案包含进来就可以啦因此在程序流程规划期间就必须好好构思 mainphp 中应该要放那些东西当然利用 include 或 require 指令把每个环节清楚分离是再好不过了
educitycn/img_///gif >在上节提到的 Smarty 程序 步骤 mainphp 就会帮我们先将前 个步骤做好后面的模块程序只要做后面两个步骤就可以了
从变量开始
如何使用变量
从上一章范例中我们可以清楚地看到我们利用 <{ 及 }> 这两个标示符号将变量包起来预设的标示符号为 { 及 } 但为了中文沖码及 Javascript 的关系因此笔者还是模仿 XOOPS 将标示符号换掉变量的命名方式和 PHP 的变量命名方式是一模一样的前面也有个 $ 字号 (这和一般的模版引擎不同)标示符号就有点像是 PHP 中的 <?php 及 ?> (事实上它们的确会被替换成这个) 所以以下的模版变量写法都是可行的
<{$var}>
<{ $var }> <! 和变量之间有空格 >
<{$var
}> <! 启始的标示符号和结束的标示符号不在同一行 >
在 Smarty 里变量预设是全域的也就是说你只要指定一次就好了指定两次以上的话变量内容会以最后指定的为主就算我们在主模版中加载了外部的子模版子模版中同样的变量一样也会被替代这样我们就不用再针对子模版再做一次解析的动作
而在 PHP 程序中我们用 Smarty 的 assign 来将变量置放到模版中 assign 的用法官方手册中已经写得很多了用法就如同上一节的范例所示不过在重复区块时我们就必须将变量做一些手脚后才能将变量 assign 到模版中这在下一章再提
修饰你的变量
上面我们提到 Smarty 变量呈现的风貌是由模版自行决定的所以 Smarty 提供了许多修饰变量的函式使用的方法如下
<{变量|修饰函式}> <! 当修饰函式没有参数时 >
<{变量|修饰函式:参数(非必要视函式而定)}> <! 当修饰函式有参数时 >
范例如下
<{$var|nlbr}> <! 将变量中的换行字符换成 <br /> >
<{$var|string_format:%d}> <! 将变量格式化 >
好那为什么要让模版自行决定变量呈现的风貌?先看看底下的 HTML 这是某个购物车结帐的部份画面
<input name=total type=hidden value= />
总金额 元
一般模版引擎的模版可能会这样写
<input name=total type=hidden value={total} />
总金额{format_total} 元
它们的 PHP 程序中要这样写
<?php
$total = ;
$tpl>assign(total $total);
$tpl>assign(format_total number_format($total));
?>
而 Smarty 的模版就可以这样写 (number_format 修饰函式请到Smarty 官方网页下载)
<input name=total type=hidden value=<{$total}> />
总金额<{$total|number_format:}> 元
Smarty 的 PHP 程序中只要这样写
<?php
$total = ;
$tpl>assign(total $total);
?>
所以在 Smarty 中我们只要指定一次变量剩下的交给模版自行决定即可这样了解了吗?这就是让模版自行决定变量呈现风貌的好处!
控制模版的内容
重复的区块
在 Smarty 样板中我们要重复一个区块有两种方式 foreach 及 section 而在程序中我们则要 assign 一个数组这个数组中可以包含数组数组就像下面这个例子
首先我们来看 PHP 程序是如何写的
testphp:
<?php
require mainphp;
$array = array( => 苹果 => 菠萝 => 香蕉 => 芭乐);
$tpl>assign(array $array);
$array = array(
array(index => data index => data index => data)
array(index => data index => data index => data)
array(index => data index => data index => data)
array(index => data index => data index => data)
array(index => data index => data index => data));
$tpl>assign(array $array);
$tpl>display();
?>
而模版的写法如下
templates/:
<html>
<head>
<meta httpequiv=ContentType content=text/html; charset=big>
<title>测试重复区块</title>
</head>
<body>
<pre>
利用 foreach 来呈现 array
<{foreach item=item from=$array}>
<{$item}>
<{/foreach}>
利用 section 来呈现 array
<{section name=sec loop=$array}>
<{$array[sec]}>
<{/section}>
利用 foreach 来呈现 array
<{foreach item=index from=$array}>
<{foreach key=key item=item from=$index}>
<{$key}>: <{$item}>
<{/foreach}>
<{/foreach}>
利用 section 来呈现 array
<{section name=sec loop=$array}>
index: <{$array[sec]index}>
index: <{$array[sec]index}>
index: <{$array[sec]index}>
<{/section}>
</pre>
</body>
</html>
执行上例后我们发现不管是 foreach 或 section 两个执行结果是一样的那么两者到底有何不同呢?
第一个差别很明显就是foreach 要以巢状处理的方式来呈现我们所 assign 的两层数组变量而 section 则以「主数组[循环名称]子数组索引」即可将整个数组呈现出来由此可知 Smarty 在模版中的 foreach 和 PHP 中的 foreach 是一样的而 section 则是 Smarty 为了处理如上列的数组变量所发展出来的叙述当然 section 的功能还不只如此除了下一节所谈到的巢状资料呈现外官方手册中也提供了好几个 section 的应用范例
不过要注意的是丢给 section 的数组索引必须是从 开始的正整数即 如果您的数组索引不是从 开始的正整数那么就得改用 foreach 来呈现您的资料您可以参考官方讨论区中的此篇讨论其中探讨了 section 和 foreach 的用法
巢状资料的呈现
模版引擎里最令人伤脑筋的大概就是巢状资料的呈现吧许多着名的模版引擎都会特意强调这点不过这对 Smarty 来说却是小儿科
最常见到的巢状资料就算论譠程序中的讨论主题区吧假设要呈现的结果如下
公告区
站务公告
文学专区
好书介绍
奇文共赏
计算机专区
硬件外围
软件讨论
程序中我们先以静态资料为例
testphp:
<?php
require mainphp;
$forum = array(
array(category_id => category_name => 公告区
topic => array(
array(topic_id => topic_name => 站务公告)
)
)
array(category_id => category_name => 文学专区
topic => array(
array(topic_id => topic_name => 好书介绍)
array(topic_id => topic_name => 奇文共赏)
)
)
array(category_id => category_name => 计算机专区
topic => array(
array(topic_id => topic_name => 硬件外围)
array(topic_id => topic_name => 软件讨论)
)
)
);
$tpl>assign(forum $forum);
$tpl>display();
?>
模版的写法如下
templates/:
<html>
<head>
<title>巢状循环测试</title>
</head>
<body>
<table width= border= align=center cellpadding= cellspacing=>
<{section name=sec loop=$forum}>
<tr>
<td colspan=><{$forum[sec]category_name}></td>
</tr>
<{section name=sec loop=$forum[sec]topic}>
<tr>
<td width=></td>
<td width=><{$forum[sec]topic[sec]topic_name}></td>
</tr>
<{/section}>
<{/section}>
</table>
</body>
</html>
执行的结果就像笔者举的例子一样
因此呢在程序中我们只要想办法把所要重复值一层一层的塞到数组中再利用 <{第一层数组[循环]第二层数组[循环]第三层数组[循环] 数组索引}> 这样的方式来显示每一个巢状循环中的值至于用什么方法呢?下一节使用数据库时我们再提
转换数据库中的资料
上面提到如何显示巢状循环而实际上应用时我们的资料可能是从数据库中抓取出来的所以我们就得想办法把数据库的资料变成上述的多重数组的形式这里笔者用一个 DB 类别来抓取数据库中的资料您可以自行用您喜欢的方法
我们只修改 PHP 程序模版还是上面那个 (这就是模版引擎的好处~)其中 $db 这个对象假设已经在 mainphp 中建立好了而且抓出来的资料就是上面的例子
testphp:
<?php
require mainphp;
// 先建立第一层数组
$category = array();
$db>setSQL($SQL CATEGORY);
if (!$db>query(CATEGORY)) die($db>error());
// 抓取第一层循环的资料
while ($item_category = $db>fetchAssoc(CATEGORY))
{
// 建立第二层数组
$topic = array();
$db>setSQL(sprintf($SQL $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();
?>
在数据库抓取一笔资料后我们得到的是一个包含该笔数据的数组透过 while 叙述及 array_push 函式我们将数据库中的资料一笔一笔塞到数组里如果您只用到单层循环就把第二层循环 (红色的部份) 去掉即可
决定内容是否显示
要决定是否显示内容我们可以使用 if 这个语法来做选择例如如果使用者已经登入的话我们的模版就可以这样写
<{if $is_login == true}>
显示使用者操作选单
<{else}>
显示输入帐号和密码的窗体
<{/if}>
要注意的是「==」号两边一定要各留至少一个空格符否则 Smarty 会无法解析
if 语法一般的应用可以参照官方使用说明所以笔者在这里就不详加介绍了不过笔者发现了一个有趣的应用常常会看到程序里要产生这样的一个表格 (数字代表的是资料集的顺序)
这个笔者称之为「横向重复表格」它的特色和传统的纵向重复不同前几节我们看到的重复表格都是从上而下一列只有一笔资料而横向重复表格则可以横向地在一列中产生 n 笔资料后再换下一列直到整个循环结束要达到这样的功能最简单的方式只需要 section 和 if 搭配即可
我们来看看下面这个例子
testphp:
<?php
require mainphp;
$my_array = array(
array(value => )
array(value => )
array(value => )
array(value => )
array(value => )
array(value => )
array(value => )
array(value => )
array(value => )
array(value => ));
$tpl>assign(my_array $my_array);
$tpl>display();
?>
模版的写法如下
templates/:
<html>
<head>
<title>横向重复表格测试</title>
</head>
<body>
<table width= border= cellspacing= cellpadding=>
<tr>
<{section name=sec loop=$my_array}>
<td><{$my_array[sec]value}></td>
<{if $smartysectionsecrownum is div by }>
</tr>
<tr>
<{/if}>
<{/section}>
</tr>
</table>
</body>
</html>
重点在于 $smartysectionsecrownum 这个 Smarty 变量在 section 循环中这个变量会取得从 开始的索引值所以当 rownum 能被 除尽时就输出 </tr><tr> 使表格换列 (注意!是 </tr> 在前面<tr> 在后面) 因此数字 就是我们在一列中想要呈现的资料笔数各位可以由此去变化其它不同的呈现方式
加载外部内容
我们可以在模版内加载 PHP 程序代码或是另一个子模版分别是使用 include_php 及 include 这两个 Smarty 模版语法 include_php 笔者较少用使用方式可以查询官方手册这里不再叙述
在使用 include 时我们可以预先加载子模版或是动态加载子模版预先加载通常使用在有共同的文件标头及版权宣告而动态加载则可以用在统一的框架页而进一步达到如 Winamp 般可换 Skin 当然这两种我们也可以混用视状况而定
我们来看看下面这个例子
testphp:
<?php
require mainphp;
$tpl>assign(title Include 测试);
$tpl>assign(content 这是模版 中的变量);
$tpl>assign(dyn_page );
$tpl>display();
?>
模版 的写法如下
templates/:
<html>
<head>
<meta httpequiv=ContentType content=text/html; charset=big>
<title><{$title}></title>
</head>
<body>
<{include file=}><br />
<{include file=$dyn_page}>
<{include file= custom_var=自订变量的内容}>
</body>
</html>
模版 的写法如下
templates/:
<{$content}>
模版 的写法如下
templates/:
这是模版 的内容
模版 的写法如下
templates/:
<{$custom_var}>
这里注意几个重点 模版的位置都是以先前定义的 template_dir 为基准 所有 include 进来的子模版中其变量也会被解译 include 中可以用「变量名称=变量内容」来指定引含进来的模版中所包含的变量如同上面模版 的做法