模板引擎是 MVC 模式中實現「關注點分離」的關鍵組件。它讓視圖層專注於呈現邏輯,透過自定義標籤語法將資料邏輯抽離,由控制器負責解析標籤並注入動態資料。本文分享一套基於正則表達式的輕量級模板解析實作。
{data ...} 處理單筆資料,{list ...}{/list} 處理列表資料<?php
class Template {
public $html, $vars, $bTag, $eTag;
public $bFlag = '{', $eFlag = '}', $pfix = 'zmm:';
private $folder, $file;
function __construct($vars = array()) {
!empty($vars) && $this->vars = $vars;
!empty($GLOBALS['cfg_tag_prefix']) && $this->pfix = $GLOBALS['cfg_tag_prefix'] . ':';
$this->bTag = $this->bFlag . $this->pfix;
$this->eTag = $this->bFlag . '\/' . $this->pfix;
empty(Tags::$vars) && Tags::$vars = &$this->vars;
}
public function LoadTpl($tpl) {
$this->file = $this->GetTplPath($tpl);
Tags::$file = &$this->file;
if (is_file($this->file)) {
if ($this->GetTplHtml()) {
$this->SetTplTags();
} else {
exit('模板文件加載失敗!');
}
} else {
exit('模板文件[' . $this->file . ']不存在!');
}
}
private function GetTplPath($tpl) {
$this->folder = WEBSITE_DIRROOT . $GLOBALS['cfg_tpl_root'];
return $this->folder . '/' . $tpl;
}
private function GetTplHtml() {
$html = self::FmtTplHtml(file_get_contents($this->file));
if (!empty($html)) {
$callFunc = Tags::$prefix . 'Syntax';
$this->html = Tags::$callFunc($html, new Template());
} else {
exit('模板文件內容為空!');
}
return true;
}
static public function FmtTplHtml($html) {
return preg_replace('/(\r)|(\n)|(\t)|(\s{2,})/is', '', $html);
}
public function Register($vars = array()) {
if (is_array($vars)) {
$this->vars = $vars;
Tags::$vars = &$this->vars;
}
}
public function Display($bool = false, $name = "", $time = 0) {
if (!empty($this->html)) {
if ($bool && !empty($name)) {
if (!is_int($time)) $time = 600;
$cache = new Cache($time);
$cache->Set($name, $this->html);
}
echo $this->html;
flush();
} else {
exit('模板文件內容為空!');
}
}
private function SetTplTags() {
$this->SetPanelTags(); // 單標籤 {tag .../}
$this->SetTrunkTags(); // 雙標籤 {tag ...}...{/tag}
$this->RegHatchVars(); // 遞迴處理巢狀標籤
}
// ... 完整程式碼包含 SetPanelTags、SetTrunkTags、TransTag 等方法
?>
<?php
class Tags {
static private $attrs = null;
static public $file, $vars, $rule, $prefix = 'TAG_';
// 語法預處理:將模板標籤轉換為 PHP 控制結構
static public function TAG_Syntax($html, $that) {
// 處理 if/elseif/else/loop/php 等控制標籤
$html = preg_replace('/' . $that->bTag . 'if\s+([^' . $that->eFlag . ']+)\s*' . $that->eFlag . '/ism', '<?php if (\1) { ?>', $html);
$html = preg_replace('/' . $that->bTag . 'elseif\s+([^' . $that->eFlag . ']+)\s*' . $that->eFlag . '/ism', '<?php } elseif (\1) { ?>', $html);
$html = preg_replace('/' . $that->bTag . 'else\s*' . $that->eFlag . '/ism', '<?php } else { ?>', $html);
$html = preg_replace('/' . $that->bTag . 'loop\s+(\S+)\s+(\S+)\s*' . $that->eFlag . '/ism', '<?php foreach (\1 as \2) { ?>', $html);
$html = preg_replace('/' . $that->bTag . 'loop\s+(\S+)\s+(\S+)\s+(\S+)\s*' . $that->eFlag . '/ism', '<?php foreach (\1 as \2 => \3) { ?>', $html);
$html = preg_replace('/' . $that->eTag . '(if|loop)\s*' . $that->eFlag . '/ism', '<?php } ?>', $html);
$html = preg_replace('/' . $that->bTag . 'php\s*' . $that->eFlag . '((?:(?!' . $that->bTag . ')[\S\s]*?|(?R))*)' . $that->eTag . 'php\s*' . $that->eFlag . '/ism', '<?php \1 ?>', $html);
return self::TAG_Execute($html);
}
// 列表標籤處理:支援巢狀、分頁、欄位格式化
static public function TAG_List($attr, $html) { /* ... */ }
static public function TAG_Data($attr, $html) { /* ... */ }
// 執行編譯後的 PHP 程式碼並捕獲輸出
static public function TAG_Execute($html) {
ob_clean(); ob_start();
if (!empty(self::$vars)) {
is_array(self::$vars) && extract(self::$vars, EXTR_OVERWRITE);
}
$file_inc = WEBSITE_DIRINC . '/buffer/' . md5(uniqid(rand(), true)) . '.php';
if ($fp = fopen($file_inc, 'xb')) {
fwrite($fp, $html);
fclose($fp);
include($file_inc);
$html = ob_get_contents();
}
ob_end_clean();
@unlink($file_inc);
return $html;
}
// ... 完整程式碼包含 TAG_GetData、TAG_DealTag、TAG_HaveTag 等私有方法
?>
(?R):支援標籤無限層級巢狀,是解析複雜模板的核心ob_start() / ob_get_contents() 捕獲 PHP 執行結果,避免直接輸出干擾流程include 執行,兼顧效能與動態性Attbt:將標籤屬性字串(如 source=table row=10)解析為陣列供 SQL 組裝使用// 模板檔案 list.tpl
<ul>
{zmm:list source="article" row="10" order="id desc"}
<li><a href="{$url}">{$title}</a></li>
{/zmm:list}
</ul>
// 控制器呼叫
$tpl = new Template(array('site_name' => '我的網站'));
$tpl->LoadTpl('list.tpl');
$tpl->Display();
Smarty 或 Blade 風格的語法糖,降低學習門檻