php模板引擎的原理與簡(jiǎn)單實(shí)例

模板引擎其實(shí)就是將一個(gè)帶有自定義標(biāo)簽的字符串,通過相應(yīng)的規(guī)則解析罗售,返回php可以解析的字符串辜窑,這其中正則的運(yùn)用是必不可少的,所以要有一定的正則基礎(chǔ)寨躁。
總體思想穆碎,引入按規(guī)則寫好的模板,傳遞給標(biāo)簽解析類(_HtmlTag)進(jìn)行解析职恳,再把解析好的字符串傳遞給php進(jìn)行解析渲染輸出
首先定義了一個(gè)_HtmlTag類:

class _HtmlTag{
  protected $taglist = "if|elseif|else|loop|for|while|=|:=|:e|:html|:";
  protected $string;
  protected $tpldir;
  function __construct($dir, $tpl) {
    $this->findReg = "/\\{[ ]*($this->findReg)(\\s*[\\{\\}]+\\s*)?\\}/i";
    $this->tpldir = $dir;
    $this->tpl =$tpl;
  }
}
  • 先解析下render方法
  • 在render方法中對(duì)傳入的字符串$s所禀,進(jìn)行相應(yīng)的替換渲染,用到了extends方法放钦,import方法色徘,''開頭的各種過濾方法,后面會(huì)一一進(jìn)行詳解.
function render($s) {
  $s = str_replace(['<--{', '}-->'], ['{','}'], $s);
  if (strpos($s, "{extends") !== false) {
    $s = $this->_extends($s);
  } 
  $s = preg_replace('/[\\n\\r]+/s', "\\n", $s);
  $s = preg_replace('/\\r+/s', "", $s);
  $s = preg_replace('/\\t+/s', "\\t", $s);
  
  $this->string = str_replace(["\\{", "\\}"], ["\\HKH1", "\\HKH2"], $s);
  preg_match_all("/\\{[ ]*(import)(\\s*[^\\{\\}]+\\s*)?\\}/i", $s, $d);
  foreach($d[0] as $k => $v) {
    call_user_func_array(array($this, "_import"), array($v, $d[1][$k], $d[2][$k]));
  }
  static $m = array("=" => "_defaultEcho", ":=" =>" _xssEcho", ":e" => "_htmlencodeEcho", ":" => "_echo", ":html" => "_htmldecodeEcho");
  preg_match_all($this->findReg, $this->string, $d);
  foreach ($d[0] as $k => $v) {
    if (isset($m[$d[1][$k]])) {
        $medth = $m[$d[1][$k]];
     } else {
        $medth = '_' . $d[1][$k];
     }
      call_user_func_array(array($this, $medth), array($v, $d[1][$k], $d[2][$k]));
  }
}
  • _extends方法
  • 使用 個(gè)方法是為了判斷是否有繼承相應(yīng)的模板文件最筒,在子文件中用{extends 模板名},一般只要填寫文件名,如index,系統(tǒng)會(huì)自動(dòng)去構(gòu)造完整的文件路徑蔚叨,一般在上一級(jí)目錄床蜘,這個(gè)在realTpl()方法時(shí)會(huì)有詳解
function _extends($tpl) {
  preg_match("/\\{\\s*extends\\s+([^\\{\\}]+?)\\s*\\}/i", $tpl, $d);
  list($search, $arg) = $d;
  if (stripos($arg, ".") !==0 ) {
    $arg = '../' . $arg;
  }
  $file = $this->tpl->me->realTpl($arg . ".htm");
  $basetpl = file_get_contents($file);
  preg_match_all("/\\{block\\s+([^\\{\\}]+?)\\s*\\}(.+)\\{\\/block\\}/s", $tpl, $ds);
  foreach($ds[1] as $n => $name) {
    $basetpl = preg_replace("/\\{block\\s+name={$name}\\s*\\}/", $ds[2][$n], $basetpl);
  }
  $basetpl = preg_replace("/\\{block\\s+name=.+?\\}/", ' ', $basetpl);
  return $basetpl;
}
  • _import 方法
  • _import 方法是用來對(duì){import 文件名}標(biāo)簽進(jìn)行解析,默認(rèn)也是上層目錄蔑水,會(huì)把該文件的內(nèi)容解析到當(dāng)前標(biāo)簽位置,同時(shí)支持使用:函數(shù)名|參數(shù)1, 參數(shù)2,...的方式進(jìn)行字符串的回調(diào).這中的display方法后面會(huì)有詳解
