帮朋友用ThinkPHP写一个网站,最近就碰到ThinkPHP模板不能多层继承的问题,就跟这里讲的问题一样:http://www.thinkphp.cn/topic/26853.html
比如base.html是这样:
- ...base内容1...
- <block name="content"></block>
- ...base内容2...
- <extend name="base" />
- <block name="content">
- ....a内容1...
- <block name="b_content"></block>
- ....a内容2...
- </block>
现在b.html想继承a.html
- <extend name="a" />
- <block name="b_content">
- ...b内容1...
- </block>
- <extend name="base" />
- ....a内容1...
- ...b内容1...
- ....a内容2...
它并没有解析a.html中的extend标签,悲剧啊,它不支持,我就看看能不能让它支持,研究了两天,终于搞定,写出来分享一下:
模板解析的实现代码文件是:ThinkPHP/Library/Think/Template.class.php,在这个文件里面主要关注两个函数parseExtend和replaceBlock,从函数名可以看出这两个函数分别是解析extend标签和替换block标签:
// 解析模板中的extend标签
protected function parseExtend($content) {
$begin = $this->config['taglib_begin'];
$end = $this->config['taglib_end'];
// 读取模板中的继承标签
$find = preg_match('/'.$begin.'extend\s(.+?)\s*?\/'.$end.'/is',$content,$matches);
if($find) {
//替换extend标签
$content = str_replace($matches[0],'',$content);
// 记录页面中的block标签
preg_replace_callback('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/is', array($this, 'parseBlock'),$content);
// 读取继承模板
$array = $this->parseXmlAttrs($matches[1]);
$content = $this->parseTemplateName($array['name']);
$content = $this->parseInclude($content, false); //对继承模板中的include进行分析
// 替换block标签
$content = $this->replaceBlock($content);
}else{
$content = preg_replace_callback('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/is', function($match){return stripslashes($match[2]);}, $content);
}
return $content;
}
//...
/**
* 替换继承模板中的block标签
* @access private
* @param string $content 模板内容
* @return string
*/
private function replaceBlock($content){
static $parse = 0;
$begin = $this->config['taglib_begin'];
$end = $this->config['taglib_end'];
$reg = '/('.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.')(.*?)'.$begin.'\/block'.$end.'/is';
if(is_string($content)){
do{
$content = preg_replace_callback($reg, array($this, 'replaceBlock'), $content);
} while ($parse && $parse--);
return $content;
} elseif(is_array($content)){
if(preg_match('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'/is', $content[3])){ //存在嵌套,进一步解析
$parse = 1;
$content[3] = preg_replace_callback($reg, array($this, 'replaceBlock'), "{$content[3]}{$begin}/block{$end}");
return $content[1] . $content[3];
} else {
$name = $content[2];
$content = $content[3];
$content = isset($this->block[$name]) ? $this->block[$name] : $content;
return $content;
}
}
}
从parseExtend函数可以看出它真的没有支持多层继承,那就加上一个递归让它支持:
// 解析模板中的extend标签
protected function parseExtend($content) {
$begin = $this->config['taglib_begin'];
$end = $this->config['taglib_end'];
// 读取模板中的继承标签
$find = preg_match('/'.$begin.'extend\s(.+?)\s*?\/'.$end.'/is',$content,$matches);
if($find) {
//替换extend标签
$content = str_replace($matches[0],'',$content);
// 记录页面中的block标签
preg_replace_callback('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/is', array($this, 'parseBlock'),$content);
// 读取继承模板
$array = $this->parseXmlAttrs($matches[1]);
$content = $this->parseTemplateName($array['name']);
$content = $this->parseInclude($content, false); //对继承模板中的include进行分析
// 替换block标签
$content = $this->replaceBlock($content);
// 递归解析,支持多层继承
$content = $this->parseExtend($content);
}else{
$content = preg_replace_callback('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/is', function($match){return stripslashes($match[2]);}, $content);
}
return $content;
}
这样做之后,之前的例子输出如下:
- ...base内容1...
-
- ...base内容2...
/**
* 替换继承模板中的block标签
* @access private
* @param string $content 模板内容
* @return string
*/
private function replaceBlock($content){
static $parse = 0;
$begin = $this->config['taglib_begin'];
$end = $this->config['taglib_end'];
$reg = '/('.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.')(.*?)'.$begin.'\/block'.$end.'/is';
if(is_string($content)){
do{
$content = preg_replace_callback($reg, array($this, 'replaceBlock'), $content);
} while ($parse && $parse--);
return $content;
} elseif(is_array($content)){
if(preg_match('/'.$begin.'block\sname=[\'"](.+?)[\'"]\s*?'.$end.'/is', $content[3])){ //存在嵌套,进一步解析
$parse = 1;
$content[3] = preg_replace_callback($reg, array($this, 'replaceBlock'), "{$content[3]}{$begin}/block{$end}");
return $content[1] . $content[3];
} else {
$name = $content[2];
$content = $content[0];//就是这一行,之前的索引是3,实际应该是0
$content = isset($this->block[$name]) ? $this->block[$name] : $content;
return $content;
}
}
}
这么改之后,结果如下:
- ...base内容1...
- ....a内容1...
- ...b内容1...
- ....a内容2...
- ...base内容2...
可以看出符合预期了,就这么两句代码的事,可是为什么ThinkPHP官方代码里不支持呢?
参考: