仿Smarty替换模板标签时遇到的问题

  最近正在做一个微型的仿TP框架,当然以鄙人之技术只能略仿表层,于是遇到的问题层出不穷。今天做到View层替换模板部分,本以为一下子搞掂的事,果不其然又是败下阵来。

好了,来重点。

模板文件 test1.tpl

{foreach from=$phone key=k item=v name=phones}
    <tr>
        <td>{$smarty.foreach.phones.iteration}</td>
        <td>{$v.brand}</td>
        <td>{$v.ver}</td>
        <td>{$v.conf}</td><td>{$v.price|test:20}</td>
        <td>{$v.date}</td></tr>
{/foreach}

而Smarty将以上代码替换为:

<?php
$_from = $_smarty_tpl->tpl_vars['phone']->value;
if (!is_array($_from) && !is_object($_from))
{
    settype($_from, 'array');
}
$__foreach_phones_1_saved = isset($_smarty_tpl->tpl_vars['__smarty_foreach_phones']) ? $_smarty_tpl->tpl_vars['__smarty_foreach_phones'] : false;
$__foreach_phones_1_saved_item = isset($_smarty_tpl->tpl_vars['v']) ? $_smarty_tpl->tpl_vars['v'] : false;
$__foreach_phones_1_saved_key = isset($_smarty_tpl->tpl_vars['k']) ? $_smarty_tpl->tpl_vars['k'] : false;
$_smarty_tpl->tpl_vars['v'] = new Smarty_Variable();
$_smarty_tpl->tpl_vars['__smarty_foreach_phones'] = new Smarty_Variable(array('iteration' => 0));
$_smarty_tpl->tpl_vars['k'] = new Smarty_Variable();
$_smarty_tpl->tpl_vars['v']->_loop = false;
foreach ($_from as $_smarty_tpl->tpl_vars['k']->value => $_smarty_tpl->tpl_vars['v']->value) 
{
    $_smarty_tpl->tpl_vars['v']->_loop = true;
    $_smarty_tpl->tpl_vars['__smarty_foreach_phones']->value['iteration']++;
    $__foreach_phones_1_saved_local_item = $_smarty_tpl->tpl_vars['v'];
?>
<tr>
        <td><?php echo (isset($_smarty_tpl->tpl_vars['__smarty_foreach_phones']->value['iteration']) ? $_smarty_tpl->tpl_vars['__smarty_foreach_phones']->value['iteration'] : null);?></td>
        <td><?php echo $_smarty_tpl->tpl_vars['v']->value['brand'];?></td>
        <td><?php echo $_smarty_tpl->tpl_vars['v']->value['ver'];?></td>
        <td><?php echo $_smarty_tpl->tpl_vars['v']->value['conf'];?></td>
        <td><?php echo smarty_modifier_test($_smarty_tpl->tpl_vars['v']->value['price'],20);?></td>
        <td><?php echo $_smarty_tpl->tpl_vars['v']->value['date'];?></td>
<?php
    $_smarty_tpl->tpl_vars['vv'] = $__foreach_v_2_saved_local_item;
}
if ($__foreach_v_2_saved_item) 
{
    $_smarty_tpl->tpl_vars['vv'] = $__foreach_v_2_saved_item;
}
if ($__foreach_v_2_saved_key) 
{
    $_smarty_tpl->tpl_vars['vk'] = $__foreach_v_2_saved_key;
}
?>
</tr>

这替换的代码也怪吓人的,其实分析也就三个部分:

  好了,问题来了,由于foreach标签的特殊性,{/foreach}结束标签替换后第三部分不是单单的?>php脚本结束标签,而是有第一部分声明而来的变量代码,因此直接替换标签不可行。于是想到用正则解决。

  搞了一个下午,一直有个困扰问题,在很多情况下都不可能是单单一个foreach,其内部有可能有嵌套不止一个foreach循环,以我目前认知水平,用正则直接匹配替换显然不可行。

  于是想到先用正则匹配出所有的{foreach}{/foreach},【1】匹配替换过程中将匹配的标签加入到一个临时数组中,以该数组下标和一个MD5值组成的混合值替换模板中该标签所在的位置作标识占位。【2】排列好临时数组中的foreach闭合标签后,【3】将排列好的foreach内数值进行匹配替换成上图代码的样子,【4】再根据该标签所在的下标替换回编译文件中的标识占位即可。

  【1】这里用到PHP一个强大的(我认为)正则替换函数preg_replace_callback《执行一个正则表达式搜索并且使用一个回调进行替换》

//模拟Smarty内部处理模板替换
$l_delimiter
='{'; $r_delimiter='}'; $_foreach=md5('foreach'); $_foreach_count=0; $_foreach_arr=array(); $reg_foreach="/\$l_delimiters*".'/?s*foreach[^\'.$r_delimiter.']*\'.$r_delimiter.'/i'; $content=preg_replace_callback($reg_foreach,'_replace_foreach', $content); function _replace_foreach($match) { global $_foreach_arr,$_foreach,$_foreach_count; $_foreach_arr[]=$match[0]; $rt=$_foreach.$_foreach_count.'_'; $foreach_count++; return $rt; } echo '<pre>'; print_r($foreach_arr); echo '</pre>';

原模板foreach部分代码:

 1 {foreach from=$phone key=k item=v name=phones}
 2     <tr>
 3         <td>{$smarty.foreach.phones.iteration}</td>
 4         <td>{$v.brand}</td>
 5         <td>{$v.ver}</td>
 6         <td>{$v.conf}</td>
 7         {foreach from=v key=vk item=vv name=v}//这是用于测试标签嵌套,实际不可能如此
 8         <td>{$v.price|test:20}</td>
 9         <td>{$v.date}</td>