function _import ($search, $tag, $arg) {
  $arg = trim($arg);
  if (stripos($arg, ".") !== 0) {
    $arg = '../' . $arg;
  }
  $file = $this->tpl->me->realTpl($arg . ".htm");
  if (file_exists($file)) {
    $this->string = str_replace($search, file_get_contents($file), $this->string);
  return;
  } else {
      if (stripos($file, "$") !== false) {
          $this->string = str_replace($search, '<? include $this->tpl->display("' . $arg . '"); ?>', $this->string);
           return;
      }
      if (stripos($arg, '|') !== false) {
         list($func, $tmp) = explode("|", trim($arg));
         $kw = explode(', ', $tmp);
      } else {
         $func = trim($arg);
          $kw = array();  
      }
      if (function_exists($func) {
        $tpl_str = call_user_func_array($func, $kw);
         $this->string = str_replace($search, $tpl_str, $this->string);
          return;
      } else {
        $this->string = str_replace($search, $arg, $this->string);
      return;
      }
      system_error($this->tpldir . trim($arg) . ".htm不存在");
  }
}
  • 現(xiàn)在來看一個(gè)標(biāo)簽函數(shù)_loop,將標(biāo)簽{loop $data $k $v}或{loop $data $v}替換邢锯,實(shí)現(xiàn)模板循環(huán),其中的parseAttr方法接下來將詳解
function _loop($search, $tag, $arg) {
  list($attr, $arg) = $this->parseAttr($arg);
  $d = preg_split("/\\s+/", trim($arg));
  if (count($d) == 3) {
     $data = $d[0];
     $k = $d[1];
     $v = $d[2];
     $s = "<? \\n if(!empty($data)) {\\n \\t foreach(" . $data . " as {$k} => {$v}){";
  } elseif (count($d) == 2) {
    $data = $d[0];
    $v = $d[1];
    $s = "<? \\n if(!empty($data)){\\n \\t
        foreach(". $datsa . " as {$v}) {";
  }

  if (isset($attr['counter'])) {
    $s = "\\n \\t\\t<? if (!isset({$attr['counter']})) { {$attr['counter']} = 0;} ?> \\n \\t\\t" . $s . "\\n \\t\\t{$attr['counter']}++; \\n ?>;
  } else {
    $s .= "?>";
  }
  $this->string = str_replace($search, $s, $this->string);
  $this->string = str_replace("{/loop}", "<? \\t}\\n}?>", $this->string);
}
  • parseAttr方法

該方法用來解析標(biāo)簽 的參數(shù)搀别,返回的第一個(gè)參數(shù)是值形式字符串丹擎,第二個(gè)是鍵值數(shù)組,如{loop $data $k $v}返回$data $k $v 組成 的字符串和一個(gè)空數(shù)組,{if $k = $v}則返回空字符串和和數(shù)組[$k => $v];

function parseAttr($s) {
  $reg = '/([a-zA-Z0-9_])+\\s*=\\s*([a-zA-Z0-9_\\$]+)/i';
  $arg = preg_replace($reg, '', $s);
  preg_match_all($reg, $s, $d);
  $arr = array();
  foreach($d[1] as $key => $value) {
    $arr[trim($value)] = trim($d[2][$key]);
  }
  return array($arr, $arg);
}
  • _if 方法
  • 將{if}{/if}標(biāo)簽解析成原生 的php蒂培,簡(jiǎn)單明了再愈,不多解釋
function _if ($search, $tag, $arg) {
  $replace[0] = "<? if($arg){?>";
  $replace[1] = "<? }?>";
  $this->string = str_replace(array($search, "{/$tag}"), $replace, $this->string);
}
  • _else方法
    將{else}解析成原生的php
function _else($search, $tag, $arg) {
  $this->string = str_replace("{else}", "\\n" . '<? } else {?>', $this->string);
}
  • _elseif方法
    將{elseif}解析成原生的php
function _elseif($search, $tag, $arg) {
  $this->string = str_replace($search, "<? }elseif($arg){?>", $this->string);
}
  • _for 方法
function _for($search, $tag, $arg) {
  $s = trim(trim(preg_replace('/\\s+/', ' ; ', $arg)), ';');
  $this->string = str_replace($search, "<? for($s){?>", $this->string);
  $this->string = str_replace("{/for}", "<? \\t\\n}?>", $this->string);
}
  • _while方法
function _while($search, $tag, $arg) {
  $this->string = str_replace($search , "<? while($aeg){ ?>", $this->string);
  $this->string = str_replace("{/while}", "<? \\t\\n} ?>", $this->string);
}
  • _end方法
    通過這個(gè)方法對(duì)其$this->string進(jìn)行修改可以對(duì)變量進(jìn)行如下標(biāo)簽定義:
    {$val}{$arr[$key]}{$arr['key']}{:val()}{$arr.key}{$arr.$key}
function _end() {
  //{$val}標(biāo)簽解析 
  $this->string = preg_replace('/\\{(\\$\\w+.*)\\}/is', "<?=\\\\1;?>", $this->string);
   //{:$val()}標(biāo)簽解析
  $this->string = preg_replace('/\\{\\:\\s*(\\$?\\w+.*?)\\}/is', "<? =\\\\1;?>", $this->string);
  //將$val[arg]解析成$val['arg']
  $this->string = preg_replace('/\\$([_a-z]+\\w*)\\[([_a-z]+\\w*)\\]/is', "$\\\\1['\\\\2']", $this->string);
  //支持點(diǎn)號(hào)訪問數(shù)組。如array['key']可以用array.key訪問
  $this->string = preg_replace('/\\$([_a-z]+\\w*)\\.(\\$[_a-z]+\\w*)/is', "$\\\\1[\\\\2]", $this->string);
  $this->string = preg_replace('/\\$([_a-z]+\\w*)\\.([_a-z]+\\w*)/is', "$\\\\1['\\\\2']", $this->string);
  $this->string = preg_replace(array("/<\?/", "/<?\php\s*=/"), array("<?php ", "<?php echo "), $this->string);
}
  • _result方法,做最后處理护戳,返回字符串翎冲,_end方法在上面已經(jīng)解釋過了
function result() {
  $this->_end();
  $this->_baseParse($this->stirng);
  return str_replace(array("\\HKH1", "\\HKH2"), array("\\{", "\\}"), $this->string);
}

下面再來介紹一下幾個(gè)過濾的方法

  • _htmlencodeEcho方法
  • 可以用此種形式對(duì)變量進(jìn)行htmlspecialchars式的過濾spspecialchars函數(shù)后面會(huì)有介紹{:e $val}
function _htmlencodeEcho($search, $tag, $arg) {
  if (stripos($arg, '$')) {
    $this->string = str_replace($search, "<? echo sphtmlspecialchars(!empty($arg)?$arg:''))?>", $this->string);
  } else {
    $this->string = str_replace($search, "<? echo sphtmlspecialchars($arg)?>", $this->string);
  }
}
  • _htmldecodeEcho方法
_htmldecodeEcho($search, $tag, $arg) {
  if (stripos($arg, '$')) {
    $this->string = str_replace($search, "<? echo htmlspecialchars_decode(!empty($arg)?$arg:'')?>", $this->string);
  } else {
    $this->string = str_replace($search, "<? echo htmlspecialchars_decode($arg)?>", $this->string);
  }
}
  • _defaultEcho方法
function _defaultEcho($search, $tag, $arg) {
  if (stripos($arg, ',') == false) {
    $arg . = ", ' ' ";
    list($v, $default) = explode(",", $arg);
    $this->string = str_replace($search, "<? echo empty($v)?$default:$v; ?>", $this->string);
  }
}
  • _xssEcho方法
_xssEcho($search, $tag, $arg) {
  $this->string = str_replace($search, "<? echo xssRemove($arg); ?>", $this->string);
}

_echo方法

function _echo($search, $tag, $arg){
  $this->string = str_replace($search, "<? echo $arg;?>", $this->string);
}
  • _baseParse方法,這個(gè)對(duì)css和js的引入進(jìn)行處理
function _baseParse($s) {
  if (strpos($s, "{loadCss") !== false) {
    $s = $this->_loadCss($s);
  }
  if (strpos($s, "{loadJs") !== false) {
    $s = $this->_loadJs($s);
  }
  return $s;
}
  • _loadJs方法
function _loadJs($tpl) {
  preg_match_all('/\{loadJs\s+([^\{\}]+?)\s*\}/i', $tpl, $match);
  include "jsmin.php";
  foreach($match[1] as $k => $js) {
    $jsstr = "";
    $cdn = "";
    $if (stripos($js, ',') !== false) {
        $filename = "";
        $md5 = "";
        $js_content = "";
        foreach(explode(',', $js) as $j) {
          $filename .= basename($j, '.js') . ',';
          $md5 .= self::fileMd5($j);
          if (stripos($j, '.min.') == false) {
            $js_content .= JSMin::minify(file_get_contents(WEB_ROOT . $j));
          } else {
            $js_content .= file_get_contents(WEB_ROOT . $j);
          }
          $js_content .= ';'
        }
      $md5 = md5($md5);
      $filename = dirname($j) . '/' .trim($filename, ',') . '.js';
      self::parseJs($js_content, $filename);
      $jsstr = "<script src=\"{$cdn}{$filename}?v={$md5}\"></script>";
    } else {
      $md5 = self::fileMd5($js);
      if (stripos($js, ".min.") === false) {
        $js = self::pareJs(JSMin::minify(file_get_contents(WEB_ROOT . $js)), dirname($js). '/' .basename($js, '.js') . '.min.js');
      }
      $jsstr = "<script src=\"{$cdn}{$js}?v={$mdt}\"></script>";
    }
    $tpl = str_replace($match[0][$k], $jsstr, $tpl);
  }
  return $tpl;
}

_loadCss方法

function _loadCss($tpl) {
  preg_match_all('/\{loadCss\s+([^\{\}]+)\s*\}/i' , $tpl , $match);
  $cdn = "";
  foreach($match[1] as $k => $css) {
    $cssstr = "";
    if (stripos($css, ',') !== false) {
      $filename = "";
      $md5 = "";
      $css_content = "";
       foreach($explode(',', $css) as $css) {
           $filename .= basename($css, '.css') . ',';
           $md5 = self::fileMd5($css);
            $css_content .= file_get_contents(WEB_ROOT .$css);   
       }
       $md5 = md5($md5);
       $filename = dirname($css) . '/' .trim($filename, ',') . '.css';
        self::parseCss($css_content, $filename);
         $cssstr = "<link rel=\"stylesheet\" href=\"{$cdn}{$filename}?v={md5}\">";
    } else {
      $md5 = self::fileMd5($css);
       if (stripos($css, ".min.") == false) {
          $css = self::parseCss(file_get_contents(WEB_ROOT . $css), dirname($css) . '/' .basename($css, '.css') . '.min.css');
        }
        $cssstr .= "<link rel=\"stylesheet\" href=\"{$cdn}{$css}?v={$md5}\">";
    }
    $tpl = str_replace($match[0][$k], $cssstr, $tpl);
  }
  return $tpl;
}
  • 上面兩個(gè)函數(shù)都用到了各自的parseCss,和parseJs,讓我們來看下
static function parseCss($css_content, $filename) {
  $css_content = preg_replace("/[\r\n\t]/", '', $css_content);
  $css_content = preg_replace("/ +/", ' ', $css_content);
  sp_file_pu_contents(WEB_ROOT . $filename, $css_content);
  return $filename;
}
static function parseJs($js, $filename) {
  sp_file_put_content(WEB_ROOT . $filename, $js);
  return $filename;
}

上面的_html類和他的方法對(duì)傳入的字符串進(jìn)行了標(biāo)簽到原生 Php的替換媳荒,可以傳入相應(yīng)的模板抗悍,也可以在其中自定義自己的過濾方法。

下面我們?cè)賮砜匆粋€(gè)template類:

  • 定義這個(gè)類來對(duì)模板進(jìn)行管理钳枕,這里會(huì)用到上面的_html類缴渊,基本定義如下,接下來會(huì)介紹其中的一些方法
class template {
  private $tpl_cache_dir;
  public $ext = ".htm";
  public $default_tpl_dir = false;
  public $me;

  function _construct($tpl_cache_dir, $me) {
    $this->sets($tpl_cache_dir);
    $this->me = $me;
  }
  function sets($tpl_cache_dir) {
    $this->tpl_cache_dir = $tpl_cache_dir;
    if (SP_DEBUG == 1) {
      spmkdir($this->tpl_cache_dir);
    }
  }
  function default_template($dir) {
    $this->default_tpl_dir = $dir;
  }
}
  • getString方法

在之前 開啟ob_start(),然后獲取這之間的ob數(shù)據(jù)鱼炒,試用于框架中

function getString() {
  $s = ob_get_contents();
  ob_clean();
  return $s;
}
  • display方法
function display($tpl, $return = 0, $script = SCRIPT) {
  $tpl .= $this->ext;
  $tpl_name = $this->tpl_cache_dir . $script . '_' . str_replace(['/', '\\\\'], '_', $tpl);
  if (SP_DEBUG){
    if (SP_DEBUG == 1 || (SP_DEBUG == 2 && !file_exists($tpl_name))) {
      $this->compiles($tpl_name, $tpl);
    }
  }
  return $tpl_name;
}
  • compiles方法, 在這里使用了sp_file_put_contentsb函數(shù)會(huì)在后面介紹
function compiles($cache_name, $tpl) {
  if (!file_exists($tpl)) {
    if (file_exists($this->default_tpl_dir . $tpl)) {
      $html = file_get_contents($this->default_tpl_dir . $tpl);
    } else {
      spmkdir(dirname($tpl), '777');
      file_get_contents($tpl, 這個(gè)是自動(dòng)生成模板');
    }
  } else {
    $html = file_get_contents($tpl);
  }
  $tag = new _HtmlTag(realpath(dirname($tpl) . '/../'), $this);
  $tag->render($html);
  $html = preg_replace_callback('//s', array(&$this, "make_magic_func"), $tag->result());
  $html = preg_replace('/[\\n\\r]{1,}/s', "\\n", $html);
  @unlink($cache_name);
  sp_file_put_contents($cache_name, $thml);
}
  • make_magic_func方法
function make_magic_func($d) {
  preg_match_all('/(\\w+)=\\s*(\\'|\\")([^\\'"]+)?\\2/m', $d[2], $dd);
  $aa = array();
  foreach($dd[1] as $i => $k) {
    $vs = preg_replace('/([^\\-]*[<>]{1,1})\\s*(\\S+)?\\s*/', '\\1\\'\\2\\' ', $dd[3][$i]);
    $vs = preg_replace('/=\\s*(\\S+)?\\s*/', '=\\'\\1\\'', $dd[3][$i]);
    if (stripos($d[3][$i], '$') !== false) {
      $vs = preg_replace('/\\$([^\\']+)\\s*/', '{$\\1}', $vs);
     }
  }
}

