《PHP Learning》模板引擎

《PHP Learning》模板引擎

  • 模板处理
    • 使用正则处理模板替换规则
    • 保存模板编译结果
  • 模板使用

自定义模板引擎 Demo 代码

模板处理

使用正则处理模板替换规则

定界符

定义模板昨天定界符为<{,右边定界符为}>,对应的变量为$left$right,这两个变量在后续的正则表达式会使用到

/* 将左右定界符号中,有影响正则的特殊符号转义  例如,<{ }>转义\<\{ \}\> */
$left = preg_quote($this->left_delimiter, '/');
$right = preg_quote($this->right_delimiter, '/');

模板文件

模板文件定义了一个主入口文件以及主入口文件包含的多个从属文件,这样的处理方式也是为了能够让模板能够做到模块化,方便开发和维护

模板主入口文件main.html

<{ include "header.html" }>
    
<table border="1" align="center" width="90%" cellpadding="3" cellspacing="0">
    <caption><h1> <{ $tableName }> <h1></caption>
    <tr bgcolor="#cccccc">
        <th>编号</th><th>姓名</th><th>性别</th><th>年龄</th><th>电子邮件</th>
    </tr>
    
    <{ loop $users $user }>
        <tr>    
            <{ loop $user $colKey => $colValue }>
                <{ if $colKey == "sex" }>
                    <{ if $colValue=="男" }>
                        <td bgColor="red"> <{ $colValue }> </td>
                    <{ elseif $colValue=="女" }>
                        <td bgColor="green"> <{ $colValue }> </td>
                    <{ else }>
                        <td bgColor="blue"> 未知 </td>
                    <{ /if }>
                <{ else }>
                    <td> <{ $colValue }> </td>
                <{ /if }>
            <{ /loop }>
        </tr>
    <{ /loop }> 
    
</table>
<center>共查找到<b> <{ $rowNum }> </b>条记录</center>

<{ include 'footer.html' }>

从属文件footer.html

        <hr><center> ############### 作者:<{ $author }> ############## </center>
    </body>
</html>

从属文件header.html

<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title> <{ $title }> </title>
    </head>
    <body>

替换变量

变量匹配规则的正则表达式

/* 1、匹配模板中变量 ,例如,"<{ $var }>"  */
'/' . $left . '\s*\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*' . $right . '/i',

变量匹配规则的处理方法,该方法作为preg_replace_callback正则替换的回调方法

/* 1、替换模板中的变量 <{ $var }> ==> <?php echo $this->tpl_vars["var"]; */
function ($matches) use ($parent) {
    $result = '<?php echo $this->tpl_vars["' . $matches[1] . '"]; ?>';
    return $result;
}

处理结果

// 原始的模板代码
<{ $tableName }>

// 转换后的php代码
<?php echo $this->tpl_vars["tableName"]; ?>

模板文件的处理结果,左边是原始模板的代码,右边是替换之后的PHP语法代码

替换变量

if语句替换

if语句匹配规则的正则表达式

/* 2、匹配模板中if标识符,例如 "<{ if $col == "sex" }> statements <{ /if }>" */
'/' . $left . '\s*if\s*(.+?)\s*' . $right . '(.+)' . $left . '\s*\/if\s*' . $right . '/is',

if语句匹配规则的处理方法,该方法作为preg_replace_callback正则替换的回调方法

/* 2、E 替换模板中的if字符串 "<{ if $col == "sex" }> statements <{ /if }>" ==> <?php if($col == "sex") { ?> <?php } ?> */
function ($matches) use ($parent) {
    $result = $parent->stripvtags('<?php if(' . $matches[1] . ') { ?>', $matches[2] . '<?php } ?>');
    return $result;
}

/**
 * 内部使用的私有方法,用来将条件语句中使用的变量替换为对应的值
 * @param    string $expr 提供模板中条件语句的开始标记
 * @param    string $statement 提供模板中条件语句的结束标记
 * @return    strin                将处理后的条件语句相连后返回
 */
private function stripvtags($expr, $statement = '')
{
    /* 匹配变量的正则 */
    $var_pattern = '/\s*\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*/is';
    /* 将变量替换为值 */
    $expr = preg_replace($var_pattern, '$this->tpl_vars["${1}"]', $expr);
    /* 将开始标记中的引号转义替换 */
    $expr = str_replace("\\\"", "\"", $expr);
    /* 替换语句体和结束标记中的引号 */
    $statement = str_replace("\\\"", "\"", $statement);
    /* 将处理后的条件语句相连后返回 */
    return $expr . $statement;
}

