轉自ibm developerWorks 作者:Martin Streicher (martin.streicher@linux-mag.com),
2007 年 9 月 06 日
隨意混用 PHP 與其他 Web 頁面標記將導致程式邏輯、HTML、層疊樣式表(Cascading Style Sheets,CSS)和 JavaScript 處於混亂狀態,使維護成為一項艱巨的任務。Smarty 模板引擎可以將形式與功能分離。
PHP Web 應用程式易於上手。PHP 語言的文法整潔且易於掌握。可以將 PHP 與 HTML、JavaScript 和 CSS 直接混用以快速產生可視結果。而且,把 PHP 應用程式部署到您自己的 Web 服務器或託管服務中只是小菜一碟。
但 是混用 PHP 與其他頁面標記也是一項責任。PHP 代碼通常是含有程式邏輯、結構化查詢語言 (SQL)(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) 和顯示引擎。應用程式代碼將調用 API 把代碼變數與模板預留位置關聯起來,而顯示引擎將解釋 Smarty 標記、執行迴圈、引用預留位置和顯示最終結果。Smarty 功能包括:
-
用於顯示 PHP 的所有基本資料結構的運算子
-
顯示簡單變數,迭代整個數組或關聯陣列,以及顯示類的成員。
-
預留位置的預設值
-
如果 PHP 代碼沒有將變數與預留位置關聯,則顯示預設值。
-
控制運算子,例如
if、then、else,可以根據輸入資料選擇動態顯示哪些內容
-
例如,設計人員可以選擇用加粗的紅色文本顯示負賬戶餘額,而用黑色文本顯示正餘額。您可以在模板中隔離此類顯示邏輯(使您可以更輕鬆地進行開發)。
-
迴圈控制,它將提供用於簡化構建列表和表的特殊變數
-
例如,可以測試迴圈的第一次迭代並建立表頭。還可以像迴圈迭代一樣迴圈執行值輪循 (round-robin) 列表,迴圈迭代非常適於改變表行的顏色。
-
渲染時用於改變資料的修飾符
-
例如,可以用 Smarty 標記
<strong>{$name|upper}</strong> 大寫加粗顯示預留位置 —— 如
$name。
<strong> 是普通 HTML。大括弧 ({}) 用於劃定 Smarty 標記,$name 是預留位置,而 |upper 是修飾符。還可以編寫自己的修飾符以擴充 Smarty 的功能。
-
如果必須 包括指令碼和原始 PHP 代碼,可以用
literal 和 php 運算子來完成
-
literal 運算子內的所有內容都將被逐字傳遞給最終頁面。
php 運算子中放置的代碼將像嵌入到
<?php ... ?> 轉義符內一樣執行。
還可以通過 {include ...} 運算子重用 Smarty 模板。要提高效能,則需緩衝每個模板,以避免每次使用的轉換負載。Smarty Web 網站提供了豐富文檔和樣本。Packt Publishing 還提供一本名為 Smarty: PHP Template Programming and Applications(請參閱 參考資料)的書,該書適於學習和參考(警告:一些最新運算子並未介紹,而且其他運算子的說明也不正確,因為該書介紹的是 Smarty V2.x 的早期版本)。
用 Smarty 進行開發
無法通過一篇文章列舉和示範 Smarty 的所有功能。但是,即使是一個如下所示的小樣本,也能證明模板的力量。
把 Smarty 添加到應用程式中十分輕鬆:
- 下載 Smarty.zip 示範代碼(請參閱 下載)。
- 解壓縮並把 Smarty 安裝到路徑中。
- 編寫要求使用 Smarty 類的應用程式。
- 建立兩個目錄一起放置應用程式:
- 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 模板是什麼樣的?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') 將利用緩衝的副本,而不再渲染模板。