模板引擎的思想是來源於MVC(Model View Controller)模型,即模型層、視圖層、控制器層。
在Web端,模型層為資料庫的操作;視圖層就是模板,也就是Web前端;Controller就是PHP對資料和請求的各種操作。模板引擎就是為了將視圖層和其他層分離開來,使php代碼和html代碼不會混雜在一起。因為當php代碼和html代碼混雜在一起時,將使代碼的可讀性變差,並且代碼後期的維護會變得很困難。
大部分的模板引擎原理都差不多,核心就是利用Regex解析模板,將約定好的特定的標識語句編譯成php語句,然後調用時只需要include編譯後的檔案,這樣就講php語句和html語句分離開來了。甚至可以更進一步將php的輸出輸出到緩衝區,然後將模板編譯成靜態html檔案,這樣請求時,就是直接開啟靜態html檔案,請求速度大大加快。
簡單的自訂模板引擎就是兩個類,第一個是模板類、第二個是編譯類。
首先是編譯類:
class CompileClass { private $template; // 待編譯檔案 private $content; // 需要替換的文本 private $compile_file; // 編譯後的檔案 private $left = '{'; // 左定界符 private $right = '}'; // 右定界符 private $include_file = array(); // 引入的檔案 private $config; // 模板的設定檔 private $T_P = array(); // 需要替換的運算式 private $T_R = array(); // 替換後的字串 public function __construct($template, $compile_file, $config) {} public function compile() { $this->c_include(); $this->c_var(); $this->c_staticFile(); file_put_contents($this->compile_file, $this->content); } // 處理include public function c_include() {} // 處理各種賦值和基本語句 public function c_var() {} // 對靜態JavaScript進行解析 public function c_staticFile() {}}
編譯類的大致結構就是上面那樣,編譯類的工作就是根據配置的檔案,將寫好的模板檔案按照規則解析,替換然後輸出到檔案中。這個檔案的內容是php和html混雜的,但在使用模板引擎進行開發時並不需要在意這個檔案,因為我們要編寫的是模板檔案,也就是html和我們自己定義的標籤混合的一個檔案。這樣View和其他兩層就分離開來了。
在這個自訂模板引擎中,我的左右定界符就是大括弧,具體的解析規則就是放在__construct()中
// 需要替換的Regex$this->T_P[] = "/$this->left\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/";$this->T_P[] = "/$this->left\s*(loop|foreach)\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/";$this->T_P[] = "/$this->left\s*(loop|foreach)\s*\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s+" . "as\s+\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)$this->right/";$this->T_P[] = "/$this->left\s*\/(loop|foreach|if)\s*$this->right/";$this->T_P[] = "/$this->left\s*if(.*?)\s*$this->right/";$this->T_P[] = "/$this->left\s*(else if|elseif)(.*?)\s*$this->right/";$this->T_P[] = "/$this->left\s*else\s*$this->right/";$this->T_P[] = "/$this->left\s*([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\xf7-\xff]*)\s*$this->right/";// 替換後的字串 $this->T_R[] = "<?php echo \$\\1; ?>";$this->T_R[] = "<?php foreach((array)\$\\2 as \$K=>\$V) { ?>";$this->T_R[] = "<?php foreach((array)\$\\2 as &\$\\3) { ?>";$this->T_R[] = "<?php } ?>";$this->T_R[] = "<?php if(\\1) { ?>";$this->T_R[] = "<?php } elseif(\\2) { ?>";$this->T_R[] = "<?php } else { ?>";$this->T_R[] = "<?php echo \$\\1; ?>";
上面的解析規則包含了基本的輸出和一些常用的文法,if、foreach等。利用preg_replace函數就能對模板檔案進行替換。具體情況如下
<!--模板檔案-->{$data}{foreach $vars} {if $V == 1 } <input value="{V}"> {elseif $V == 2} <input value="123123"> {else } <input value="sdfsas是aa"> {/if}{/foreach}{ loop $vars as $var} <input value="{var}">{ /loop } // 解析後<?php echo $data; ?><?php foreach((array)$vars as $K=>$V) { ?> <?php if( $V == 1) { ?> <input value="<?php echo $V; ?>"> <?php } elseif( $V == 2) { ?> <input value="123123"> <?php } else { ?> <input value="sdfsas是aa"> <?php } ?><?php } ?><?php foreach((array)$vars as &$var) { ?> <input value="<?php echo $var; ?>"><?php } ?>
編譯類的工作大致就是這樣,剩下的include和對JavaScript的解析都和這個大同小異。
然後就是模板類
class Template { // 配置數組 private $_arrayConfig = array( 'root' => '', // 檔案根目錄 'suffix' => '.html', // 模板檔案尾碼 'template_dir' => 'templates', // 模板所在檔案夾 'compile_dir' => 'templates_c', // 編譯後存放的檔案夾 'cache_dir' => 'cache', // 靜態html存放地址 'cache_htm' => false, // 是否編譯為靜態html檔案 'suffix_cache' => '.htm', // 設定編譯檔案的尾碼 'cache_time' => 7200, // 自動更新間隔 'php_turn' => true, // 是否支援原生php代碼 'debug' => 'false', ); private $_value = array(); private $_compileTool; // 編譯器 static private $_instance = null; public $file; // 模板檔案名稱 public $debug = array(); // 調試資訊 public function __construct($array_config=array()) {} // 單步設定設定檔 public function setConfig($key, $value=null) {} // 注入單個變數 public function assign($key, $value) {} // 注入陣列變數 public function assignArray($array) {} // 是否開啟緩衝 public function needCache() {} // 如果需要重新編譯檔案 public function reCache() {} // 顯示模板 public function show($file) {} }
整個模板類的工作流程就是先執行個體化模板類對象,然後利用assign和assignArray方法給模板中的變數賦值,然後調用show方法,將模板和設定檔傳入編譯類的執行個體化對象中然後直接include編譯後的php、html混編檔案,顯示輸出。簡單的流程就是這樣,詳細的代碼如下
public function show($file) { $this->file = $file; if(!is_file($this->path())) { exit("找不到對應的模板檔案"); } $compile_file = $this->_arrayConfig['compile_dir']. md5($file). '.php'; $cache_file = $this->_arrayConfig['cache_dir']. md5($file). $this->_arrayConfig['suffix_cache']; // 如果需要重新編譯檔案 if($this->reCache($file) === false) { $this->_compileTool = new CompileClass($this->path(), $compile_file, $this->_arrayConfig); if($this->needCache()) { // 輸出到緩衝區 ob_start(); } // 將賦值的變數匯入當前符號表 extract($this->_value, EXTR_OVERWRITE); if(!is_file($compile_file) or filemtime($compile_file) < filemtime($this->path())) { $this->_compileTool->vars = $this->_value; $this->_compileTool->compile(); include($compile_file); } else { include($compile_file); } // 如果需要編譯成靜態檔案 if($this->needCache() === true) { $message = ob_get_contents(); file_put_contents($cache_file, $message); } } else { readfile($cache_file); }}
在show方法中,我首先判斷模板檔案存在,然後利用MD5編碼產生編譯檔案和快取檔案的檔案名稱。然後就是判斷是否需要進行編譯,判斷的依據是看編譯檔案是否存在和編譯檔案的寫入時間是否小於模板檔案。如果需要編譯,就利用編譯類進行編譯,產生一個php檔案。然後只需要include這個編譯檔案就好了。
為了加快模板的載入,可以將編譯後的檔案輸出到緩衝區中,也就是ob_start()這個函數,所有的輸出將不會輸出到瀏覽器,而是輸出到預設的緩衝區,在利用ob_get_contents()將輸出讀取出來,儲存成靜態html檔案。
具體的使用如下
require('Template.php');$config = array( 'debug' => true, 'cache_htm' => false, 'debug' => true);$tpl = new Template($config);$tpl->assign('data', microtime(true));$tpl->assign('vars', array(1,2,3));$tpl->assign('title', "hhhh");$tpl->show('test');
緩衝後的檔案如下
<!DOCTYPE html><html> <head> <title>hhhh</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> 1466525760.32 <input value="1"> <input value="123123"> <input value="sdfsas是aa"> <input value="1"> <input value="2"> <input value="3"> <script src="123?t=1465898652"></script> </body></html>
一個簡單的自訂模板引擎就完成了,雖然簡陋但是能用,而且重點在於造輪子的樂趣和收穫。
完整代碼可見我的 github
以上就是本文的全部內容,希望對大家的學習有所協助,也希望大家多多支援雲棲社區。