PHP早期開(kāi)發(fā)中通常是PHP代碼和HTML代碼混寫(xiě),這也使代碼中充斥著(zhù)數據庫操作,邏輯處理等。當項目不大時(shí),這樣的代碼還可以接受,但是隨著(zhù)項目不斷擴大,我們就會(huì )發(fā)現同一個(gè)文件中同時(shí)存在前端邏輯和后端處理,當邏輯越來(lái)越復雜時(shí),代碼的可讀性和可維護性都會(huì )變得非常差,以至于后來(lái)不得不進(jìn)行大規模的代碼重構。所以后來(lái)就出現了代碼分層的思想,盡量拆分開(kāi)前端代碼和后端代碼。
PHP模板引擎能解決這種混亂嗎?當然可以。但是呢,即使你不用專(zhuān)門(mén)的模板引擎也可以寫(xiě)出邏輯清晰的代碼,重點(diǎn)是要有分層的思想,有專(zhuān)門(mén)的腳本去進(jìn)行數據獲取,數據處理,邏輯處理等,在展示頁(yè)面只進(jìn)行盡可能簡(jiǎn)單的邏輯處理即可。既然這樣,那還有使用PHP模板引擎的必要嗎?毫無(wú)疑問(wèn)當然有,因為模板引擎的功能不僅于此。
那接下來(lái)就說(shuō)一下PHP模板引擎的主要作用:
1、它實(shí)現了一些自定義標簽,用于展示層的簡(jiǎn)單邏輯處理,相較于不適用引擎的好處是代碼看起來(lái)不像是PHP代碼了,感覺(jué)上HTML代碼和PHP代碼完全分開(kāi)了,但這只是假象,壞處是效率降低了,因為這樣的頁(yè)面需要專(zhuān)門(mén)的腳本解析后才能正確顯示,解析的方法就是使用正則表達式替換,這明顯降低了效率。到現在來(lái)看感覺(jué)PHP模板引擎有還不如沒(méi)有呢,那為什么還要用它呢,重點(diǎn)是他的下一個(gè)功能。
2、文件緩存,這是模板引擎在生產(chǎn)環(huán)境中提高效率的非常好的手段??梢杂迷陧?yè)面加載時(shí)所用數據量很大但不經(jīng)常變或者不需要實(shí)時(shí)更新,即使延遲一會(huì )也無(wú)妨的頁(yè)面。我個(gè)人感覺(jué)文件緩存是PHP模板引擎的最重要的部分。
接下來(lái)我們就寫(xiě)一個(gè)簡(jiǎn)易的模板引擎(最后有完整文件代碼)
首先我們先要計劃好我們的所需要的基礎類(lèi),有Template類(lèi)和Compile類(lèi)。
在我們具體實(shí)現編譯功能之前先搭好一個(gè)空的骨架,具體如下:


從上邊的代碼中我們能發(fā)現對于模板文件不存在和編譯文件不存在處理方式不同,這也很容易理解,如果你連模板文件都沒(méi)有有啥好編譯的,但是你有模板文件沒(méi)編譯文件這也很正常,正常進(jìn)行編譯即可。
如上所示,我們首先想好了這個(gè)模板引擎需要什么配置,還有一些設置配置的方法和檢查配置的方法等,而我們的核心方法show()還沒(méi)有實(shí)現呢,先不著(zhù)急,我們先去寫(xiě)編譯類(lèi)Compile,如下所示:

