《PHP Learning》模板引擎
- 模板处理
- 使用正则处理模板替换规则
- 保存模板编译结果
- 模板使用
模板处理
使用正则处理模板替换规则
定界符
定义模板昨天定界符为<{
,右边定界符为}>
,对应的变量为$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语法代码
- 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"); //包括替换模板中的变量输出模板页面
网友评论