注意

  • 兩個(gè)類的相應(yīng)方法都 定義好了衔沼,這其中用到的函數(shù)sp_file_pu_contents($file, $data)
function sp_file_put_contents($file, $data) {
    $dir = dirname($file);
    if (is_writeable($dir)) {
        return file_put_contents($file, $data);
    } else {
        throw new writeFail(sprintf("寫入文件%s,失敗,目錄不可寫", $file), 503);
    }
}

js壓縮類: jsmin.class.php
鏈接:http://pan.baidu.com/s/1kUBx0X5 密碼:y5jb
將這兩個(gè) 類定義在一個(gè)文件中田柔,使用進(jìn)進(jìn)行相應(yīng)的配置

define("WEB_ROOT", dirname(__FILE__) . '/');

使用

  • 文件準(zhǔn)備
    base.htm,main.htm,test.js,test1.js,test.css,test1.css,indx.php,im.htm

  • main.htm:

{import ./im}
{block name=head}
{block name=body}
{block name=foot}
  • base.htm
{extends ./main}
{block head}
{loadCss test.css,test1.css}
{loop $data $k $v}
<p>
我是頭部
</p>
{/loop}
{if $bb == 'aa'}
<p>
我是if 測(cè)試
</p>
{elseif $bb = 'bb'}
<p>
我是elesif測(cè)試
</p>
{else}
<p>
我是eles測(cè)試
</p>
{/if}
{for $i=0 $i<10 $i++}
<p>
我是for循環(huán)
</p>
{/for}
{while $con >5}
<? $con--;?>
<p>
我是while循環(huán)
</p>
{/while}
{/block}
{block foot}
<p>
我是尾部
</p>
{loadJs test1.js,test.js}
{/block}
  • im.htm
