用 Smarty 分离 PHP 应用程序中的形式与功能

转自ibm developerWorks 作者:Martin Streicher  ([email protected]),

2007 年 9 月 06 日

随意混用 PHP 与其他 Web 页面标记将导致程序逻辑、HTML、层叠样式表(Cascading Style Sheets,CSS)和 JavaScript 处于混乱状态,使维护成为一项艰巨的任务。Smarty 模板引擎可以将形式与功能分离。

PHP Web 应用程序易于上手。PHP 语言的语法整洁且易于掌握。可以将 PHP 与 HTML、JavaScript 和 CSS 直接混用以快速生成可视结果。而且,把 PHP 应用程序部署到您自己的 Web 服务器或托管服务中只是小菜一碟。

但 是混用 PHP 与其他页面标记也是一项责任。PHP 代码通常是含有程序逻辑、结构化查询语言(Structured Query Language,SQL)查询、函数、类、开发人员注释、HTML、CSS 样式和脚本的复杂 web(不是开玩笑)。更糟糕的是,把内容从 PHP、echo 发送到输出缓冲区有很多种方法。维护这样混乱的页面十分费力。对代码或标记做出无关紧要的更改会带来严重破坏,并且增强页面可能需要设计人员与程序员的共同努力。使用 PHP,形式(页面的布局)及功能(页面的目的和构造)将被混在一起。

在 理想情况下,形式与功能是相互独立的。例如,CSS 和 HTML 一定应该如此。CSS 是形式,而 HTML 是功能。在使用 PHP 的情况下,如果页面标记和代码能够分离将是十分理想的。代码将处理输入,制定决策并生成显示数据,而标记将期待获得数据并提供所需的支架以渲染信息。

例如,主页的标记可能留下一个 “填空” (fill in the blank) 以供用户登录,以及其他占位符以供保存用户的图像和重要信息。此模板 —— 这样命名是因为它将提供页面显示的模式 —— 只面向设计人员,设计人员将控制页面的整体外观并留下名称、图片和其他数据的占位符。代码只是为占位符提供数据。开发人员的任务仍然主要集中在计算上。

当然,形式与功能必须协作。如果模板期望获得以美元为单位的金额,则代码不应当提供 URL。如果模板期望获得对象,则代码不应当提供列表。因此,模板系统必须将表单与函数分离,但还必须在两者之间建立联系。

最流行的 Web 应用程序编程语言(Perl、Python、Ruby、Java™)都有模板引擎,而 PHP 也不例外。在搜索引擎中键入 PHP template engine,然后您可能会找到 25 个以上的选项(有关强调所研究的每个引擎功能的列表 The PHP Template Engine Roundup,请参阅 参考资料)。

一些 PHP 模板引擎进行了速度优化。其他 PHP 模板引擎旨在鼓励分离表单与函数的同时简化使用。在某些包中,占位符是在 PHP 本身中描述的,而其他解决方案都有一种自定义的简短编程语言。如何选择模板引擎在很大程度上取决于要求,因此适宜进行少量研究和试验。

在 这里,我向您介绍 Smarty,它是最流行的 PHP 模板引擎之一。Smarty “代码” 有它自己的语法和运算符扩展列表,但是系统并不难学。阅读或浏览 Smarty 文档,以便熟悉它的所有功能。从 Smarty 的小修改开始,根据需求扩展您的技能,然后越来越精通。

获得 Smarty

Smarty Web 站点维护着一张活动邮件列表、一个支持论坛和一个 Internet Relay Chat (IRC) 论坛(请参阅 参考资料)。开发正在进行,而本文基于 V2.6.18 版本,该版本发布于 2007 年 3 月 7 日。

Smarty 有两个方面:PHP 应用程序编程接口 (API) 和显示引擎。应用程序代码将调用 API 把代码变量与模板占位符关联起来,而显示引擎将解释 Smarty 标记、执行循环、引用占位符和显示最终结果。Smarty 功能包括:

