模板引擎,這四個字聽起來很高深的樣子,一般用到“引擎”兩字都會感覺比較高級,類似游戲3D引擎、Zend引擎等,其實都是唬人的,騙外行人的。所以在我初學PHP的那會,也因為這四個字導致了我覺得很難而沒有去看他到底是什么樣一個東西,直到很長時間以后使用Smarty才真正了解模板引擎的原理和作用。Smarty(http://smarty.php.net),PHP官方模板引擎,看名字給人感覺應該很快,其實很慢,即使他有預編譯(另一個看起來很高級的名詞,同樣也是唬人的,下面我會講到這個)。[注:我剛才點開Smarty發現他說他已經不是一個PHP子項目了,汗,看來確實唬人,哈玩笑^_^]。其實在PHP里,模板引擎扮演著View(其實通俗說就是頁面,看英文有時候會給人很高級的錯覺)的角色,這是一個很重要的角色,因為用戶的交互啊,界面效果啊等等都在這里,這是最終用戶看到的你的系統的樣子。
開頭就說模板引擎,只是跟大家說明一下這個東西其實沒有什么難理解的,明白其原理以后你會發現他是紙老虎,所以你要有信心你會很輕松看完此文。
為了更好的說明模板引擎所扮演的角色,我不得不也談談MVC。這個話題恐怕互聯網上談及的很多,我也只能根據我的理解來描述,可能有不恰當的地方,歡迎討論。通常的MVC是指Model、View和Controller。也就是模型、視圖和控制器。我理解MVC也是在學了PHP不短時間后了,當時請教老廖(http://qeephp.com),才恍然大悟。
先來說說Controller,也就是控制器,控制器是個什么東西呢?在PHP里他是扮演一個接收用戶請求,把用戶請求定位到指定數據模型的角色。解釋起來感覺不是很好解釋,來看一個簡單的留言本的例子:
//用戶請求可能是 http://www.example.com/guest.php?module=list
$module = $_GET[‘module’];
switch ($module) {
case ‘list’:
require_once ‘list.php’;
break;
case ‘add’:
require_once ‘add.php’;
break;
case ‘del’:
require_once ‘del.php’;
break;
default:
require_once ‘list.php’;
break;
}
是不是看起來很簡單好像沒什么東西呀,只是根據用戶的請求參數包含不同的文件而已。沒錯,確實很容易,這個switch語句其實就一個最簡單的控制器的實現。他控制什么?他控制你根據不同的用戶請求參數調用不同的數據模型處理用戶請求。那么這里的list可能是一個留言列表,add是添加留言,del是刪除留言。Controller的傳統實現可以這么簡單,當然現在的很多技巧包括根據不同的用戶請求包含不同的業務邏輯處理類,比如list自動定位到/model/List.class.php這樣的一些技巧性操作等。
再來說說Model,其實我們一般花比較長時間設計和編寫的也是這塊內容,也就是具體的業務邏輯實現。比如一個留言列表要處理些什么,都是在這里實現。還是直接看一個Model例子比較直觀:
//Guest_List.class.php
class Guest_List {
public $page = 1;
public function __construct() {
$this->db = DB::init($GLOBALS[‘dsn’]);
$this->page = (int) $_GET[‘page’];
}
public function getList() {
$begin = $this->page * 10;
$sql = “SELECT * FROM guest ORDER BY addTime DESC LIMIT $begin, 10”;
return $this->db->getAll($sql);
}
}
這里的Guest_List就是一個簡單的Model實現,構造函數取得頁數page參數,getList方法查詢留言列表并返回結果集。那么在list.php里可能是這樣調用的:
//list.php
require_once ‘Guest_List.class.php’;
$model = new Guest_List();
$lists = $model->getList();
嗯,其實很多MVC框架都是這么實現的,只不過可能加了一些自動調用的機制,會根據用戶請求自動調用類,自動執行方法,呵呵。Model大功告成。這里需要明確一點就是,Model只是返回視圖上所可能需要用到的數據,他不負責任何和顯示有關的事情,那么顯示相關的就交給View來做了。我們是不是不知不覺已經把表現和業務邏輯分離了?沒錯,分離就是這么簡單。
好了,來看看View怎么利用Model返回的數據來顯示頁面吧。最簡單的例子,我們只需要在list.php里增加一行即可。
//list.php
require_once ‘Guest_List.class.php’;
$model = new Guest_List();
$lists = $model->getList();
//上面是Model,那么下面就是View
require_once ‘list.html’;
來看看View都做些什么吧,我們用list.html來表示留言列表所展現給用戶的界面文件,用html來命名看起來會更直觀一些,他好像是個html文件,負責輸出html代碼給瀏覽器。來看看list.html可能長什么樣子:
<!–list.html–>
<table>
<?php foreach ($lists as $value) { ?>
<tr>
<td><?php echo htmlspecialchars($value[‘guest_user_name’]);?></td>
<td><?php echo date(‘Y-m-d H:i’, $value[‘guest_date_time’]);?></td>
<td><?php echo htmlspecialchars($value[‘guest_content’]);?></td>
</tr>
<?php } ?>
</table>
不難看出來這個文件所做的只不過是遍歷留言數組$lists,然后輸出每一行的留言,對留言的內容處理做了htmlspecialchars和date轉換(與顯示相關的處理),除了和顯示相關的操作,他沒有再做任何業務邏輯了(也不應該有)。
我發現寫到這里真的沒有什么好寫的了,MVC就是這些(或者再做一些擴展),至于怎么做到表現和業務分離,那么就是在你的Model里只返回數據,也就是你View所需要用到的數據,而你的View拿到這些數據后負責去顯示他就可以了,不應該在你的Model里做顯示和視覺相關的操作,也不應該在你的View里做一些業務邏輯相關的操作,把這兩者分清楚,就自然而然的表現與業務分離了。
接下來說說負責View的模板引擎吧,其實你在上面應該已經看到了一個最簡陋的模板引擎,那就是View部分的 require_once 語句。厄,實在是太簡單了,模板引擎其實是調度并解析模板的東西,其中調度模板由 require_once 搞定了,那么解析呢?這里由 PHP 引擎本身來搞定了。哈,沒錯,我一直都認為 PHP 是個最好的模板引擎。
不過還是不得不說說傳統的模板引擎的實現原理,一般來說會有這么幾個步驟:
1、注冊變量,也就是把從Model返回的數據注冊到模板引擎中,告訴模板引擎這個變量可以使用,其實所謂的注冊也只不過是不得不這么做,因為一般引擎內部函數是沒辦法直接訪問Model返回的變量的(變量作用域的問題),所以不得不加一個注冊操作,把這些變量轉換從模板引擎類的屬性等。
2、模板解析,就是讀取模板文件,按照模板語法將標簽解析成 PHP 語法,或者執行一些替換操作,用變量內容替換掉模板標簽,其實效果都差不多。
3、如果不是將變量內容替換掉模板標簽,那么基本上第三步就是將注冊的變量和解析完的模板融合在一起輸出,類似于上面的list.html,是個解析完的文件,然后輸出。
一般模板引擎還會提供不少用于顯示內容處理的插件,比如日期轉換、字符串處理、生成表格、生成select等,這些給頁面制作提供了一些方便。Smarty還包含了一些頁面緩存機制,也很不錯。
很多模板引擎都頂著語法簡單的嚎頭,美其名曰降低美工的學習門檻。其實我不得不問,有多少模板是由美工來做的呢?而且對比兩種語法,不覺得 PHP 的簡單循環和輸出有什么難以理解的,對比下面兩種語法:
<!– <?php foreach ($lists as $value) { ?> –>
<?=value[‘userName’]?>
<!– <?php } ?> –>
和
<!– loop lists value –>
{value[‘userName’]}
<!– loop –>
我左看右看都覺得他們差不多,呵呵,與其再學習一套語法,還不如直接用你已經非常熟悉的PHP呢,為什么要虐待自己呢?而且從可維護性的角度來講,維護PHP語法和維護模板語法,哪種更容易呢?PHP是標準,只要會PHP都知道他怎么寫,表示什么,但是模板引擎千奇百怪的,各種語法都有,不是一個統一的標準,我想誰維護一個從來沒有用過的模板,都需要花不少時間去學習引擎語法。更何況即使模板可以那樣寫,最終還是需要一堆正則替換成PHP語法。我敢肯定,前面寫的哪種模板引擎語法最終會被轉換成它上面那種PHP。其實模板引擎的解析也就是將模板語法轉換成PHP語法的過程。拋開效率來說,多此一舉。就象《C專家編程》作者說的,即使你能用宏把C寫成看起來好像另外一種語言,但是你不要這么做,同樣的這句告誡是否適合于模板引擎呢,它看起來很像另外一種語言。當然我這篇文章不是來批判模板引擎的,哈。它既然存在,也有其存在的道理,某些場合還是不得不用的,比如如果你把模板提供給用戶去制作和使用,那么你不得不采用標簽以限制用戶使用PHP語法,來增強系統安全性。
再來說效率問題,由于模板引擎要解析模板語法,會用到很多正則匹配和替換,那么在實際運行中是比較消耗系統資源的,而且當模板標簽非常復雜或者嵌套多層的時候,效率是比較低的,因為有了一種處理方法,就是預編譯。所謂的預編譯,就是把帶有模板語法的模板,通過處理,轉換成 PHP 語法的文件,只要模板文件沒有被修改,那么直接包含編譯后的文件即可,這樣就不需要再次替換和匹配,可以大大提高效率,不過由于模板引擎的復雜性,導致編譯后的結果文件仍然比我們一般寫出來的PHP文件復雜得多。所以其實效率還是遠低于直接編寫PHP模板的。有興趣的可以打開一個Smarty編譯過的文件,看看其嵌套,其實要比直接循環來得復雜。
本文寫到這里也差不多了,具體模板引擎如何編譯如何處理,各個模板引擎的方式都不一樣,有興趣的可以去下載幾個比較經典的引擎看看,比如Smarty。隨后我附上我自己用的PHP模板引擎。