我是import進(jìn)來的
  • index.php
<?php
define('WEB_ROOT',dirname(__FILE__) . '/');
function sp_file_put_contents($file, $data) {
    $dir = dirname($file);
    if (is_writeable($dir)) {
        return file_put_contents($file, $data);
    } else {
        throw new writeFail(sprintf("寫入文件%s,失敗俐巴,目錄不可寫", $file), 503);
    }
}
class _HtmlTag{
    protected $taglist = "if|elseif|else|loop|for|while|=|:=|:e|:html|:";
    protected $string;
    protected $tpldir;
    public $findReg;
    function __construct($dir, $tpl) {
        $this->findReg = "/\{[ ]*($this->taglist)(\s*[^\{\}]+\s*)?\}/i";
        $this->tpldir = $dir;
        $this->tpl =$tpl;
    }
    function render($s) {
        $s = str_replace(['<--{', '}-->'], ['{','}'], $s);
        if (strpos($s, "{extends") !== false) {
            $s = $this->_extends($s);
        } 
        $s = preg_replace('/[\n\r]+/s', "\n", $s);
        $s = preg_replace('/\r+/s', "", $s);
        $s = preg_replace('/\t+/s', "\t", $s);
        $this->string = str_replace(["\{", "\}"], ["\HKH1", "\HKH2"], $s);
        preg_match_all("/\{[ ]*(import)(\s*[^\{\}]+\s*)?\}/i", $s, $d);
        foreach($d[0] as $k => $v) {
            call_user_func_array(array($this, "_import"), array($v, $d[1][$k], $d[2][$k]));
        }
        static $m = array("=" => "_defaultEcho", ":=" =>" _xssEcho", ":e" => "_htmlencodeEcho", ":" => "_echo", ":html" => "_htmldecodeEcho");
        preg_match_all($this->findReg, $this->string, $d);
        foreach ($d[0] as $k => $v) {
            if (isset($m[$d[1][$k]])) {
                $medth = $m[$d[1][$k]];
            } else {
                $medth = '_' . $d[1][$k];
            }
            call_user_func_array(array($this, $medth), array($v, $d[1][$k], $d[2][$k]));
        }
    }
    function _extends($tpl) {
        preg_match("/\{\s*extends\s+([^\{\}]+?)\s*\}/i", $tpl, $d);
        list($search, $arg) = $d;
        if (stripos($arg, ".") !==0 ) {
            $arg = '../' . $arg;
        }
        //$file = $this->tpl->me->realTpl($arg . ".htm");
        $file = $arg . ".htm";
        $basetpl = file_get_contents($file);
        preg_match_all("/\{block\s+([^\{\}]+?)\s*\}(.+?)\{\/block\}/s", $tpl, $ds);
        foreach($ds[1] as $n => $name) {
            $basetpl = preg_replace("/\{block\s+name={$name}\s*\}/", $ds[2][$n], $basetpl);
        }
        $basetpl = preg_replace("/\{block\s+name=.+?\}/", ' ', $basetpl);

        return $basetpl;
    }
    function _import ($search, $tag, $arg) {

        $arg = trim($arg);
        if (stripos($arg, ".") !== 0) {
            $arg = '../' . $arg;
        }
        //$file = $this->tpl->me->realTpl($arg . ".htm");
        $file = $arg . ".htm";
        if (file_exists($file)) {
            $this->string = str_replace($search, file_get_contents($file), $this->string);
            return;
        } else {
            if (stripos($file, "$") !== false) {
            $this->string = str_replace($search, '<? include $this->tpl->display("' . $arg . '"); ?>', $this->string);
            return;
        }
        if (stripos($arg, '|') !== false) {
            list($func, $tmp) = explode("|", trim($arg));
            $kw = explode(', ', $tmp);
        } else {
            $func = trim($arg);
            $kw = array();  
        }
        if (function_exists($func)) {
            $tpl_str = call_user_func_array($func, $kw);
            $this->string = str_replace($search, $tpl_str, $this->string);
            return;
        } else {
            $this->string = str_replace($search, $arg, $this->string);
            return;
        }
            system_error($this->tpldir . trim($arg) . ".htm不存在");
        }
    }
    