用于显示 PHP 的所有基本数据结构的运算符
显示简单变量,迭代整个数组或关联数组,以及显示类的成员。
占位符的默认值
如果 PHP 代码没有将变量与占位符关联,则显示默认值。
控制运算符,例如 ifthenelse,可以根据输入数据选择动态显示哪些内容
例如,设计人员可以选择用加粗的红色文本显示负账户余额,而用黑色文本显示正余额。您可以在模板中隔离此类显示逻辑(使您可以更轻松地进行开发)。
循环控制,它将提供用于简化构建列表和表的特殊变量
例如,可以测试循环的第一次迭代并创建表头。还可以像循环迭代一样循环执行值轮循 (round-robin) 列表,循环迭代非常适于改变表行的颜色。
渲染时用于改变数据的修饰符
例如,可以用 Smarty 标记 <strong>{$name|upper}</strong> 大写加粗显示占位符 —— 如 $name

<strong> 是普通 HTML。大括号 ( {}) 用于划定 Smarty 标记, $name 是占位符,而 |upper 是修饰符。还可以编写自己的修饰符以扩展 Smarty 的功能。
如果必须 包括脚本和原始 PHP 代码,可以用 literalphp 运算符来完成
literal 运算符内的所有内容都将被逐字传递给最终页面。 php 运算符中放置的代码将像嵌入到 <?php ... ?> 转义符内一样执行。

还可以通过 {include ...} 运算符重用 Smarty 模板。要提高性能,则需缓存每个模板,以避免每次使用的转换负载。Smarty Web 站点提供了丰富文档和示例。Packt Publishing 还提供一本名为 Smarty: PHP Template Programming and Applications(请参阅 参考资料)的书,该书适于学习和参考(警告:一些最新运算符并未介绍,而且其他运算符的说明也不正确,因为该书介绍的是 Smarty V2.x 的早期版本)。





回页首


用 Smarty 进行开发

无法通过一篇文章列举和演示 Smarty 的所有功能。但是,即使是一个如下所示的小示例,也能证明模板的力量。

把 Smarty 添加到应用程序中十分轻松:

  1. 下载 Smarty.zip 演示代码(请参阅 下载)。
  2. 解压缩并把 Smarty 安装到路径中。
  3. 编写要求使用 Smarty 类的应用程序。
  4. 创建两个目录一起放置应用程序:
    • templates 将包含模板
    • templates_c 将包含缓存的模板

例如,示例应用程序的文件夹内容包含 Example.class.php index.php templates/ templates_c/

模 板目录中的文件将由 Smarty 引擎读取。确保 Web 服务器对那些文件拥有适当的访问权。另外,templates_c 的内容必须可读可写,因为缓存的模板副本放置在该文件夹中。至少要使 Web 服务器可以将数据写入 templates_c。或者,如果不需要考虑安全问题,可以将目录更改为模式 777

清单 1 显示了 Example.class.php,这是一个有代表性的 PHP V5 类。清单 2 包含 index.php,即应用程序。图 1 中显示了用浏览器访问示例应用程序的结果。


清单 1. 简单 PHP V5 类,用于存储任意类型的已命名属性的随机列表
                
<?php

//
// Example is a simple class that stores an arbitrary
// number of named properties.
//

class Example {
private $catalog = array();

public function SetProperties( $arrayVariables ) {
foreach ( $arrayVariables as $name => $value ) {
$this->SetProperty( $name, $value );
}
}

public function SetProperty( $name, $value ) {
$this->$name = $value;
$this->catalog[] = $name;
}

public function GetProperties( ) {
$result = array();
foreach ( $catalog as $name ) {
$result[$name] = $this->GetProperty( $name );
}

return( $result );
}

public function GetProperty( $name ) {
return ( $this->$name );
}
}
?>