替换的结果如下

// 原始的模板代码
<?php if($colKey == "sex") { ?>

// 转换后的php代码
<?php if($this->tpl_vars["colKey"]== "sex") { ?>
    <{ if $colValue=="男" }>
        <td bgColor="red"> <?php echo $this->tpl_vars["colValue"]; ?> </td>
    <{ elseif $colValue=="女" }>
        <td bgColor="green"> <?php echo $this->tpl_vars["colValue"]; ?> </td>
    <{ else }>
        <td bgColor="blue"> 未知 </td>
    <{ /if }>
<{ else }>
    <td> <?php echo $this->tpl_vars["colValue"]; ?> </td>
<?php } ?>

模板文件的处理结果,左边是原始模板的代码,右边是替换之后的PHP语法代码

if语句替换

else if 语句处理

else if语句匹配规则的正则表达式

/* 3、匹配elseif标识符, 例如 "<{ elseif $col == "sex" }>" */
'/' . $left . '\s*else\s*if\s*(.+?)\s*' . $right . '/is',

else if语句匹配规则的处理方法,该方法作为preg_replace_callback正则替换的回调方法

/* 3、E 替换elseif的字符串  "<{ elseif $col == "sex" }>" ==> <?php } elseif($col == "sex") { ?> */
function ($matches) use ($parent) {
    $result = $parent->stripvtags('<?php } elseif(' . $matches[1] . ') { ?>', "");
    return $result;
}

替换的结果如下

// 原始的模板代码
<{ elseif $colValue=="女" }>

// 转换后的php代码
<?php } elseif($this->tpl_vars["colValue"]=="女") { ?>

模板文件的处理结果,左边是原始模板的代码,右边是替换之后的PHP语法代码

else if 语句处理

else 语句处理

else 语句匹配规则的正则表达式

/* 4、匹配else标识符, 例如 "<{ else }>" */
'/' . $left . '\s*else\s*' . $right . '/is',

else 语句匹配规则的处理方法,该方法作为preg_replace_callback正则替换的回调方法

/* 4、替换else的字符串 "<{ else }>" ==> <?php } else { ?> */
function ($matches) use ($parent) {
    $result = '<?php } else { ?>';
    return $result;
}

替换的结果如下

// 原始的模板代码
<{ else }>

// 转换后的php代码
<?php } else { ?>

模板文件的处理结果,左边是原始模板的代码,右边是替换之后的PHP语法代码

else 语句处理

loop 语句处理

loop 语句匹配规则的正则表达式,loop语句也就是PHP中的foreach语句,有两种语法格式

/* 5、用来匹配模板中的loop标识符,用来遍历数组中的值,  例如 "<{ loop $arrs $value }> <{ /loop}>" */
'/' . $left . '\s*loop\s+\$(\S+)\s+\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*' . $right . '(.+?)' . $left . '\s*\/loop\s*' . $right . '/is',
/* 6、用来遍历数组中的键和值,例如 "<{ loop $arrs $key => $value }> <{ /loop}>"  */
'/' . $left . '\s*loop\s+\$(\S+)\s+\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\s*=>\s*\$(\S+)\s*' . $right . '(.+?)' . $left . '\s*\/loop \s*' . $right . '/is',

loop 语句匹配规则的处理方法,遍历元素的key/Value值是以模板对象中数组属性tpl_vars中的某个值的方式进行保存的,转换规则如下表所示

模板代码 PHP语法
$colKey $this->tpl_vars["colKey"]
$colKey $this->tpl_vars["colKey"]
$colValue $this->tpl_vars["colValue"]
/* 5、以下两条用来替换模板中的loop标识符为foreach格式  "<{ loop $arrs $value }> statement <{ /loop}>"
==> <?php foreach($this->tpl_vars["users"] as $this->tpl_vars["user"]) { ?> statement <?php } ?>*/
function ($matches) use ($parent) {
    $result = '<?php foreach($this->tpl_vars["' . $matches[1] . '"] as $this->tpl_vars["' . $matches[2] . '"]) { ?>' . $matches[3] . '<?php } ?>';
    return $result;
}
/* 6、 "<{ loop $arrs $key => $value }> statement <{ /loop}>"
==> <?php foreach($this->tpl_vars["user"] as $this->tpl_vars["colKey"] => $this->tpl_vars["colValue"]) { ?> statement <?php } ?>*/
function ($matches) use ($parent) {
    $result = '<?php foreach($this->tpl_vars["' . $matches[1] . '"] as $this->tpl_vars["' . $matches[2] . '"] => $this->tpl_vars["' . $matches[3] . '"]) { ?>' . $matches[4] . '<?php } ?>';
    return $result;
}

替换的结果如下

// 原始的模板代码
<{ loop $users $user }>
    <tr>    
        <{ loop $user $colKey => $colValue }>
            <?php if($this->tpl_vars["colKey"]== "sex") { ?>
                <{ if $colValue=="男" }>
                    <td bgColor="red"> <?php echo $this->tpl_vars["colValue"]; ?> </td>
                <?php } elseif($this->tpl_vars["colValue"]=="女") { ?>
                    <td bgColor="green"> <?php echo $this->tpl_vars["colValue"]; ?> </td>
                <?php } else { ?>
                    <td bgColor="blue"> 未知 </td>
                <{ /if }>
            <?php } else { ?>
                <td> <?php echo $this->tpl_vars["colValue"]; ?> </td>
            <?php } ?>
        <{ /loop }>
    </tr>
<{ /loop }> 

// 转换后的php代码
<?php foreach($this->tpl_vars["users"] as $this->tpl_vars["user"]) { ?>
    <tr>    
        <?php foreach($this->tpl_vars["user"] as $this->tpl_vars["colKey"] => $this->tpl_vars["colValue"]) { ?>
            <?php if($this->tpl_vars["colKey"]== "sex") { ?>
                <{ if $colValue=="男" }>
                    <td bgColor="red"> <?php echo $this->tpl_vars["colValue"]; ?> </td>
                <?php } elseif($this->tpl_vars["colValue"]=="女") { ?>
                    <td bgColor="green"> <?php echo $this->tpl_vars["colValue"]; ?> </td>
                <?php } else { ?>
                    <td bgColor="blue"> 未知 </td>
                <{ /if }>
            <?php } else { ?>
                <td> <?php echo $this->tpl_vars["colValue"]; ?> </td>
            <?php } ?>
        <?php } ?>
    </tr>
<?php } ?>  

模板文件的处理结果,左边是原始模板的代码,右边是替换之后的PHP语法代码

  1. foreach 第一种语法格式,只有值的遍历
foreach 第一种语法格式
  1. foreach 第二种语法格式,带有键值对的遍历
foreach 第二种语法格式

include 语句处理

include 语句匹配规则的正则表达式

/* 7、匹配include标识符, 例如,'<{ include "header.html" }>' */
'/' . $left . '\s*include\s+[\"\']?(.+?)[\"\']?\s*' . $right . '/i'

include 语句匹配规则的处理方法,该方法作为preg_replace_callback正则替换的回调方法

/* 7、E 替换include的字符串*/
function ($matches) use ($parent) {
    $result = file_get_contents($this->template_dir . "/" . $matches[1]);
    return $result;
}

替换的结果如下

// 原始的模板代码
<{ include "header.html" }>

// 转换后的PHP代码
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title> <{ $title }> </title>
    </head>
    <body>
// 原始的模板代码
<{ include 'footer.html' }>

// 转换后的PHP代码
        <hr><center> ############### 作者:<{ $author }> ############## </center>
    </body>
</html>

模板文件的处理结果,左边是原始模板的代码,右边是替换之后的PHP语法代码

include 语句处理

最终的结果

上面的步骤之后,通过正则匹配模板语法的定界符来做的判断,如果还有存在模板语法,需要进行递归处理,把所有的模板语法代码转换为PHP语法代码

/* 使用正则替换函数处理 */
$repContent = $this->replaceContent($pattern, $replacementFunctions, $content);
/* 如果还有要替换的标识,递归调用自己再次替换 */
if (preg_match('/' . $left . '([^(' . $right . ')]{1,})' . $right . '/', $repContent)) {
    $repContent = $this->tpl_replace($repContent);
}

以下是两份最终的代码的比较

原始的模板代码

// header.html
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title> <{ $title }> </title>
    </head>
    <body>

// footer.html
        <hr><center> ############### 作者:<{ $author }> ############## </center>
    </body>
</html>


// main.html
<{ include "header.html" }>
    
<table border="1" align="center" width="90%" cellpadding="3" cellspacing="0">
    <caption><h1> <{ $tableName }> <h1></caption>
    <tr bgcolor="#cccccc">
        <th>编号</th><th>姓名</th><th>性别</th><th>年龄</th><th>电子邮件</th>
    </tr>
    
    <{ loop $users $user }>
        <tr>    
            <{ loop $user $colKey => $colValue }>
                <{ if $colKey == "sex" }>
                    <{ if $colValue=="男" }>
                        <td bgColor="red"> <{ $colValue }> </td>
                    <{ elseif $colValue=="女" }>
                        <td bgColor="green"> <{ $colValue }> </td>
                    <{ else }>
                        <td bgColor="blue"> 未知 </td>
                    <{ /if }>
                <{ else }>
                    <td> <{ $colValue }> </td>
                <{ /if }>
            <{ /loop }>
        </tr>
    <{ /loop }> 
    
</table>
<center>共查找到<b> <{ $rowNum }> </b>条记录</center>

<{ include 'footer.html' }>

替换之后的PHP代码

<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <title> <?php echo $this->tpl_vars["title"]; ?> </title>
    </head>
    <body>

    
<table border="1" align="center" width="90%" cellpadding="3" cellspacing="0">
    <caption><h1> <?php echo $this->tpl_vars["tableName"]; ?> <h1></caption>
    <tr bgcolor="#cccccc">
        <th>编号</th><th>姓名</th><th>性别</th><th>年龄</th><th>电子邮件</th>
    </tr>
    
    <?php foreach($this->tpl_vars["users"] as $this->tpl_vars["user"]) { ?>
        <tr>    
            <?php foreach($this->tpl_vars["user"] as $this->tpl_vars["colKey"] => $this->tpl_vars["colValue"]) { ?>
                <?php if($this->tpl_vars["colKey"]== "sex") { ?>
                    <?php if($this->tpl_vars["colValue"]=="男") { ?>
                        <td bgColor="red"> <?php echo $this->tpl_vars["colValue"]; ?> </td>
                    <?php } elseif($this->tpl_vars["colValue"]=="女") { ?>
                        <td bgColor="green"> <?php echo $this->tpl_vars["colValue"]; ?> </td>
                    <?php } else { ?>
                        <td bgColor="blue"> 未知 </td>
                    <?php } ?>
                <?php } else { ?>
                    <td> <?php echo $this->tpl_vars["colValue"]; ?> </td>
                <?php } ?>
            <?php } ?>
        </tr>
    <?php } ?>  
    
</table>
<center>共查找到<b> <?php echo $this->tpl_vars["rowNum"]; ?> </b>条记录</center>

        <hr><center> ############### 作者:<?php echo $this->tpl_vars["author"]; ?> ############## </center>
    </body>
</html>
最终的结果

保存模板编译结果

模板编译是一个耗资源的操作,所以合适的处理方式是对结果进行缓存,因为使用到缓存,所以涉及到缓存的更新替换等问题,这里的处理方式比较简单也就是比较模板编译文件和主入口的模板文件的时间,如果编译文件的时间小于主入口模板文件的时间,也就意味着模板文件存在更新,需要更新编译文件。然后使用include()方法把模板文件的内容输出。相关的代码如下

/* 获取组合的模板文件,该文件中的内容都是被替换过的 */
$comFileName = $this->compile_dir . "/com_" . $fileName . '.php';
/* 判断替换后的文件是否存在或是存在但有改动,都需要重新创建 */
if (!file_exists($comFileName) || filemtime($comFileName) < filemtime($tplFile)) {

    if (DEBUG_CONTENT_CHANGE) {
        $fileName = "./test/result.txt";
        FileUtil::createFile($fileName);
        file_put_contents($fileName, "");
    }

    /* 调用内部替换模板方法 */
    $repContent = $this->tpl_replace(file_get_contents($tplFile));
    /* 保存由系统组合后的脚本文件 */
    FileUtil::createFile($comFileName);
    file_put_contents($comFileName, $repContent);
}
/* 包含处理后的模板文件输出给客户端 */
include($comFileName);

模板使用

使用模板有三个步骤:

  • 1、指定使用的模板
  • 2、给模板使用的数据赋值
  • 3、让模板引擎处理展示逻辑

对应的使用的代码如下:

$tpl=new MyTpl;                                 //创建模板引擎类对象

$tpl->assign("title", "自定义模板引擎示例");    //分配标题变量给头部模板header.tpl
$tpl->assign("tableName", "用户信息表");        //分配表名变量给主模板
$tpl->assign("author", "高洛峰");               //分配作者变量给尾部模板footer.tpl
$tpl->assign("users", $users);                  //分配存有表User的二维数组给主模板
$tpl->assign("rowNum", $stmt->rowCount());      //分配所取的数据行数变量给主模板

$tpl->display("main.html");                     //包括替换模板中的变量输出模板页面
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335