    function mytest(){
        return $this->string;
    }


    function _baseParse($s) {
        if (strpos($s, "{loadCss") !== false) {
            $s = $this->_loadCss($s);
        }
        if (strpos($s, "{loadJs") !== false) {
            $s = $this->_loadJs($s);
        }
        return $s;
    }

    function fileMd5($f) {
        if (stripos($f, "http://") !== false) {
            return md5($f);
        }
            return md5_file(WEB_ROOT . $f);
    }

    function _loadCss($tpl) {
        preg_match_all('/\{loadCss\s+([^\{\}]+)\s*\}/i' , $tpl , $match);
        $cdn = "";
        foreach($match[1] as $k => $css) {
            $cssstr = "";
            if (stripos($css, ',') !== false) {
                $filename = "";
                $md5 = "";
                $css_content = "";
                foreach(explode(',', $css) as $css) {
                    $filename .= basename($css, '.css') . ',';
                    $md5 = self::fileMd5($css);
                    $css_content .= file_get_contents(WEB_ROOT .$css);   
                }
                $md5 = md5($md5);
                $filename = dirname($css) . '/' . trim($filename, ',') . '.css';
                self::parseCss($css_content, $filename);
                $cssstr = "<link rel=\"stylesheet\" href=\"{$cdn}{$filename}?v={$md5}\">";
                } else {
                    $md5 = self::fileMd5($css);
                    if (stripos($css, ".min.") == false) {
                        $css = self::parseCss(file_get_contents(WEB_ROOT . $css), dirname($css) . '/' .basename($css, '.css') . '.min.css');
                    }
                    $cssstr .= "<link rel=\"stylesheet\" href=\"{$cdn}{$css}?v={$md5}\">";
            }
            $tpl = str_replace($match[0][$k], $cssstr, $tpl);
        }
        return $tpl;
    }   


