作者:Brian Lozier
譯者:taowen
In general, template engines are a "good thing."
總體來說,模板引擎是一個"好東西"
作為一個PHP/Perl的程序員,許多模板引擎(fastTemplate, Smarty, Perl的 HTML::Template)的用戶,以及我自己的 bTemplate [1]的作者,我講這句話很多次了。
然而,在同事進行了長時間的討論之后,我確信了大量的模板引擎(包括我自己寫的)根本是錯誤的。 我想唯一的例外是 Smarty [2],雖然我認為它太龐大了,并且考慮到這篇文章的其余部分相當?shù)臎]有觀點。然而,就你為什么選擇Smarty(或者類似的解決方案)有幾個理由,這些將在文章后面探究。
這篇文章討論模板的理論。我們將看到為什么大部分"模板引擎"是過于肥大,并且最終我們將回過頭來看一個輕量級的,小巧快速的另類選擇。
下載和授權(quán)
一些關(guān)于模板引擎的背景知識
讓我們首先研究一下模板引擎的背景知識。模板引擎被設(shè)計出來用于把商業(yè)邏輯(例如從數(shù)據(jù)庫中獲取數(shù)據(jù)或者計算貿(mào)易耗費)從數(shù)據(jù)的表現(xiàn)分離開來。模板引擎解決了兩個主要問題:
- 如何實現(xiàn)這種分離
- 如何從HTML中分離"復(fù)雜"的php代碼
這從理論上使得沒有PHP經(jīng)驗的HTML設(shè)計者能夠不看任何PHP代碼的條件下修改站點的外觀。
然而,模板系統(tǒng)也引入了一些復(fù)雜性。首先,我們現(xiàn)在有一個從多個文件得來的"頁面"。典型的,你可能有一個主PHP頁負責業(yè)務(wù)邏輯,一個外面的"布局"模板把整個站點的整體布局進行渲染,一個內(nèi)部的內(nèi)容特定的模板,一個數(shù)據(jù)庫抽象層,以及模板引擎本身(這些可能是也可能不是由多個文件組成)。也有可能,一些人僅僅簡單地在每個PHP頁面的首尾處包含"頭部"和"尾部"文件。
這產(chǎn)生的單個頁面的文件數(shù)量是很可觀的。然而,因為PHP解析器非?,用到的文件數(shù)量可能不是那么重要除非你的站點流量很大。 然而,要記住模板系統(tǒng)引入了另外一個處理的層次。模板文件不僅僅是必須被包含,他們還必須被解析(取決于模板系統(tǒng),這個行為有很多種方式來完成 —— 使用正則表達式,字符串替換,編譯,詞法分析,等等)。這就是為什么對模板進行測速變得流行起來:因為模板引擎使用各種方法來解析數(shù)據(jù),它們中的一些比另外一些要快(而且,一些模板引擎提供了比其他引擎更加豐富的功能)。
模板引擎基礎(chǔ)知識
簡單地說,模板引擎利用了用C寫的腳本語言(PHP)。在這些嵌入的腳本語言中,你有另外一個偽腳本語言(無論你的模板引擎支持何種標簽)。某些提供了簡單的變量改寫和循環(huán)。另外一些呢,則提供了條件和嵌套循環(huán)。而再其他的呢(至少有Smarty)提供了一個PHP的比較大的子集的接口,以及一個緩沖層。
為什么我認為Smarty最接近于正確的方向?因為Smarty的目標是"把業(yè)務(wù)邏輯從表現(xiàn)中分離出來"而不是"PHP代碼和HTML代碼的分離"。這看上去區(qū)別不大,但是它正是要點所在。任何模板引擎的最終目標不應(yīng)該是從HTML移除所有的邏輯。它應(yīng)該是把表現(xiàn)邏輯從業(yè)務(wù)邏輯中分離出來。
有很多你僅僅需要邏輯來正確顯示你的數(shù)據(jù)的例子。例如,你的業(yè)務(wù)邏輯是從你的數(shù)據(jù)庫中獲取一個用戶列表。你的表現(xiàn)邏輯可能是把用戶列表用3列顯示?赡苄薷挠脩袅斜砗瘮(shù)使得它返回3個數(shù)組是很笨的辦法。畢竟函數(shù)不應(yīng)該關(guān)心數(shù)據(jù)接下來要怎么處理這樣的事情。然而,在你的模板文件中缺少一些邏輯,那些正是你要做的事情。
在這點上Smarty是正確的(使得你利用PHP的很多東西),但是仍然有許多問題;旧希鼉H僅提供了一個以新語法訪問PHP的接口。以那開始,它看上去不那么聰明了。是不是事實上寫 {foreach --args} 比 <? foreach --args ?> 更加簡單?如果你認為這樣簡單一些,問問你自己是不是在包含一個巨大的模板庫來到成這種分離時能夠看到真正的意義要更加簡單一些。誠然,Smarty提供了許多其他很好的特性,但是看上去這些益處能夠在不用承擔包含Smarty類庫的情況下也能獲得。
別樣的解決方案
我主要要鼓吹的一個解決方案是一個使用PHP代碼作為它的原生腳本語言的"模板引擎"。我知道這以前有人做過。而且當我第一次看到的時候,我想,"為什么要這樣做?",然而我在考慮過我同事的論據(jù)之后,并且實現(xiàn)了一個直接使用PHP代碼仍然實現(xiàn)了把業(yè)務(wù)邏輯和表現(xiàn)邏輯分離的最終目標的模板系統(tǒng)時(只用了大約25行代碼,不包括注釋),我意識到了好處所在。
這個系統(tǒng)給像我們這樣的開發(fā)者提供了對PHP核心函數(shù)的訪問權(quán)利,我們能夠使用他們來格式化輸出——像日期格式化這樣的任務(wù)應(yīng)該在模板中處理。而且,因為模板是普通的PHP文件,像 Zend Performance Suite [6]和 PHP Accelerator [7]這樣的字節(jié)碼緩存程序,能夠自動緩存模板(因而,它們不需要在每次被訪問時都被重新解釋執(zhí)行)。只要你記得把你的模板文件命名為程序能夠辨認出是PHP文件的名字(通常,你僅僅需要確保它們有一個.php的后綴),這確實是一個好處。
當我認為這種方法比經(jīng)典的模板引擎要高明得多時,肯定還有一些要商榷的問題。最明顯的反面意見是,PHP代碼太復(fù)雜了,而且設(shè)計者不應(yīng)該強迫去學(xué)習(xí)PHP。事實上,PHP代碼和像Smarty這樣的高級模板引擎的語法差不多簡單(如果不是更簡單的話)。而且,設(shè)計者能夠使用像<?=$var;?> 這樣的簡寫PHP。這要比{$var} 復(fù)雜很多?當然,這要長一些,但是如果你習(xí)慣了,你能夠獲得了PHP的威力而且不用承受解析模板文件帶來的負擔。
第二,而且可能更重要的,在基于PHP的模板中沒有固有的安全。Smarty提供了選項在模板文件中徹底禁用PHP代碼。它使得開發(fā)者能夠約束模板能夠訪問的函數(shù)和變量。如果你沒有不懷好意的設(shè)計者,這不會是什么問題。然而,如果你允許外部的用戶上傳或者修改模板,我在此展示的基于PHP的解決方案絕對沒有任何安全可言!任何代碼都能放入模板中并且得到運行。是的,甚至是一個print_r($GLOBALS) (這將改有惡意的用戶訪問腳本中任何變量的權(quán)利)。
但是,我個人或者工作上寫過的項目中,絕大多數(shù)不允許最終的用戶修改或者上傳模板。如果是這樣,問題就不存在了。因此現(xiàn)在讓我們來看看代碼吧。
例子
這是一個簡單的用戶列表頁面的例子。
<?php require_once('template.php');
/** * This variable holds the file system path to all our template files. */ $path = './templates/';
/** * Create a template object for the outer template and set its variables. */ $tpl = & new Template($path); $tpl->set('title', 'User List');
/** * Create a template object for the inner template and set its variables. The * fetch_user_list() function simply returns an array of users. */ $body = & new Template($path); $body->set('user_list', fetch_user_list());
/** * Set the fetched template of the inner template to the 'body' variable in * the outer template. */ $tpl->set('body', $body->fetch('user_list.tpl.php'));
/** * Echo the results. */ echo $tpl->fetch('index.tpl.php'); ?>
其中有兩個值得注意的重要的概念。第一個就是內(nèi)部和外部模板的概念。外部模板包含定義站點主要外觀的HTML代碼。而內(nèi)部模板包含定義站點內(nèi)容區(qū)域的HTML代碼。當然,你能夠在任意數(shù)目的層上有任意數(shù)目的模板。因為通常我們給每個區(qū)域使用不同的模板對象,所以沒有名字空間的問題。例如,我能在內(nèi)部和外部模板中都有變量叫"title",而不用害怕有什么沖突。
這是一個用來顯示用戶列表的模板的簡單例子。注意特殊的foreach和endforeach;語法在PHP手冊中有 說明 [8]。它完全是可選擇的。
而且,你可能奇怪我為什么要用.php的后綴來命名我的模板文件。呵呵,許多PHP字節(jié)碼緩存解決方案(比如 phpAccelerator)如果要被認成PHP文件,需要文件有一個.php后綴。因為這些模板是PHP文件,為什么不去獲得這些好處?
<table> <tr> <th>Id</th> <th>Name</th> <th>Email</th> <th>Banned</th> </tr> <? foreach($user_list as $user): ?> <tr> <td align="center"><?=$user['id'];?></td> <td><?=$user['name'];?></td> <td><a href="mailto:<?=$user['email'];?>"><?=$user['email'];?></a></td> <td align="center"><?=($user['banned'] ? 'X' : ' ');?></td> </tr> <? endforeach; ?> </table>
這個layout.tpl.php是一個簡單的例子(定義了整個頁面看上去是什么樣子的模板文件)
<html> <head> <title><?=$title;?></title> </head> <body> <h2><?=$title;?></h2> <?=$body;?> </body> </html>
And here's the parsed output. 而這是解析后的輸出。
代碼拷貝框
[Ctrl+A 全部選擇 然后拷貝]
Caching
緩存
因為解決方案簡單如斯,實現(xiàn)模板緩存成為了一個非常簡單的任務(wù)。為了實現(xiàn)緩存,我們有一個二級類,它擴展了原來的模板類。CachedTemplate類事實上使用和原來的模板類相同的API。不同點是我們必須傳遞緩存的設(shè)置給構(gòu)造函數(shù),并且調(diào)用fetch_cache() 而不是fetch() 。
緩存的概念是簡單的。簡單的說,我們設(shè)置一個緩存時間來調(diào)表輸出應(yīng)該被保存的時長(以秒為單位)。在產(chǎn)生一個頁面的所有工作開展之前,我們必須首先測試頁面是否已經(jīng)被緩存了,而且緩存是否仍然沒有過期。如果緩存在這那,我們不需要在去麻煩數(shù)據(jù)庫和業(yè)務(wù)邏輯來產(chǎn)生頁面——我們可以簡單地輸出原先緩存地內(nèi)容。
這種方法需要解決唯一地標識緩存文件的問題。如果一個站點是被一個顯示基于GET變量的中心腳本所控制,對每個PHP文件只有一個緩存不會有什么幫助。例如,如果index.php?page=about_us 和用戶調(diào)用index.php?page=contact_us 得到的顯示完全不同。
問題是通過給每個頁面產(chǎn)生一個唯一的cache_id 來解決的。為了做到這個目的,我們把事實上被請求的文件變成REQUEST_URI (基本上就是整個URL:index.php?foo=bar&bar=foo )。當然,這個轉(zhuǎn)換過程是受到CachedTemplate類控制的,但是要記住的重要的事情是你絕對要在創(chuàng)建CachedTemplate 對象時傳遞一個唯一的cache_id 。當然下面有例子來說明。
使用緩存包括以下步驟。
include() 模板源文件
- 創(chuàng)建一個新的
CachedTemplate 對象(并且傳遞路徑,唯一的cache_id 和緩存過期時間給模板)
- 測試內(nèi)容是否已經(jīng)被緩存了
- 如果還促拿了,顯示文件并且結(jié)束腳本
- 否則,進行所有的處理并且
fetch() 模板
- 對
fetch_cache() 的調(diào)用將自動產(chǎn)生一個新的緩存文件
這個腳本假定你的緩存文件將放到./cache/ 中,因此你必須創(chuàng)建那個目錄并且改變它的目錄權(quán)限(chmod )使得Web服務(wù)器能夠?qū)懭胛募6疫要注意如果你在編寫腳本的過程中發(fā)現(xiàn)了錯誤,錯誤也會被緩存!因而在你開發(fā)的過程中禁用緩存是一個好主意。最好的辦法是給cache的生存周期傳遞0——這樣,緩存總是立即就失效了。
出處:CSDN
責任編輯:cjj
◎進入論壇網(wǎng)絡(luò)編程版塊參加討論
|