1. 程式人生 > >自定義php模板引擎

自定義php模板引擎

    模板引擎的思想是來源於MVC(Model View Controller)模型,即模型層、檢視層、控制器層。

    在Web端,模型層為資料庫的操作;檢視層就是模板,也就是Web前端;Controller就是PHP對資料和請求的各種操作。模板引擎就是為了將檢視層和其他層分離開來,使php程式碼和html程式碼不會混雜在一起。因為當php程式碼和html程式碼混雜在一起時,將使程式碼的可讀性變差,並且程式碼後期的維護會變得很困難。

    大部分的模板引擎原理都差不多,核心就是利用正則表示式解析模板,將約定好的特定的標識語句編譯成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()中

// 需要替換的正則表示式
$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>

一個簡單的自定義模板引擎就完成了,雖然簡陋但是能用,而且重點在於造輪子的樂趣和收穫。

歡迎訪問我的個人部落格