Example 是一个简单类,用于持久保存任意数目的已命名属性。该类最令人感兴趣的部分是第 18 行 $this->$name = $value;。这行代码将动态创建类实例成员。例如,如果调用 $example->SetProperty( 'name', 'Groucho' ),则可以用(传统的)$example->name 检索名称。


清单 2. PHP 应用程序,完全没有任何 Web 页面标记
                
<?php
require( 'Smarty.class.php' );
require( 'Example.class.php' );

$groucho = new Example();
$groucho->SetProperty( 'name', 'Groucho' );
$groucho->SetProperty( 'quote',
'Time flies like an arrow. Fruit flies like a banana.'
);

$chico = new Example();
$chico->SetProperties( array(
'name' => 'Chico',
'quote' => "There's no such thing as a sanity clause!"
)
);

$movies = new Example();
$movies->SetProperties( array(
'movies' => array(
'A Night at the Opera',
'Horse Feathers',
'Coconuts',
'The Big Store'
))
);

$looks = new Example();
$looks->SetProperty( 'novelty', array(
array( name =>'Groucho', 'signature' => 'moustache' ),
array( name =>'Chico', 'signature' => 'hat' ),
array( name => 'Harpo', 'signature' => 'raincoat' ),
array( name => 'Zeppo', 'signature' => 'hair')
)
);

$smarty = new Smarty();
$smarty->assign( 'person', $chico );
$smarty->assign( 'people', $looks->novelty );
$smarty->assign( 'quote', $groucho->quote );
$smarty->assign( 'movies', $movies->movies );
$smarty->display( 'template.tpl' );
?>

清单 2 反映了把 PHP 变量与 Smarty 占位符关联起来的一般策略。代码行 $smarty->assign( 'name', $x ) 将把 PHP 变量(或数组、对象)$x 与占位符名称关联起来。模板中显示 name 的所有位置都将显示 $x 的值。


图 1. 渲染最终页面,结合表单与函数
用 Smarty 分离 PHP 应用程序中的形式与功能_第1张图片

Smarty 模板是什么样的?Smarty 代码都是轻量级的,如清单 3、清单 4 和清单 5 所示。Smarty 将把大括号 ({}) 中的所有内容都视为 Smarty 代码。因此,如果任何其他页面标记(例如嵌入式 CSS 或 JavaScript)使用大括号,则必须用 {literal}...{/literal} 把那个标记括起来,如清单 3 中所示:


清单 3. 主模板中包括的简单 header 模板
                
<html>
<head>
<title>{$title|default:'The Marx Brothers'}</title>
<style type="text/css">
{literal}
body {
font-family: Verdana, Arial, sans-serif;
margin: 5%;
}
{/literal}
</style>
</head>
<body>

如前所述,清单 3 将应用 {literal} 运算符来逐字渲染标记:定界符之间的所有文本都将被传递而无需进一步解释。第 3 行将显示名为 <title> 的占位符的值,该占位符与 PHP 应用程序中的标量变量相关联。如果不与 <title> 关联,则 |default: 修饰符将提供默认值。注意 Smarty 运算符中的空白。通常,必须忽略它。幸运的是,Smarty 编译器将提供有帮助的错误消息。

清单 4 页脚显示了使用 {if condition} 在渲染时做出决定。在这里,如果占位符 quote 的值已设定,则将显示 {if}{/if} 之间插入的标记。代码行 {$quote|upper} 将用全大写的形式发送 quote 的值。|upper 是改变字符串输出的众多修饰符之一 —— 同时,它对于分离字符串内容与显示形式十分有用。


清单 4. 页脚模板
                
<div style="clear: both;">
<p align="center">
{if $quote}
<{$style|default:'strong'}>
{$quote|upper}
</{$style|default:'strong'}>
{/if}
</p>
</div>
</body>
</html>

清单 5 渲染了最终页面。


清单 5. 应用程序的主要 Smarty 模板
                
{include file='header.tpl'}

<p>
{$person->name} said, "{$person->quote}"
</p>

