上一章節(jié),我們講述了ppf是如何工作的(鏈接),這一章節(jié),我們主要講解下ppf中的模版引擎的原理以及實現(xiàn),我們先來認(rèn)識下模版引擎是什么
什么是模版引擎
在現(xiàn)代的web編程中,MVC模式已成為主流,為了讓前后端更好的分工協(xié)作,模版引擎就是作為視圖層跟模型層分離的解決辦法,所以我們可以從早期的混編過度到現(xiàn)在的模版引擎來實現(xiàn)模版引擎的實現(xiàn)情況
早期的動態(tài)頁面事這樣表示的:
<html>
<head>
<title><?php echo $result;?></title>
</head>
<body>
<?php if ($max == 4) {?>
<?php echo 111;?>
<?php }?>
</body>
</html>
然后我們目標(biāo)的模版引擎結(jié)果應(yīng)該像這樣
<html>
<head>
<title>{$result}</title>
</head>
<body>
{if $max == 4}
111
{/if}
</body>
</html>
模版引擎工作流程:
既然有了目標(biāo),那么實現(xiàn)的方法就自然而然得出來了。模版引擎的流程應(yīng)該是這樣:
- 首先通過正則表達(dá)式將{$result} 替換成<?php echo $result;?>
- 實現(xiàn)變量的賦值與注入
- 定義好變量后,include這個替換后的php文件,使用ob_start()打開輸出緩沖區(qū),捕獲文件內(nèi)容,變量注入,然后將其另存為html頁面
- 這個時候的html就是最終的視圖頁
搭建基礎(chǔ)的模版引擎框架
1.我們先來定義一個編譯類Compile.php,我們完成以下工作,簡單的正則匹配替換,然后傳入源文件替換成編譯后的文件
<?php
/**
* 編譯類 Compile
* 正則匹配式可拓展 可設(shè)定多個編譯模版
*
*/
class Compile
{
public $config = array(
'compiledir' => 'Cache/', //設(shè)置編譯后存放的目錄
'suffix_cache' => '.htm', //設(shè)置編譯文件的后綴
);
public $value = array();
public $compare_pattern = array();
public $compare_destpattern = array();
public $compare_include_pattern = "";
public function __CONSTRUCT()
{
//添加include 模版
$this->compare_pattern[] = '#\{include (.*?)\}#';
//簡單的key value賦值
$this->compare_pattern[] = '#\{\\$(.*?)\}#';
//if條件語句實現(xiàn)
$this->compare_pattern[] = '#\{if (.*?)\}#';
$this->compare_pattern[] = '#\{elseif(.*?)\}#';
$this->compare_pattern[] = '#\{else(.*?)\}#';
$this->compare_pattern[] = '#\{/if\}#';
//foreach實現(xiàn)
$this->compare_pattern[] = '#\{foreach name=\\$(.*?) item=(.*?) value=(.*?)\}#';
$this->compare_pattern[] = '#\{\\$(.*?)\}#';
$this->compare_pattern[] = '#\{/foreach\}#';
//支持原生php語言實現(xiàn)
$this->compare_pattern[] = '#\{php (.*?)\}#';
$this->compare_pattern[] = '#\{/php\}#';
$this->compare_pattern[] = '#\{compile_include (.*?)\}#';
//以下是上面幾個模版編譯后的php語言實現(xiàn)
$this->compare_destpattern[] = "<?php include PPF_PATH.'/'.\$this->config['compiledir'].".Dispath::$current_module.".'/'.md5('".Dispath::$current_controller.'_'.Dispath::$current_action.'_'."\\1').'.php'; ?>";
$this->compare_destpattern[] = "<?php echo $\\1;?>";
$this->compare_destpattern[] = "<?php if(\\1){ ?>";
$this->compare_destpattern[] = "<?php }else if(\\1){ ?>";
$this->compare_destpattern[] = "<?php }else{ ?>";
$this->compare_destpattern[] = "<?php }?>";
$this->compare_destpattern[] = "<?php foreach(\$\\1 as \$\\2 => \$\\3){?>";
$this->compare_destpattern[] = '<?php echo $\\1; ?>';
$this->compare_destpattern[] = "<?php } ?>";
$this->compare_destpattern[] = "<?php \\1 ";
$this->compare_destpattern[] = "?>";
$this->compare_destpattern[] = '<?php include "'.APPLICATION_PATH.'/'.Dispath::$current_module.'/View/'.'\\1";?>';
$this->compare_include_pattern = '#\{include (.*?)\}#';
}
/**
* 基本的編譯功能實現(xiàn) 講視圖文件通過正則匹配編譯并寫入到php文件中
*
*/
public function compile($pre_compile_file,$dest_compile_file)
{
$compile_content = preg_replace($this->compare_pattern,$this->compare_destpattern,file_get_contents($pre_compile_file));
file_put_contents($dest_compile_file,$compile_content);
}
}
?>
這個類很簡單,但是涵蓋了大多數(shù)的模版語言(包括include,key-value賦值,if,foreach,原聲方法),具體可以參考 ppf手冊-模版引擎中的使用方法
這個類中有一個方法compile 需要傳遞2個變量的,一個是帶編譯的文件,一個是編譯后的文件,所以我們需要創(chuàng)建一個方法來給這個方法傳值。
我們可以回到Controller 也就是控制器,我們仿造smarty一樣,我們可以寫出類似以下的語法
public function addAction() {
$fruit = array("loving"=>'banana',"hating"=>'apple',"no_sense"=>'orange');
$this->view->assign("fruit",$fruit);
$this->view->assign("result","hello");
$this->view->show();
}
以上應(yīng)該有2個步驟,一個是assign 也就是變量的賦值
第二個也就是show方法,其中實現(xiàn)的邏輯肯定是模版的編譯以及變量的注入,再到最后頁面的展示過程,ppf中這2個方法是通過$this->view來傳遞的,所以view這個類必須有這2個方法或者繼承父類的方法
view.php
<?php
/**
* 視圖類 繼承于 Template類
*
*/
class View extends Template
{
}
?>
這里面啥也不寫,就是單純的繼承Template類,而這個類才是模版引擎的核心
Template類構(gòu)筑的思想
基礎(chǔ)方法:
- 申明assign,show這2個方法
- 要在構(gòu)造函數(shù)的時候?qū)ompile類的實例化,并調(diào)用compile方法完成模版渲染,
高級方法:
- 設(shè)置編譯策略(目標(biāo)是判斷是否需要編譯或者是直接使用緩存文件)
- 給Controller提供是否需要編譯的方法(默認(rèn)是都需要進(jìn)行編譯的)
好的,我們一步步實現(xiàn)這些功能
1.assign方法
/**
* 將變量賦值到$this->vaule中
* @param $key
* @param $value
*/
public function assign($key, $value) {
$this->value[$key] = $value;
}
2.show方法
/**
* 視圖跳轉(zhuǎn)方法(包含了模版引擎,模版編譯轉(zhuǎn)化功能)
* @param $file 視圖跳轉(zhuǎn)文件
*
*/
public function show($file = null) {
/**
* 將例如assign("test","aaa") 轉(zhuǎn)化成 $test = 'aaa';
* 所以這塊是有2個賦值情況 一個是$test = 'aaa' 另一個是 $this->value['test'] = 'aaa';
* 這里設(shè)定 可以支持多維數(shù)組傳遞賦值
* @param string $file 視圖文件
*/
foreach ($this->value as $key => $val) {
$$key = $val;
}
$current_module = Dispath::$current_module;
$current_controller = Dispath::$current_controller;
$compile_file_path = PPF_PATH . '/' . $this->config['compiledir'] . $current_module . '/';
/**
* 如果未指定視圖名稱則默認(rèn)跳至該current_action的名稱
* 在這塊定義視圖地址,編譯php文件地址,緩存htm文件地址
*/
if (!$file) {
$current_action = Dispath::$current_action;
$html_file = APPLICATION_PATH . '/' . $current_module . '/View/' . $current_controller . '/' . $current_action . '.html';
$compile_file = $compile_file_path . md5($current_controller . '_' . $current_action) . '.php';
$cache_file = $compile_file_path . md5($current_controller . '_' . $current_action) . $this->config['suffix_cache'];
} else {
$html_file = APPLICATION_PATH . '/' . $current_module . '/View/' . $current_controller . '/' . $file . '.html';
$compile_file = $compile_file_path . md5($current_controller . '_' . $file) . '.php';
$cache_file = $compile_file_path . md5($current_controller . '_' . $file) . $this->config['suffix_cache'];
}
/**
* 如果存在視圖文件html_file 則繼續(xù)根據(jù)條件編譯,否則跳至/Index/view/Notfound/index.html
*/
if (is_file($html_file)) {
/**
* 對compile_file_path進(jìn)行是否為路徑的判斷 如果不是 則進(jìn)行創(chuàng)建并賦予755的權(quán)限
*/
if (!is_dir($compile_file_path)) {
mkdir($compile_file_path);
//chmod($compile_file_path, 0755);
}
/**
* 這3行代碼是將Controller.php文件某一方法例如:$this->assign("add",'test');
* 將這個以鍵值對的方式傳給在__CONSTRUCT實例化的Compile類中,并通過compile方法進(jìn)行翻譯成php文件
* 最后ob_start()方法需要 include $compile_file;
*/
if ($this->cache_strategy($html_file, $compile_file)) {
$this->compile->value = $this->value;
/**
* @desc 這里是先編譯include部分的內(nèi)容,然后在全部編譯完畢
*/
ob_start();
$this->compile->match_include_file($html_file);
$this->compile->compile($html_file, $compile_file);
include "$compile_file";
/**
* 這塊是得到輸出緩沖區(qū)的內(nèi)容并將其寫入緩存文件$cache_file中,同時將編譯文件跟緩存文件進(jìn)行賦予755權(quán)限
* 這時可以去看看Cache下面會有2個文件 一個是php文件 一個是htm文件 htm文件就是翻譯成html語言的緩存文件
*/
$message = ob_get_contents();
/**
if(file_exists($compile_file)) {
chmod($compile_file, 0777);
}
if(file_exists($cache_file)) {
chmod($cache_file, 0777);
}
*/
$file_line = file_put_contents($cache_file, $message);
ob_end_flush();
} else {
include "$cache_file";
}
} else {
include APPLICATION_PATH . '/Index/View/Notfound/index.html';
}
}
上述的方法中最關(guān)鍵的地方在于如果在視圖文件中含有include的方法,例如{include 'Public/header.html'},這是包含公共header.html文件,而往往這些文件也是需要變量賦值的,那也就是說include文件也是需要編譯的,所以我們需要實現(xiàn)編譯好include文件,然后再編譯body體
我們需要在Compile類中還要定義一個方法,用來編譯include這個文件
/**
* @desc 這塊內(nèi)容是先將include編譯,然后生成在對應(yīng)cache目錄下的php文件,
* 將數(shù)組回傳給Template.php然后在Template.php進(jìn)行編譯
*
*/
public function match_include_file($file) {
$matchArr = array();
$match_file = preg_match_all($this->compare_include_pattern,file_get_contents($file),$matchArr);
if($match_file && !empty($matchArr[1])) {
$include_file_arr = array();
foreach($matchArr[1] as $key => $val) {
$compile_file_path = $this->get_compile_file_path();
$destpatternFile = APPLICATION_PATH."/".Dispath::$current_module."/View/".$val;
$compile_content = preg_replace($this->compare_pattern,$this->compare_destpattern,file_get_contents($destpatternFile));
$compile_file = $compile_file_path . md5(Dispath::$current_controller . '_' . Dispath::$current_action.'_'.$val) . '.php';
file_put_contents($compile_file,$compile_content);
$include_file_arr[] = $compile_file;
}
return $include_file_arr;
}else {
return false;
}
}
就是正則匹配include模版,然后遍歷所有include進(jìn)來的文件,將其寫入緩存文件中,返回這些文件名
3.這里需要使用緩存策略方法cache_strategy()
/**
* 緩存策略
* 根據(jù)need_compile 是否需要重新編譯
* 以及當(dāng)前時間比該文件編譯時間是否大于自動更新cache_time時間
* 以上2點來決定是需要再次編譯還是直接使用緩存文件
* @param string $html_file 視圖文件
* @param string $compile_file 編譯文件
* @return bool $default_status 是否需要重新編譯
*/
public function cache_strategy($html_file, $compile_file) {
$default_status = false;
if(file_exists($compile_file)) {
$compile_file_time = filemtime($compile_file);
}
$time_minus = time() - $compile_file_time;
if (($this->config['need_compile']) || ($time_minus > $this->config['cache_time']) || filemtime($compile_file) < filemtime($html_file)) {
$default_status = true;
} else {
$default_status = false;
}
return $default_status;
}
4.給Controller提供是否需要編譯的方法(默認(rèn)是都需要進(jìn)行編譯的)
/**
* 設(shè)置是否緩存(給控制器調(diào)用)
*
*/
public function set_compile($value) {
if(is_bool($value)) {
$this->config['need_compile'] = $value;
}else {
throw new FrameException("設(shè)置緩存編譯參數(shù)不正確",0);
}
}
這里需要傳遞bool值,如果傳的不是bool值的話,會拋出框架異常,這個異常是在Controller中定義的.后續(xù)會講到如何申明異常方法
以上過程就是所有的模版編譯過程的思路跟實現(xiàn)方式,可以從這里獲取到ppf最新文章,大家可以仔細(xì)參考 ppf-github 中的Compile.php 以及Template.php這2個文件,
以及查看 ppf手冊-模版引擎 的相關(guān)內(nèi)容,一定會有些許收獲