    function _loadJs($tpl) {

        preg_match_all('/\{loadJs\s+([^\{\}]+?)\s*\}/i', $tpl, $match);
        //var_dump($match);die;
        include "jsmin.class.php";
        foreach($match[1] as $k => $js) {
            $jsstr = "";
            $cdn = "";
            if (stripos($js, ',') !== false) {
                $filename = "";
                $md5 = "";
                $js_content = "";
                foreach(explode(',', $js) as $j) {
                    $filename .= basename($j, '.js') . ',';
                    $md5 .= self::fileMd5($j);
                    if (stripos($j, '.min.') === false) {
                        $js_content .= JSMin::minify(file_get_contents(WEB_ROOT . $j));
                    } else {
                        $js_content .= file_get_contents(WEB_ROOT . $j);
                    }
                    $js_content .= ';';
                }
                $md5 = md5($md5);
        
                $filename = dirname($js) . '/' .trim($filename, ',') . '.js';
                self::parseJs($js_content, $filename);
                $jsstr = "<script src=\"{$cdn}{$filename}?v={$md5}\"></script>";
            } else {
                $md5 = self::fileMd5($js);
                if (stripos($js, ".min.") === false) {
                    $js = self::pareJs(JSMin::minify(file_get_contents(WEB_ROOT . $js)), dirname($js). '/' .basename($js, '.js') . '.min.js');
                }
                $jsstr = "<script src=\"{$cdn}{$js}?v={$mdt}\"></script>";
            }
            $tpl = str_replace($match[0][$k], $jsstr, $tpl);
        }
        return $tpl;
    }