<?phpclass Compile{ private $template; // 待編譯的文件 private $content; // 需要替換的文件 private $comfile; // 編譯后的文件 private $left = '{'; // 左定界符 private $right = '}'; // 右定界符 private $value = array(); // 值棧 private $phpTurn; private $T_P = array(); // 匹配正則數組 private $T_R = array(); // 替換數組 public function __construct($template, $compileFile, $config) { $this->template = $template; $this->comfile = $compileFile; $this->content = file_get_contents($template); } public function compile() { $this->c_var(); file_put_contents($this->comfile, $this->content); } public function c_var() { $this->content = preg_replace($this->T_P, $this->T_R, $this->content); } public function __set($name, $value) { $this->$name = $value; } public function __get($name) { return $this->$name; }}?>

從上面Compile類(lèi)的構造函數我們可以看出,他需要模板文件路徑,編譯文件路徑,和具體編譯時(shí)的配置參數,但是在這這個(gè)配置參數嗎,沒(méi)有用到。之前說(shuō)過(guò)模板引擎主要使用的正則表達式來(lái)進(jìn)行匹配替換,將模板文件編譯成能正確執行的PHP文件,這里使用數組存放正則匹配數組和替換數組來(lái)進(jìn)行整體替換。
接下來(lái)我們就簡(jiǎn)單實(shí)現幾個(gè)常用的標簽,先看怎么替換簡(jiǎn)單變量:


正如我們看到的,上邊的那個(gè)是正則匹配,下邊的是替換。但是我們沒(méi)有給編譯類(lèi)的value變量賦值,那這個(gè)替換能找到正確的值嗎?答案是能,因為他用的不是這個(gè)類(lèi)的value用的是模板類(lèi)的value,接下來(lái)一會(huì )會(huì )講到。
然后我們在看看怎么實(shí)現foreach標簽,這個(gè)很常用

$this->T_P[] = '#\{(loop|foreach)\s+\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*\}#i';$this->T_P[] = '#\{\/(loop|foreach)\}#';

這里用到的主要正則表達式的知識有:元組、反向引用等,這些只要稍微看一下正則表達式基礎就能理解了。
我們再來(lái)一個(gè)if else標簽:

$this->T_P[] = '#\{\/(loop|foreach|if)\}#';$this->T_P[] = '#\{if (.*?)\}#';$this->T_P[] = '#\{(else if|elseif) (.*?)\}#';$this->T_P[] = '#\{else\}#';$this->T_R[] = '<?php } ?>';$this->T_R[] = '<?php if(\\1){ ?>';$this->T_R[] = '<?php }elseif(\\2){ ?>';$this->T_R[] = '<?php }else{ ?>';

我們將if的閉合標簽和foreach的閉合標簽放一塊了。
現在我們已經(jīng)能編譯一些標簽了我們就再轉回模板類(lèi),現在我們想一想要怎么展示呢,這個(gè)才是我們的根本目的。寫(xiě)代碼之前先理一下思路:
1、判斷是否開(kāi)啟了緩存,如果是進(jìn)行第二步,否則直接進(jìn)行編譯輸出。
2、判斷是否需要更新緩存(判斷方式主要是緩存時(shí)間和編譯文件和模板文件的修改時(shí)間的關(guān)系),如果是就進(jìn)行第三步,否則直接讀取緩存文件輸出。
3、重新編譯模板文件,并將編譯后的PHP文件輸出保存到靜態(tài)緩存文件中。
簡(jiǎn)單來(lái)說(shuō)就是上邊的那三個(gè)步驟,具體實(shí)現如下:


上邊的代碼基本是按照上述的三個(gè)步驟來(lái)進(jìn)行的,好好看一下不難理解。
接下來(lái)就是模板文件了:

<html>{$data},{$person}<br/>列表一:<br/><ul>{foreach $arr1}<li>$v</li>{/foreach}</ul><br/>列表二:<br/><ul>{loop $arr2}<li>$v</li>{/loop}</ul>{if $a == '1'}a等于1{elseif $a == '2'}a等于2{else}a不等于1也不等于2{/if}</html>

這個(gè)模板文件主要測試了之前我們事先的模板標簽。
下面寫(xiě)一個(gè)測試文件:


這就是一個(gè)簡(jiǎn)單的測試我們的模板引擎能不能用的測試用例。
下面我們看看完整代碼吧:

<?php/*** 模板引擎基礎類(lèi)*/class Template{ private $config = array( 'suffix' => '.m', // 設置模板文件的后綴 'templateDir' => 'template/', // 設置模板所在的文件夾 'compileDir' => 'cache/', // 設置編譯后存放的目錄 'cache_html' => true, // 是否需要編譯成靜態(tài)的HTML文件 'suffix_cache' => '.html', // 設置編譯文件的后綴 'cache_time' => 7200, // 多長(cháng)時(shí)間自動(dòng)更新,單位秒 'php_turn' => true, // 是否支持原生PHP代碼 'cache_control' => 'control.dat', 'debug' => false, ); private static $instance = null; private $value = array(); // 值棧 private $compileTool; // 編譯器 public $file; // 模板文件名,不帶路徑 public $debug = array(); // 調試信息 private $controlData = array(); public function __construct($config = array()) { $this->debug['begin'] = microtime(true); $this->config = $config + $this->config; if (! is_dir($this->config['templateDir'])) { exit('模板目錄不存在!'); } if (! is_dir($this->config['compileDir'])) { mkdir($this->config['compileDir'], 0770); } $this->getPath(); include './Compile.php'; } /** *獲取絕對路徑 */ public function getPath() { $this->config['templateDir'] = strtr(realpath($this->config['templateDir']), '\\', '/').'/'; $this->config['compileDir'] = strtr(realpath($this->config['compileDir']), '\\', '/').'/'; } /** *取得模板引擎的實(shí)例 */ public static function getInstance() { if (is_null(self::$instance)) { self::$instance = new self(); } return self::$instance; } /* 設置模板引擎參數 */ public function setConfig($key, $value = null) { if (is_array($key)) { $this->config = $key + $this->config; }else { $this->config[$key] = $value; } } /* 獲取當前模板引擎配置,僅供調試使用 */ public function getConfig($key = null) { if ($key) { return $this->config[$key]; }else { return $this->config; } } /** *注入單個(gè)變量 */ public function assign($key, $value) { $this->value[$key] = $value; } /** *注入數組變量 */ public function assignArray($array) { if (is_array($array)) { foreach($array as $k => $v) { $this->value[$k] = $v; } } } /** * 獲取模板文件完整路徑 */ public function path() { return $this->config['templateDir'].$this->file.$this->config['suffix']; } /** *判斷是否開(kāi)啟了緩存 */ public function needCache() { return $this->config['cache_html']; } /** *是否需要重新生成靜態(tài)文件 */ public function reCache($file) { $flag = true; $cacheFile = $this->config['compileDir'].md5($file).$this->config['suffix_cache']; if ($this->config['cache_html'] === true) { $timeFlag = (time() - @filemtime($cacheFile)) < $this->config['cache_time'] ? true : false; if (is_file($cacheFile) && filesize($cacheFile) > 1 && $timeFlag && filemtime($compileFile) >= filemtime($this->path())) { $flag = false; }else { $flag = true; } } return $flag; } /** *顯示模板 */ public function show($file) { $this->file = $file; if (! is_file($this->path())) { exit('找不到對應的模板!'); } $compileFile = $this->config['compileDir'].md5($file).'.php'; $cacheFile = $this->config['compileDir'].md5($file).$this->config['suffix_cache']; extract($this->value, EXTR_OVERWRITE); if ($this->config['cache_html'] === true) { // 開(kāi)啟緩存的處理邏輯 if ($this->reCache($file) === true) { // 需要更新緩存的處理邏輯 $this->debug['cached'] = 'false'; $this->compileTool = new Compile($this->path(), $compileFile, $this->config); if ($this->needCache()) {ob_start();} // 打開(kāi)輸出控制緩沖 if (! is_file($compileFile) || filemtime($compileFile) < filemtime($this->path())) { $this->compileTool->value = $this->value; $this->compileTool->compile(); include $compileFile; }else { include $compileFile; } if ($this->needCache()) { $message = ob_get_contents(); // 獲取輸出緩沖中的內容(在include編譯文件的時(shí)候就有輸出了) file_put_contents($cacheFile, $message); } }else { readfile($cacheFile); $this->debug['cached'] = 'true'; } }else { if (! is_file($compileFile) || filemtime($compileFile) < filemtime($this->path())) { $this->compileTool = new Compile($this->path(), $compileFile, $this->config); $this->compileTool->value = $this->value; $this->compileTool->compile(); include $compileFile; }else { include $compileFile; } } $this->debug['spend'] = microtime(true) - $this->debug['begin']; $this->debug['count'] = count($this->value); //$this->debug_info(); } public function debug_info() { if ($this->config['debug'] === true) { echo PHP_EOL, '---------debug info---------', PHP_EOL; echo '程序運行日期:', date('Y-m-d H:i:s'), PHP_EOL; echo '模板解析耗時(shí):', $this->debug['spend'], '秒', PHP_EOL; echo '模板包含標簽數目:', $this->debug['count'], PHP_EOL; echo '是否使用靜態(tài)緩存:', $this->debug['cached'], PHP_EOL; echo '模板引擎實(shí)例參數:', var_dump($this->getConfig()); } } /** *清理緩存的HTML文件 */ public function clean($path = null) { if ($path === null) { $path = $this->config['compileDir']; $path = glob($path.'* '.$this->config['suffix_cache']); }else { $path = $this->config['compileDir'].md5($path).$this->config['suffix_cache']; } foreach((array)$path as $v) { unlink($v); } }}?>



模板文件member.m代碼:

<html>{$data},{$person}<br/>列表一:<br/><ul>{foreach $arr1}<li>$v</li>{/foreach}</ul><br/>列表二:<br/><ul>{loop $arr2}<li>$v</li>{/loop}</ul>{if $a == '1'}a等于1{elseif $a == '2'}a等于2{else}a不等于1也不等于2{/if}</html>

測試用例:


本文內容大部分來(lái)自于《PHP核心技術(shù)與最佳實(shí)踐》的第六章。
聯(lián)系客服