10         {/foreach}//这是用于测试标签嵌套,实际不可能如此
11     </tr>
12     {/foreach}
13 
14     {foreach $phone as $v}
15     <tr>
16         <td>{$smarty.foreach.phones.iteration}</td>
17         <td>{$v.brand}</td>
18         <td>{$v.ver}</td>
19         <td>{$v.conf}</td>
20         <td>{$v.price|test:20}</td>
21         <td>{$v.date}</td>
22     </tr>
23     {/foreach}
24 
25     {foreach $phone as $k=>$v name=phonename}
26     <tr>
27         <td>{$smarty.foreach.phones.iteration}</td>
28         <td>{$v.brand}</td>
29         <td>{$v.ver}</td>
30         <td>{$v.conf}</td>
31         <td>{$v.price|test:20}</td>
32         <td>{$v.date}</td>
33     </tr>
34     {/foreach}
View Code

模板替换后的编译文件(只看替换foreach的部分)代码:

 1 1dfae1db802b03f8a8debd7a9a1cb34b0_
 2     <tr>
 3         <td><?php echo $_tpl_data["_smarty_sys_reserved_value"]["foreach"]["phones"]["iteration"]; ?></td>
 4         <td><?php echo $_tpl_data["v"]["brand"]; ?></td>
 5         <td><?php echo $_tpl_data["v"]["ver"]; ?></td>
 6         <td><?php echo $_tpl_data["v"]["conf"]; ?></td>
 7         1dfae1db802b03f8a8debd7a9a1cb34b1_
 8         <td><?php echo test($_tpl_data["v"]["price"],20); ?></td>
 9         <td><?php echo $_tpl_data["v"]["date"]; ?></td>
10         1dfae1db802b03f8a8debd7a9a1cb34b2_
11     </tr>
12     1dfae1db802b03f8a8debd7a9a1cb34b3_
13 
14     1dfae1db802b03f8a8debd7a9a1cb34b4_
15     <tr>
16         <td><?php echo $_tpl_data["_smarty_sys_reserved_value"]["foreach"]["phones"]["iteration"]; ?></td>
17         <td><?php echo $_tpl_data["v"]["brand"]; ?></td>
18         <td><?php echo $_tpl_data["v"]["ver"]; ?></td>
19         <td><?php echo $_tpl_data["v"]["conf"]; ?></td>
20         <td><?php echo test($_tpl_data["v"]["price"],20); ?></td>
21         <td><?php echo $_tpl_data["v"]["date"]; ?></td>
22     </tr>
23     1dfae1db802b03f8a8debd7a9a1cb34b5_
24 
25     1dfae1db802b03f8a8debd7a9a1cb34b6_
26     <tr>
27         <td><?php echo $_tpl_data["_smarty_sys_reserved_value"]["foreach"]["phones"]["iteration"]; ?></td>
28         <td><?php echo $_tpl_data["v"]["brand"]; ?></td>
29         <td><?php echo $_tpl_data["v"]["ver"]; ?></td>
30         <td><?php echo $_tpl_data["v"]["conf"]; ?></td>
31         <td><?php echo test($_tpl_data["v"]["price"],20); ?></td>
32         <td><?php echo $_tpl_data["v"]["date"]; ?></td>
33     </tr>
34     1dfae1db802b03f8a8debd7a9a1cb34b7_
View Code

替换后可见原本foreach标签所在的位置已经被一串md5码+数字+下划线替代。

打印显示:

Array
(
    [0] => {foreach from=$phone key=k item=v name=phones}
    [1] => {foreach from=v key=vk item=vv name=v}
    [2] => {/foreach}
    [3] => {/foreach}
    [4] => {foreach $phone as $v}
    [5] => {/foreach}
    [6] => {foreach $phone as $k=>$v name=phonename}
    [7] => {/foreach}
)

拿到了存储foreach的临时数组$foreach_arr,即可以【2】排列数组中的foreach闭合标签。

刚开始想到用递归,孰知递归及算法乃鄙人之短板,折腾了一晚,终究还是笨方法,使用foreach

以上面数组排列,正确应该是0->3;1->2;4->5;6->7;。思考良久,有一个思路。一个数组存头标签$a_start,每当遇到非{/foreach}的标签,则加入到$a_start数组中,一旦遇到{/foreach}标签,立刻将$a_start最后一个数据取出,加入到$a_result数组中。

具体实现如下:

 1 function foreach_sort($arr)
 2 {
 3     $a_start=array();
 4     $a_result=array();
 5     for($i=0;$i<count($arr);$i++)
 6     {
 7         preg_match('/{s*/foreachs*}/i',$arr[$i],$match);
 8         if(!$match)//如果是开头
 9         {
10             $a_start[]=$i;
11         }
12         else
13         {    
14             $a_result[]=array_pop($a_start).'->'.$i;
15         }
16     }
17     return $a_result;
18 }
19 
20 echo '<pre>';
21 print_r(foreach_sort($foreach_arr));
22 echo '</pre>';

打印显示:

Array
(
    [0] => 1->2
    [1] => 0->3
    [2] => 4->5
    [3] => 6->7
)

排列OK。

【3】将排列好的$foreach_arr内数值进行匹配替换。

未完,明天继续...

原文地址:https://www.cnblogs.com/GaZeon/p/5290249.html