    static function parseCss($css_content, $filenme) {

        $css_content = preg_replace("/[\r\n\t]/", '', $css_content);
        $css_content = preg_replace("/ +/", ' ', $css_content);
        // echo WEB_ROOT . $filenme;

        sp_file_put_contents(WEB_ROOT . $filenme, $css_content);

        return $filenme;
    }

    static function parseJs($js, $filenme) {

        sp_file_put_contents(WEB_ROOT . $filenme, $js);

        return $filenme;

    }

    function _loop($search, $tag, $arg) {
        list($attr, $arg) = $this->parseAttr($arg);
        $d = preg_split("/\s+/", trim($arg));
        if (count($d) == 3) {
            $data = $d[0];
            $k = $d[1];
            $v = $d[2];
            $s = "<? \n if(!empty($data)) {\n \t foreach(" . $data . " as {$k} => {$v}){";
        } elseif (count($d) == 2) {
            $data = $d[0];
            $v = $d[1];
            $s = "<? \n if(!empty($data)){\n \t foreach(". $datsa . " as {$v}) {";
        }

        if (isset($attr['counter'])) {
            $s = "\n\t\t <? if (!isset({$attr['counter']})) { {$attr['counter']} = 0;} ?> \n \t\t" . $s . "\n \t\t{$attr['counter']}++; \n ?>";
            } else {
            $s .= "?>";
        }
        $this->string = str_replace($search, $s, $this->string);
        $this->string = str_replace("{/loop}", "<? \t}\n}?>", $this->string);
    }
    function parseAttr($s) {
        $reg = '/([a-zA-Z0-9_])+\s*=\s*([a-zA-Z0-9_\$]+)/i';
        $arg = preg_replace($reg, '', $s);
        preg_match_all($reg, $s, $d);
        $arr = array();
        foreach($d[1] as $key => $value) {
            $arr[trim($value)] = trim($d[2][$key]);
        }
        return array($arr, $arg);
    }
    function _if ($search, $tag, $arg) {
        $replace[0] = "<? if($arg){?>";
        $replace[1] = "<? }?>";
        $this->string = str_replace(array($search, "{/$tag}"), $replace, $this->string);
    }
    function _else($search, $tag, $arg) {
        $this->string = str_replace("{else}", "\n" . '<? } else {?>', $this->string);
    }
    function _elseif($search, $tag, $arg) {
     $this->string = str_replace($search, "<? }elseif($arg){?>", $this->string);
    }

    function _for($search, $tag, $arg) {
      $s = trim(trim(preg_replace('/\s+/', ' ; ', $arg)), ';');
      $this->string = str_replace($search, "<? for($s){?>", $this->string);
      $this->string = str_replace("{/for}", "<? \t\n}?>", $this->string);
    }
    function _while($search, $tag, $arg) {
      $this->string = str_replace($search , "<? while($arg){ ?>", $this->string);
      $this->string = str_replace("{/while}", "<? \t\n} ?>", $this->string);
    }
    function _end() {
        //{$val}標(biāo)簽解析 
        $this->string = preg_replace('/\\{(\\$\\w+.*)\\}/is', "<?=\\\\1;?>", $this->string);
        //{:$val()}標(biāo)簽解析
        $this->string = preg_replace('/\\{\\:\\s*(\\$?\\w+.*?)\\}/is', "<? =\\\\1;?>", $this->string);
        //將$val[arg]解析成$val['arg']
        $this->string = preg_replace('/\\$([_a-z]+\\w*)\\[([_a-z]+\\w*)\\]/is', "$\\\\1['\\\\2']", $this->string);
        //支持點(diǎn)號(hào)訪問數(shù)組。如array['key']可以用array.key訪問
        $this->string = preg_replace('/\\$([_a-z]+\\w*)\\.(\\$[_a-z]+\\w*)/is', "$\\\\1[\\\\2]", $this->string);
        $this->string = preg_replace('/\\$([_a-z]+\\w*)\\.([_a-z]+\\w*)/is', "$\\\\1['\\\\2']", $this->string);
        $this->sring = preg_replace(array("/<\?/", "/<\?php\s*=/"), array("<?php ", "<?php echo "), $this->string);
    }
    function result() {
        $this->_end();
        $this->string = $this->_baseParse($this->string);
        return str_replace(array("\\HKH1", "\\HKH2"), array("\\{", "\\}"), $this->string);
    }
}
$data = ['aa','bb','cc'];
$bb = 'aa';
$con = 11;
$tag = new _HtmlTag('./index', '');
$str = file_get_contents('base.htm');
$tag->render($str);
$html = $tag->result();
var_dump($html);
echo '</br>----------------------------</br>';
sp_file_put_contents("./index.html", $html);
include "index.html";
  • 運(yùn)行index.php查看結(jié)果


    Paste_Image.png

    Paste_Image.png

總結(jié)

我們來總結(jié)一下這個(gè)類的使用過程硬爆,先實(shí)例話template類欣舵,調(diào)用template::display()方法,其中會(huì)調(diào)用template::compiles()方法缀磕,這個(gè)方法會(huì)實(shí)例化_HhtmlTag類缘圈,調(diào)用_HtmlTag::render方法,在這個(gè)方法里袜蚕,對(duì)傳入的帶字符串進(jìn)行一個(gè)結(jié)構(gòu)的完整化糟把,包括調(diào)用_HtmlTag::_extends,_HtmlTag::_import,_HtmlTag::_loop,_HtmlTag::_if,_HtmlTag::elseif,_HtmlTag::else,_HtmlTag::_for,_HtmlTag::while接著調(diào)用_HtmlTag::result方法牲剃,里面調(diào)用_HtmlTag::_end方法對(duì)變量進(jìn)行php原生化和_HtmlTag::_baseParse()引入css和js遣疯,最后返回解析好的完整模板字符串。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凿傅,一起剝皮案震驚了整個(gè)濱河市缠犀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聪舒,老刑警劉巖辨液,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異箱残,居然都是意外死亡滔迈,警方通過查閱死者的電腦和手機(jī)止吁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來燎悍,“玉大人敬惦,你說我怎么就攤上這事〖浜” “怎么了仁热?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)勾哩。 經(jīng)常有香客問我抗蠢,道長(zhǎng),這世上最難降的妖魔是什么思劳? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任迅矛,我火速辦了婚禮,結(jié)果婚禮上潜叛,老公的妹妹穿的比我還像新娘秽褒。我一直安慰自己,他們只是感情好威兜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布销斟。 她就那樣靜靜地躺著,像睡著了一般椒舵。 火紅的嫁衣襯著肌膚如雪蚂踊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天笔宿,我揣著相機(jī)與錄音犁钟,去河邊找鬼。 笑死泼橘,一個(gè)胖子當(dāng)著我的面吹牛涝动,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炬灭,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼醋粟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了重归?” 一聲冷哼從身側(cè)響起米愿,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎提前,沒想到半個(gè)月后吗货,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泳唠,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狈网,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拓哺。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡勇垛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出士鸥,到底是詐尸還是另有隱情闲孤,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布烤礁,位于F島的核電站讼积,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏脚仔。R本人自食惡果不足惜勤众,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲤脏。 院中可真熱鬧们颜,春花似錦、人聲如沸猎醇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)硫嘶。三九已至阻问,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間音半,已是汗流浹背则拷。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曹鸠,地道東北人煌茬。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像彻桃,于是被迫代替她去往敵國(guó)和親坛善。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理邻眷,服務(wù)發(fā)現(xiàn)眠屎,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)肆饶。 張土汪:刷leetcod...
    土汪閱讀 12,724評(píng)論 0 33
  • <?php /** * 常用函數(shù)庫(kù) * */ class Core_Fun { /** * 對(duì)變量進(jìn)行反...
    尋夢(mèng)xunm閱讀 502評(píng)論 0 0
  • 那時(shí)候我可以肆無忌憚的告訴你們我多么愛財(cái)改衩,以后的以后,我要爭(zhēng)很多很多的錢……可如今我再也不敢對(duì)別人說驯镊,身邊...
    YOUNAS閱讀 292評(píng)論 0 0
  • (一) 不曾忘記,卻曾后悔橄镜。 我心心念念偎快,難以忘懷的那個(gè)人,是我的初戀洽胶。我們感情純得就像六月的荷花晒夹。直到后來我嫁為...
    見玉閱讀 257評(píng)論 5 4