1.1 Discuz! 模板系统是怎么工作的
> 弄清一条请求怎么变成 HTML,改模板才不用瞎猜。
骨架与血肉:先建立一个比喻
Discuz! 的模板文件既不是纯 HTML,也不是 PHP 文件,它是一份带标记的 HTML 骨架,标记在编译阶段会被替换成真正的数据。可以把它看成一次装修改造:骨架决定这副身体长什么样,血肉是长在骨头上的肌肉,决定它呈现什么内容。
改骨架——换掉 div 层级、换掉整页布局——那叫换模板套系;只换血肉——把 logo 换掉、主色从蓝改成红——那叫换模板风格。这个比方 1.2 节还会再用一次。
从 URL 到 HTML 的完整链路
| 阶段 | 谁在干活 | 产出 | 观察证据 |
|---|---|---|---|
| 1. 请求入口 | forum.php / home.php / portal.php |
确定 CURSCRIPT,加载框架 |
地址栏文件名变了,行为随之变化 |
| 2. 框架初始化 | class_core.php 引导 $_G |
建数据库连接、判定登录态与权限 | $_G['uid']、$_G['adminid'] 有值 |
| 3. 业务逻辑 | source/app/xxx/module/ 下的类 |
查数据、组装数组 | 模板里出现了只有逻辑层才该有的数据 |
| 4. 调用渲染 | 业务代码调用 template() |
指定渲染哪个模板文件 | 页面开始出现完整 HTML |
| 5. 模板编译 | source/class/class_template.php |
编译成 PHP 缓存到 data/cache/ |
缓存目录多出可读 PHP 文件 |
| 6. 输出 | PHP 执行编译产物 | 浏览器收到 HTML | 页面正常显示 |
第 3 步和第 4 步的边界,是初学者最该记住的一条:查数据、写判断,都是业务逻辑的活;模板只负责把备好的数据摆到页面上。一旦你在模板里写复杂查询,八成是放错了地方。
第 1 步:入口脚本与 $_G
Discuz! 没有路由分发,所有入口都是站点根目录下的实体文件。用户访问 forum.php 就进 forum.php,访问 portal.php 就进 portal.php。入口脚本做的事只有三件:引入核心、初始化、把控制权交给对应模块。
最常见的渲染调用只有一个参数:
<?php
// 放在 source/app/xxx/module/ 下对应的业务文件里
// 渲染当前套系下的 forum/forumdisplay.htm
template('forum/forumdisplay');
需要指定非默认目录时会用上后面几个参数。X5.0 里 app 自带的模板走 app 内部模板目录,且只支持 .php 格式模板:
<?php
require './source/class/class_core.php';
$discuz = C::app();
$discuz->cachelist = [];
$discuz->init();
require_once appfile('yyy/zzz'); // 调用 /source/app/xxx/yyy/zzz.php
template('tpl', 0, 'source/app/xxx/template'); // 渲染 app 的模板
这段就是加载辅助函数的标准写法:初始化框架、引入某个 app 的文件、再渲染模板。appfile() 和第三个参数指定的模板目录,是 X5.0 相对 X3.5 变化最明显的地方。老代码里找不到对应写法时,先确认自己是不是在照抄 X3 的模板。
第 5 步:模板是怎么变成 PHP 的
模板文件用 XML/HTML 语法,模板标签写在 HTML 注释里。下面是一段真实结构的片段:
<!--{if $thread['subject']}-->
<li class="thread-item">
<a href="forum.php?mod=viewthread&amp;tid={$thread['tid']}" class="thread-title">
<!--{$thread['subject']}--></a>
</li>
<!--{/if}-->
class_template.php 逐条扫描,把注释形式的标签翻译成 PHP 控制语句,拼成一个完整的 PHP 文件写进缓存:
| 模板里写的 | 编译后大致等价于 | 说明 |
|---|---|---|
{$var} |
echo $var; |
输出变量 |
<!--{if $a}--> |
if ($a): |
条件分支开始 |
<!--{loop $arr $k $v}--> |
foreach ($arr as $k => $v): |
循环,可嵌套 |
<!--{echo $x}--> |
echo $x; |
显式输出 |
<!--{subtemplate common/header}--> |
编译期把另一模板内容合并进来 | 不是运行时 include |
<!--{template common/footer}--> |
运行时 include 另一模板 | 与上一行区别很大 |
<!--{hook/xxx}--> |
调用插件钩子 | 插件与模板的连接点 |
<!--{eval echo 1;}--> |
echo 1; |
直接执行 PHP |
这张表解释了两个常被搞混的标签:{subtemplate xxx} 与 {template xxx}。前者发生在编译期,被引用的模板内容会拼进当前缓存文件;后者发生在运行时,是一次真正的 include。引用 common/header 这类公共片段,习惯上用 {subtemplate common/header}。
为什么模板里能写 PHP
因为模板的最终产物就是 PHP 文件。{eval ...} 之所以能用,原理不是「模板引擎支持嵌入 PHP」,而是这一段会被原样写进编译产物,等于你亲手往生成的 PHP 里插了一行。
这也顺带解释了两件事:一是模板里能访问 $_G、能调用全局函数,因为它跑在 PHP 全局作用域里;二是模板里能写 PHP 语法,却不该写 PHP 逻辑。业务规则一旦写进模板,就没法被其他入口复用,也没法做成后台配置项——记住这条分界线,后面讲细胞模板封装时会明白它的价值。
为什么不改完缓存看不到效果
模板不是每次访问都重新解析。缓存命中时,系统直接 include 已生成的 PHP 文件,跳过整个解析过程。好处是性能,代价是你手改的模板文件,在缓存更新之前根本没人读。
| 你看到的现象 | 真正的原因 | 该做什么 |
|---|---|---|
| 改了 HTML,页面毫无变化 | 仍在 include 旧缓存 PHP | 更新缓存 |
| 只改了一部分文件,另一部分却变了 | 两批文件对应的缓存新旧不一 | 更新缓存,让它们重新编译 |
改了 {lang} 调用,看不到新文案 |
语言包与模板是两套缓存 | 一起更新 |
| 更新缓存后短暂变慢 | 下一次访问要现场编译模板 | 正常现象,多访问几次即恢复 |
具体怎么更新缓存、开发者模式开关怎么加、怎么用设计者模式查钩子,都在 1.4 节讲。开发阶段建议养成习惯:改完模板先更新缓存,再刷浏览器。
小结与动手清单
压缩成三句话:模板是 HTML 骨架加模板标记,业务逻辑在模板之外准备数据,class_template.php 把它编译成 PHP 缓存到 data/cache/ 执行。你要建立的核心认知是缓存。
打开你的站点,按这个顺序确认一遍:
- 进
data/cache/目录,随便打开一个模板缓存文件看看——里面是 PHP 代码,不是 HTML。 - 访问任意一个 Discuz! 页面,右键查看源代码,对照
template/default/下的同名文件,找出对应的 header 片段。 - 记住上面那张标签翻译表,1.3 节开始就要动手改文件了。