<ul>
{section name=index loop=$movies}
<li>
{$movies[index]}
</li>
{/section}
</ul>

<table>
{foreach item=person from=$people name=people}
{if $smarty.foreach.people.first}
<tr>
<th>#</th>
<th>Name</th>
<th>Signature</th>
</tr>
{/if}
<tr bgcolor="{cycle values="#eeeeee,#d0d0d0}">
<td>{$smarty.foreach.people.iteration}</td>
<td>{$person.name}</td>
<td>{$person.signature}</td>
</tr>
{foreachelse}
<tr><td>Print this only if there's no data.</td></tr>
{/foreach}
</table>

{include file='footer.tpl'}

应用程序的这个主要 Smarty 模板采用了若干个 Smarty 运算符:

{include file='filename'}
像是 PHP 自己的 include() 方法一样运行,在适当的位置立即插入和解释 filename 的内容。虽然并未显示,但是可以将变量从一个模板传递给另一个模板,这样做鼓励重用。
{$person->GetProperty('name')}
假定 person 与名为 GetProperty() 的方法相关。您可以调用对象的方法和引用对象成员,像 {$person->quote} 所做的那样。
{section name=index loop=$placeholder}
在数组内迭代。 loop 属性将给占位符命名,而 name 属性将指定一个名称以供数组索引使用。在循环内,将把数组元素作为 {$placeholder[index]} 来引用。
foreach
section 一样迭代,但是提供了一个非常优秀的功能来处理一组关联数组,例如数据库查询的行列表。每个关联数组都被 “转换” 到名为 item 的索引中。例如,在清单 5 中, person 被命名为 item。每执行一次循环, person 就会被指定来自数组 people 的关联数组。在那之后,在整个循环过程中,可以通过关键字引用关联数组中的值,如 {$person.signature}
foreach 中的 name 属性
类似于 HTML 标记的 id 属性,它将惟一地识别循环。使用此 ID 来引用反映循环状态的特殊变量集。例如,一个特殊变量是 first,它只在循环的第一次迭代时才被设定。因此,值 $smarty.foreach.people.first 将引用与名为 people ( people) 的 foreach 循环 ( foreach) 关联的特殊 Smarty 变量 ( smarty)。正如您可能会想到的那样,还有 last 值和 iteration 值,它们从 1 开始,并随每次迭代增加(如果需要从零开始的计数器,请使用 index 而不要使用 iteration)。
cycle
用于构建表的优秀运算符。如果提供 values 列表,Smarty 将像循环迭代一样在所有值中循环。将循环添加到 bgcolor 中将改变每个表行的颜色可以使表更清晰。
{foreachelse}
如果要迭代的数组为空,则转而显示 {foreachelse}...{/foreachelse} 的内容。

既然您已经预览了模板,那么 清单 2 读起来可能很简单。跟平常一样,清单 2 将执行计算并把渲染页面的工作传递给 Smarty。代码行 $smarty->display('template.tpl') 将渲染模板。要捕捉 Smarty 的输出,请使用 $smarty->fetch('template.tpl')





回页首


使用 Smarty 更聪明一点,而不是更辛苦一点

虽 然本例是经过设计的,但是它展示了 Smarty 的强大之处和灵活性以及使用它分离标记与代码是多么简单。Smarty 还有更多技巧。Smarty 可能实现您所需要的几乎所有功能。您可以将模板输出捕捉到 Smarty 占位符中。您可以过滤模板,无论是在编译前,还是在编译后,还可以在渲染输出被显示或获取之前先进行处理。而且 Smarty 允许您缓存模板。

向 PHP 代码中添加 $smarty->caching = 1; 即可获得上述特性。如果缓存被启用,则调用 display('template.tpl') 将像往常一样渲染模板并将在缓存中保存一份模板副本。下一次调用 display('template.tpl') 将利用缓存的副本,而不再渲染模板。

 
 

你可能感兴趣的:(用 Smarty 分离 PHP 应用程序中的形式与功能)