我记得我刚开始学PHP的时候,对模板解析实在是觉得很奇怪,不知道这个原理怎么实现的,后来看书看多了也明白有一个著名的Smarty在那,曾经也用过一段,不过感觉不是很好,就开始分析Discuz 的模板技术是怎么实现的了,然后我把这个模板解析的代码分离出来了,觉得很好用,用了一段时间, Discuz 的模板解析是用正则表达式替换一些模板中的规定的语言标记,然后呢,写到forumdata/templates中,再用include 引用到index, forumdisplay等等中,和smarty的原理基本上相同,只是功能没有smarty那么多,smarty内建了一个cache来着…连个User Guide都几百页…
呵呵,不过现在基本上两个都没用过,正则表达式好是好用,不过正则的效率不是很高,以前看PHP技术文档的时候说能不用正则就尽量不要用,那东西烦 着,现在做什么项目基本上用的是框架,MVC的模式,View中的代码一般不用模板解析出来,混杂php代码在里面,也觉得不错,OOP的开发效率比较 高,很多地方重用代码是很开心的~!
Discuz 的模板解析要分析出来只要用到两个文件:./include /global.func.php和. /include /template .func.php,global只要一个函数就够了,template 要全部的文件下面我就分开讲一下,会比较详细,大家耐心看:
Section One--./include /global.func.php---->template function
CODE:
function template ($file, $templateid = 0, $tpldir = '') {
这个函数一共有三个传入参数:
global $tplrefresh;
$tpldir = $tpldir ? $tpldir : TPLDIR;
$templateid = $templateid ? $templateid : TEMPLATEID;
$tplfile = DISCUZ_ROOT.'./'.$tpldir.'/'.$file.'.htm';
$objfile = DISCUZ_ROOT.'./forumdata/templates/'.$templateid.'_'.$file.'.tpl.php';
if(TEMPLATEID != 1 && $templateid != 1 && !file_exists($tplfile)) {
return template ($file, 1, './templates/default/');
}
if($tplrefresh == 1 || ($tplrefresh > 1 && substr($GLOBALS['timestamp'], -1) > $tplrefresh)) {
if(@filemtime($tplfile) > @filemtime($objfile)) {
require_once DISCUZ_ROOT.'./include /template .func.php';
parse_template($file, $templateid, $tpldir);
}
}
return $objfile;
}
QUOTE:
$file 表示模板名
$templateid 表示模板id
$tpldir 表示模板目录
CODE:
global $tplrefresh;
这个是把$tplrefresh作为一个全局变量,tplrefresh表示是不是刷新模板
CODE:
$tpldir = $tpldir ? $tpldir : TPLDIR;
给$tpldir和$templateid赋值,如果没有传进来就用常量TPLDIR和TEMPLATEID给它们值
$templateid = $templateid ? $templateid : TEMPLATEID;
CODE:
$tplfile = DISCUZ_ROOT.'./'.$tpldir.'/'.$file.'.htm';
这里是把$tplfile(表示的是模板文件)和$objfile(表示的是要编译成的文件)赋值
$objfile = DISCUZ_ROOT.'./forumdata/templates/'.$templateid.'_'.$file.'.tpl.php';
CODE:
if(TEMPLATEID != 1 && $templateid != 1 && !file_exists($tplfile)) {
return template ($file, 1, './templates/default/');
}
防止TEMPLATEID不等于1且$templateid不等于1,而且模板文件不存在导致空白问题。
这里也可以算一个迭代,也可以不算,就是把1作为第二个参数再调用函数本身。
技术末学 发布于2007-07-04 10:18:09
CODE:
if($tplrefresh == 1 || ($tplrefresh > 1 && substr($GLOBALS['timestamp'], -1) > $tplrefresh)) {
if(@filemtime($tplfile) > @filemtime($objfile)) {
require_once DISCUZ_ROOT.'./include /template .func.php';
parse_template($file, $templateid, $tpldir);
}
}
return $objfile;
很巧妙的一段,
Discuz 的 模板缓存就体现在这里,如果你没打开模板刷新的话(config.inc.php->$tplrefresh=0),这里就直接返回一 个$objfile了,而这个文件是你第一次建论坛的时候就写入的,如果你不改模板的话,关了是能提高相当一部分效率的!反之,如果你打开了模板刷新的话 就接着判断是不是模板文件的建立时间大于 forumdata/templates下的文件,是的话就引用./
include /
template .func.php写入模板文件到 forumdata/templates中,否则的话还是直接返回一个已经编译好的模板文件。
关于 template .func.php文件中函数的分析在下面:
CODE:
//防止非法引用
if(!defined('IN_DISCUZ')) {
exit('Access Denied');
}
CODE:
/**
* 这个是关键模板解析函数,通过正则表达式来替换掉模板中的相关语句,使之变成标准的php语句,写入缓存(./forumdata/template ),从而include 到页面中显示出来
* @para string $file //解析的模板名,只要文件名就可以了,会自动加上htm,如果有缓存的话就变成"模板id_文件名.tpl.php"
* @para int $templateid //模板的id
* @para string $tpldir //模板所在的目录
*
*/
function parse_template($file, $templateid, $tpldir) {
global $language;
$nest = 5;
$tplfile = DISCUZ_ROOT."./$tpldir/$file.htm";
$objfile = DISCUZ_ROOT."./forumdata/templates/{$templateid}_$file.tpl.php";
if(!@$fp = fopen($tplfile, 'r')) {
dexit("Current template file './$tpldir/$file.htm' not found or have no access!");
} elseif(!include_once language('templates', $templateid, $tpldir)) {
dexit("<br>Current template pack do not have a necessary language file 'templates.lang.php' or have syntax error!");
}
$template = fread($fp, filesize($tplfile));
fclose($fp);
$var_regexp = "((///$[a-zA-Z_/x7f-/xff][a-zA-Z0-9_/x7f-/xff]*)(/[[a-zA-Z0-9_/-/./"/'/[/]/$/x7f-/xff]+/])*)";
$const_regexp = "([a-zA-Z_/x7f-/xff][a-zA-Z0-9_/x7f-/xff]*)";
$template = preg_replace("/([/n/r]+)/t+/s", "//1", $template );
$template = preg_replace("//</!/-/-/{(.+?)/}/-/-/>/s", "{//1}", $template );
$template = preg_replace("//{lang/s+(.+?)/}/ies", "languagevar('//1')", $template );
$template = preg_replace("//{faq/s+(.+?)/}/ies", "faqvar('//1')", $template );
$template = str_replace("{LF}", "<?=/"//n/"?>", $template );
$template = preg_replace("//{(///$[a-zA-Z0-9_/[/]/'/"/$/./x7f-/xff]+)/}/s", "<?=//1?>", $template );
$template = preg_replace("/$var_regexp/es", "addquote('<?=//1?>')", $template );
$template = preg_replace("//</?/=/</?/=$var_regexp/?/>/?/>/es", "addquote('<?=//1?>')", $template );
$template = "<? if(!defined('IN_DISCUZ')) exit('Access Denied'); ?>/n$template ";
$template = preg_replace("/[/n/r/t]*/{template /s+([a-z0-9_]+)/}[/n/r/t]*/is", "/n<? include template ('//1'); ?>/n", $template );
$template = preg_replace("/[/n/r/t]*/{template /s+(.+?)/}[/n/r/t]*/is", "/n<? include template (//1); ?>/n", $template );
$template = preg_replace("/[/n/r/t]*/{eval/s+(.+?)/}[/n/r/t]*/ies", "stripvtags('/n<? //1 ?>/n','')", $template );
$template = preg_replace("/[/n/r/t]*/{echo/s+(.+?)/}[/n/r/t]*/ies", "stripvtags('/n<? echo //1; ?>/n','')", $template );
$template = preg_replace("/[/n/r/t]*/{elseif/s+(.+?)/}[/n/r/t]*/ies", "stripvtags('/n<? } elseif(//1) { ?>/n','')", $template );
$template = preg_replace("/[/n/r/t]*/{else/}[/n/r/t]*/is", "/n<? } else { ?>/n", $template );
for($i = 0; $i < $nest; $i++) {
$template = preg_replace("/[/n/r/t]*/{loop/s+(/S+)/s+(/S+)/}[/n/r]*(.+?)[/n/r]*/{//loop/}[/n/r/t]*/ies", "stripvtags('/n<? if(is_array(//1)) { foreach(//1 as //2) { ?>','/n//3/n<? } } ?>/n')", $template );
$template = preg_replace("/[/n/r/t]*/{loop/s+(/S+)/s+(/S+)/s+(/S+)/}[/n/r/t]*(.+?)[/n/r/t]*/{//loop/}[/n/r/t]*/ies", "stripvtags('/n<? if(is_array(//1)) { foreach(//1 as //2 => //3) { ?>','/n//4/n<? } } ?>/n')", $template );
$template = preg_replace("/[/n/r/t]*/{if/s+(.+?)/}[/n/r]*(.+?)[/n/r]*/{//if/}[/n/r/t]*/ies", "stripvtags('/n<? if(//1) { ?>','/n//2/n<? } ?>/n')", $template );
}
$template = preg_replace("//{$const_regexp/}/s", "<?=//1?>", $template );
$template = preg_replace("/ /?/>[/n/r]*/</? /s", " ", $template );
if(!@$fp = fopen($objfile, 'w')) {
dexit("Directory './forumdata/templates/' not found or have no access!");
}
$template = preg_replace("//"(http)?[/w/.//:]+/?[^/"]+?&[^/"]+?/"/e", "transamp('//0')", $template );
$template = preg_replace("//<script[^/>]*?src=/"(.+?)/".*?/>/s*/<//script/>/ise", "stripscriptamp('//1')", $template );
flock($fp, 2);
fwrite($fp, $template );
fclose($fp);
}
CODE:
/**
* 几个替换,过滤掉一些东西
* @para string $str
*
* @return string
*/
function transamp($str) {
$str = str_replace('&', '&', $str);
$str = str_replace('&', '&', $str);
$str = str_replace('/"', '"', $str);
return $str;
}
CODE:
/**
* 从正则表达式来看是给ubb代码去掉一个/符号的,应该是为安全性着想的
* @para string $val
*
* @return string
*/
function addquote($var) {
return str_replace("///"", "/"", preg_replace("//[([a-zA-Z0-9_/-/./x7f-/xff]+)/]/s", "['//1']", $var));
}
-
CODE:
/**
* 把一个字符用语言包中的来替换,没有找到的话就用!string来替换
* @para string $val
*
* @return string
*/
function languagevar($var) {
if(isset($GLOBALS['language'][$var])) {
return $GLOBALS['language'][$var];
} else {
return "!$var!";
}
}CODE:
/**
* FAQ中把id和关建字用一个有效的链接来替换
* @para array $val
*
* @return string
*/
function faqvar($var) {
global $_DCACHE;
include_once DISCUZ_ROOT.'./forumdata/cache/cache_faqs.php';
if(isset($_DCACHE['faqs'][$var])) {
return '<a href="faq.php?action=message&id='.$_DCACHE['faqs'][$var]['id'].'" target="_blank">'.$_DCACHE['faqs'][$var]['keyword'].'</a>';
} else {
return "!$var!";
}
}CODE:
/**
* 去掉自定义的一些tag
* @para string $expr
* @para string $statement
* @return string
*/
function stripvtags($expr, $statement) {
$expr = str_replace("///"", "/"", preg_replace("//</?/=(///$.+?)/?/>/s", "//1", $expr));
$statement = str_replace("///"", "/"", $statement);
return $expr.$statement;
}CODE:
/**
* 作用是把&符号的html编码形式换成&,然后换成javascript引用的形式。
* @para string $s
*
* @return string
*/
function stripscriptamp($s) {
$s = str_replace('&', '&', $s);
return "<script src=/"$s/" type=/"text/javascript/"></script>";
}
![技术末学的个人空间](https://i-blog.csdnimg.cn/blog_migrate/77a9ea85532ba6a6d8814a7b0c39b7b3.png)