首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 實作 MVC 模板引擎核心解析邏輯

     編輯:PHP 相關知識

MVC 架構下的模板引擎設計思路

模板引擎是 MVC 模式中實現「關注點分離」的關鍵組件。它讓視圖層專注於呈現邏輯,透過自定義標籤語法將資料邏輯抽離,由控制器負責解析標籤並注入動態資料。本文分享一套基於正則表達式的輕量級模板解析實作。

核心設計原則

  • 雙標籤體系:{data ...} 處理單筆資料,{list ...}{/list} 處理列表資料
  • 巢狀支援:解決標籤多層嵌套問題,滿足 80% 以上介面呈現需求
  • 正則驅動:採用 PCRE 正則表達式進行模板解析,效能與彈性兼顧

模板解析類核心程式碼

<?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 等私有方法
?>

關鍵技術點解析

  1. 正則遞迴模式 (?R):支援標籤無限層級巢狀,是解析複雜模板的核心
  2. 緩衝區捕獲輸出:透過 ob_start() / ob_get_contents() 捕獲 PHP 執行結果,避免直接輸出干擾流程
  3. 動態編譯快取:將解析後的 PHP 程式碼寫入暫存檔 include 執行,兼顧效能與動態性
  4. 屬性解析類 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 風格的語法糖,降低學習門檻
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved