PHP早期開發(fā)中通常是PHP代碼和HTML代碼混寫,這也使代碼中充斥著數(shù)據(jù)庫(kù)操作,邏輯處理等。當(dāng)項(xiàng)目不大時(shí),這樣的代碼還可以接受,但是隨著項(xiàng)目不斷擴(kuò)大,我們就會(huì)發(fā)現(xiàn)同一個(gè)文件中同時(shí)存在前端邏輯和后端處理,當(dāng)邏輯越來越復(fù)雜時(shí),代碼的可讀性和可維護(hù)性都會(huì)變得非常差,以至于后來不得不進(jìn)行大規(guī)模的代碼重構(gòu)。所以后來就出現(xiàn)了代碼分層的思想,盡量拆分開前端代碼和后端代碼。
PHP模板引擎能解決這種混亂嗎?當(dāng)然可以。但是呢,即使你不用專門的模板引擎也可以寫出邏輯清晰的代碼,重點(diǎn)是要有分層的思想,有專門的腳本去進(jìn)行數(shù)據(jù)獲取,數(shù)據(jù)處理,邏輯處理等,在展示頁面只進(jìn)行盡可能簡(jiǎn)單的邏輯處理即可。既然這樣,那還有使用PHP模板引擎的必要嗎?毫無疑問當(dāng)然有,因?yàn)槟0逡娴墓δ懿粌H于此。
那接下來就說一下PHP模板引擎的主要作用:
1、它實(shí)現(xiàn)了一些自定義標(biāo)簽,用于展示層的簡(jiǎn)單邏輯處理,相較于不適用引擎的好處是代碼看起來不像是PHP代碼了,感覺上HTML代碼和PHP代碼完全分開了,但這只是假象,壞處是效率降低了,因?yàn)檫@樣的頁面需要專門的腳本解析后才能正確顯示,解析的方法就是使用正則表達(dá)式替換,這明顯降低了效率。到現(xiàn)在來看感覺PHP模板引擎有還不如沒有呢,那為什么還要用它呢,重點(diǎn)是他的下一個(gè)功能。
2、文件緩存,這是模板引擎在生產(chǎn)環(huán)境中提高效率的非常好的手段??梢杂迷陧撁婕虞d時(shí)所用數(shù)據(jù)量很大但不經(jīng)常變或者不需要實(shí)時(shí)更新,即使延遲一會(huì)也無妨的頁面。我個(gè)人感覺文件緩存是PHP模板引擎的最重要的部分。
接下來我們就寫一個(gè)簡(jiǎn)易的模板引擎(最后有完整文件代碼)
首先我們先要計(jì)劃好我們的所需要的基礎(chǔ)類,有Template類和Compile類。
在我們具體實(shí)現(xiàn)編譯功能之前先搭好一個(gè)空的骨架,具體如下:
從上邊的代碼中我們能發(fā)現(xiàn)對(duì)于模板文件不存在和編譯文件不存在處理方式不同,這也很容易理解,如果你連模板文件都沒有有啥好編譯的,但是你有模板文件沒編譯文件這也很正常,正常進(jìn)行編譯即可。
如上所示,我們首先想好了這個(gè)模板引擎需要什么配置,還有一些設(shè)置配置的方法和檢查配置的方法等,而我們的核心方法show()還沒有實(shí)現(xiàn)呢,先不著急,我們先去寫編譯類Compile,如下所示:
<?phpclass Compile{ private $template; // 待編譯的文件 private $content; // 需要替換的文件 private $comfile; // 編譯后的文件 private $left = '{'; // 左定界符 private $right = '}'; // 右定界符 private $value = array(); // 值棧 private $phpTurn; private $T_P = array(); // 匹配正則數(shù)組 private $T_R = array(); // 替換數(shù)組 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類的構(gòu)造函數(shù)我們可以看出,他需要模板文件路徑,編譯文件路徑,和具體編譯時(shí)的配置參數(shù),但是在這這個(gè)配置參數(shù)嗎,沒有用到。之前說過模板引擎主要使用的正則表達(dá)式來進(jìn)行匹配替換,將模板文件編譯成能正確執(zhí)行的PHP文件,這里使用數(shù)組存放正則匹配數(shù)組和替換數(shù)組來進(jìn)行整體替換。
接下來我們就簡(jiǎn)單實(shí)現(xiàn)幾個(gè)常用的標(biāo)簽,先看怎么替換簡(jiǎn)單變量:
正如我們看到的,上邊的那個(gè)是正則匹配,下邊的是替換。但是我們沒有給編譯類的value變量賦值,那這個(gè)替換能找到正確的值嗎?答案是能,因?yàn)樗玫牟皇沁@個(gè)類的value用的是模板類的value,接下來一會(huì)會(huì)講到。
然后我們?cè)诳纯丛趺磳?shí)現(xiàn)foreach標(biāo)簽,這個(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)\}#';
這里用到的主要正則表達(dá)式的知識(shí)有:元組、反向引用等,這些只要稍微看一下正則表達(dá)式基礎(chǔ)就能理解了。
我們?cè)賮硪粋€(gè)if else標(biāo)簽:
$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的閉合標(biāo)簽和foreach的閉合標(biāo)簽放一塊了。
現(xiàn)在我們已經(jīng)能編譯一些標(biāo)簽了我們就再轉(zhuǎn)回模板類,現(xiàn)在我們想一想要怎么展示呢,這個(gè)才是我們的根本目的。寫代碼之前先理一下思路:
1、判斷是否開啟了緩存,如果是進(jìn)行第二步,否則直接進(jìn)行編譯輸出。
2、判斷是否需要更新緩存(判斷方式主要是緩存時(shí)間和編譯文件和模板文件的修改時(shí)間的關(guān)系),如果是就進(jìn)行第三步,否則直接讀取緩存文件輸出。
3、重新編譯模板文件,并將編譯后的PHP文件輸出保存到靜態(tài)緩存文件中。
簡(jiǎn)單來說就是上邊的那三個(gè)步驟,具體實(shí)現(xiàn)如下:
上邊的代碼基本是按照上述的三個(gè)步驟來進(jìn)行的,好好看一下不難理解。
接下來就是模板文件了:
<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è)模板文件主要測(cè)試了之前我們事先的模板標(biāo)簽。
下面寫一個(gè)測(cè)試文件:
這就是一個(gè)簡(jiǎn)單的測(cè)試我們的模板引擎能不能用的測(cè)試用例。
下面我們看看完整代碼吧:
<?php/*** 模板引擎基礎(chǔ)類*/class Template{ private $config = array( 'suffix' => '.m', // 設(shè)置模板文件的后綴 'templateDir' => 'template/', // 設(shè)置模板所在的文件夾 'compileDir' => 'cache/', // 設(shè)置編譯后存放的目錄 'cache_html' => true, // 是否需要編譯成靜態(tài)的HTML文件 'suffix_cache' => '.html', // 設(shè)置編譯文件的后綴 'cache_time' => 7200, // 多長(zhǎ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(); // 調(diào)試信息 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'; } /** *獲取絕對(duì)路徑 */ 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; } /* 設(shè)置模板引擎參數(shù) */ public function setConfig($key, $value = null) { if (is_array($key)) { $this->config = $key + $this->config; }else { $this->config[$key] = $value; } } /* 獲取當(dāng)前模板引擎配置,僅供調(diào)試使用 */ 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; } /** *注入數(shù)組變量 */ 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']; } /** *判斷是否開啟了緩存 */ 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('找不到對(duì)應(yīng)的模板!'); } $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) { // 開啟緩存的處理邏輯 if ($this->reCache($file) === true) { // 需要更新緩存的處理邏輯 $this->debug['cached'] = 'false'; $this->compileTool = new Compile($this->path(), $compileFile, $this->config); if ($this->needCache()) {ob_start();} // 打開輸出控制緩沖 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(); // 獲取輸出緩沖中的內(nèi)容(在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 '程序運(yùn)行日期:', date('Y-m-d H:i:s'), PHP_EOL; echo '模板解析耗時(shí):', $this->debug['spend'], '秒', PHP_EOL; echo '模板包含標(biāo)簽數(shù)目:', $this->debug['count'], PHP_EOL; echo '是否使用靜態(tài)緩存:', $this->debug['cached'], PHP_EOL; echo '模板引擎實(shí)例參數(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>
測(cè)試用例:
本文內(nèi)容大部分來自于《PHP核心技術(shù)與最佳實(shí)踐》的第六章。
聯(lián)系客服