本帖不仅仅是分析文件,还把Discuz中模板解析这一原理分析了一下。转自www.discuz.net 作者:郭鑫
我记得我刚开始学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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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; } |
这个函数一共有三个传入参数:
$file 表示模板名
$templateid 表示模板id
$tpldir 表示模板目录
1 |
global $tplrefresh; |
这个是把$tplrefresh作为一个全局变量,tplrefresh表示是不是刷新模板
1 2 |
$tpldir = $tpldir ? $tpldir : TPLDIR; $templateid = $templateid ? $templateid : TEMPLATEID; |
$tpldir和$templateid赋值,如果没有传进来就用常量TPLDIR和TEMPLATEID给它们值
1 2 |
$tplfile = DISCUZ_ROOT.'./'.$tpldir.'/'.$file.'.htm'; $objfile = DISCUZ_ROOT.'./forumdata/templates/'.$templateid.'_'.$file.'.tpl.php' |
这里是把$tplfile(表示的是模板文件)和$objfile(表示的是要编译成的文件)赋值
1 2 3 |
if(TEMPLATEID != 1 && $templateid != 1 && !file_exists($tplfile)) { return template($file, 1, './templates/default/'); } |
防止TEMPLATEID不等于1且$templateid不等于1,而且模板文件不存在导致空白问题。
这里也可以算一个迭代,也可以不算,就是把1作为第二个参数再调用函数本身。
1 2 3 4 5 6 7 |
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文件中函数的分析在下面:
1 2 3 4 |
//防止非法引用 if(!defined('IN_DISCUZ')) { exit('Access Denied'); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
/** * 这个是关键模板解析函数,通过正则表达式来替换掉模板中的相关语句,使之变成标准的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); } |
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * 几个替换,过滤掉一些东西 * @para string $str * * @return string */ function transamp($str) { $str = str_replace('&', '&', $str); $str = str_replace('&', '&', $str); $str = str_replace('\"', '"', $str); return $str; } |
1 2 3 4 5 6 7 8 9 |
/** * 从正则表达式来看是给ubb代码去掉一个\符号的,应该是为安全性着想的 * @para string $val * * @return string */ function addquote($var) { return str_replace("\\\"", "\"", preg_replace("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", "['\\1']", $var)); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * 把一个字符用语言包中的来替换,没有找到的话就用!string来替换 * @para string $val * * @return string */ function languagevar($var) { if(isset($GLOBALS['language'][$var])) { return $GLOBALS['language'][$var]; } else { return "!$var!"; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * 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!"; } } |
1 2 3 4 5 6 7 8 9 10 11 |
/** * 去掉自定义的一些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; } |
1 2 3 4 5 6 7 8 9 10 |
/** * 作用是把&符号的html编码形式换成&,然后换成javascript引用的形式。 * @para string $s * * @return string */ function stripscriptamp($s) { $s = str_replace('&', '&', $s); return "<script src=\"$s\" type=\"text/javascript\"></script>"; } |