理解这个原理,不仅能让你明白 Discuz! 是如何工作的,也能帮助你更好地进行二次开发和模板制作。

核心思想:分离逻辑与表现
Discuz! 的模板引擎,其最核心的设计思想就是 “逻辑与表现分离”。
- 逻辑:指 PHP 代码,它负责处理数据、进行业务计算、与数据库交互等,查询论坛的帖子列表、计算用户积分、判断用户权限等。
- 表现:指 HTML 代码,它只负责如何展示数据,帖子列表是表格形式还是卡片形式、标题是什么颜色、用户头像多大等。
在传统的 PHP 开发中,我们常常会把 HTML 和 PHP 代码混在一起,像这样:
// 混合了逻辑和表现,难以维护
<ul>
<?php
foreach ($posts as $post) {
echo "<li><a href='view.php?id={$post['id']}'>{$post['title']}</a> - {$post['author']}</li>";
}
?>
</ul>
当需要修改页面布局时,程序员需要去修改 PHP 文件,非常容易出错,Discuz! 的模板引擎就是为了解决这个问题而生的。
它的工作流程可以概括为:

- 定义模板:设计师或开发者创建一个纯 HTML 文件,其中使用 Discuz! 的特殊标记来表示动态内容。
- PHP 准备数据:PHP 脚本执行业务逻辑,从数据库获取数据,并将这些数据传递给模板引擎。
- 模板解析与编译:模板引擎读取 HTML 模板文件,将里面的特殊标记替换成真实的 PHP 代码,生成一个编译后的 PHP 文件。
- 执行编译文件:服务器执行这个编译后的 PHP 文件,它直接从 PHP 引擎接收数据并生成最终的 HTML 页面,发送给用户的浏览器。
详细解析步骤
下面我们一步步拆解这个过程。
第 1 步:模板文件
模板文件是纯 HTML 文件,存放在 template/ 目录下,它使用 Discuz! 定义的一套标签来标记需要动态插入的内容。
常见的 Discuz! 模板标签包括:
- 变量输出:
{变量名}{subject}会输出主题标题。
- 循环:
{loop 数组变量名 临时变量名} ... {/loop}这是处理列表数据的核心,遍历帖子列表。
(图片来源网络,侵删) - 条件判断:
{if 条件} ... {elseif 条件} ... {else} ... {/if}根据用户是否登录显示不同的内容。
- 模板包含:
{template 头部文件名}- 用于引入公共模板,如
header.htm,footer.htm等,实现代码复用。
- 用于引入公共模板,如
- 其他内置函数:
{lang 语言键}用于多语言,{eval PHP代码}用于执行一小段 PHP 代码(不推荐滥用)。
示例:一个简单的帖子列表模板 forum_list.htm
<!-- {template header} -->
<h2>论坛列表</h2>
<table border="1">
<tr>
<th>标题</th>
<th>作者</th>
<th>回复数</th>
</tr>
<!-- {loop $postlist $post} -->
<tr>
<td><a href="view.php?id={$post['tid']}">{$post['subject']}</a></td>
<td>{$post['author']}</td>
<td>{$post['replies']}</td>
</tr>
<!-- {/loop} -->
</table>
<!-- {template footer} -->
在这个模板里,你看不到任何 PHP 代码,只有 HTML 和 Discuz! 的标签。$postlist 这个变量是由 PHP 代码传递进来的。
第 2 步:PHP 代码准备数据
在 PHP 脚本中(forum.php),开发者会查询数据库,获取帖子列表数据,然后调用模板引擎进行渲染。
// forum.php (伪代码)
require './source/class/class_core.php'; // 加载核心类
$discuz = & discuz_core::instance(); // 初始化
$discuz->init(); // 加载必要模块
// 1. 准备数据
$postlist = array(); // 假设这是从数据库查询到的帖子数组
// ... 查询数据库逻辑 ...
$postlist[] = array('tid' => 1, 'subject' => '欢迎来到Discuz!', 'author' => 'Admin', 'replies' => 10);
$postlist[] = array('tid' => 2, 'subject' => '模板解析原理探讨', 'author' => 'UserA', 'replies' => 5);
// 2. 将数据传递给模板引擎
// $postlist 变量将在模板中可用
include template('forum:forum_list'); // 关键函数!
这里的 template('forum:forum_list') 是一个核心函数,它启动了整个模板解析流程。
第 3 步:模板解析与编译(核心原理)
当 template() 函数被调用时,模板引擎开始工作,它的核心是 “编译” 而不是 “实时解析”。
-
定位模板文件:引擎根据
template('forum:forum_list')找到对应的模板文件template/default/forum/forum_list.htm。(forum是风格目录,default是默认模板)。 -
检查编译文件是否存在:引擎会去
template/cache/目录下寻找一个对应的编译文件,forum/forum_list.php。 -
判断是否需要重新编译:
- 需要编译:如果模板源文件(
forum_list.htm)比编译文件新,或者编译文件不存在,那么引擎就必须重新解析模板。 - 无需编译:如果编译文件存在且比源文件新,说明模板没有被修改,引擎会直接跳过解析步骤,直接执行编译后的文件。这是 Discuz! 性能优化的关键,它避免了每次访问页面都去解析模板文本的开销。
- 需要编译:如果模板源文件(
-
执行编译过程:
- 如果需要编译,引擎会读取
forum_list.htm的内容。 - 它会使用正则表达式来替换模板标签,将其转换成合法的 PHP 代码。
{loop $postlist $post}会被替换成<?php foreach ((array)$postlist as $post) { ?>{/loop}会被替换成<?php } ?>{$post['subject']}会被替换成<?php echo htmlspecialchars($post['subject']); ?>(注意,通常会加上htmlspecialchars来防止 XSS 攻击){if condition}会被替换成<?php if (condition) { ?>
- 编译后的 PHP 代码被写入到
template/cache/forum/forum_list.php文件中。
- 如果需要编译,引擎会读取
编译后的 forum_list.php 文件可能长这样:
// template/cache/forum/forum_list.php
<?php if(!defined('IN_DISCUZ')) exit('Access Denied'); ?>
<?php include template('header'); ?>
<h2>论坛列表</h2>
<table border="1">
<tr>
<th>标题</th>
<th>作者</th>
<th>回复数</th>
</tr>
<?php foreach ((array)$postlist as $post) { ?>
<tr>
<td><a href="view.php?id=<?php echo $post['tid']; ?>"><?php echo htmlspecialchars($post['subject']); ?></a></td>
<td><?php echo $post['author']; ?></td>
<td><?php echo $post['replies']; ?></td>
</tr>
<?php } ?>
</table>
<?php include template('footer'); ?>
看到这里,你应该明白了:模板引擎本质上是一个 “文本到代码的转换器”,它把一个结构化的 HTML 文件,转换成了一个可以直接执行的 PHP 脚本。
第 4 步:执行编译文件
template() 函数在完成编译(或找到编译文件)后,会 include 这个编译后的 PHP 文件。
// 回到 forum.php
include template('forum:forum_list'); // 这行代码实际上变成了 include 'template/cache/forum/forum_list.php';
PHP 引擎开始执行 forum_list.php,在这个文件里,$postlist 变量是已经由父脚本(forum.php)定义好的,foreach 循环可以正常执行,数据被填充进 HTML 结构中,最终生成纯粹的 HTML 字符串,并输出到浏览器。
总结与优点
Discuz! 模板解析原理总结:
“一次编译,多次执行” 的编译型模板引擎,它将带有特殊标签的 HTML 模板文件,通过正则表达式转换为标准的 PHP 脚本文件(编译文件),当页面被访问时,直接执行这个编译文件,从而避免了每次都解析模板标签的性能损耗。
这种设计带来的优点:
- 性能高:编译后的文件是纯 PHP,执行效率极高,模板源文件的修改只会触发一次重新编译,对用户访问的性能影响最小。
- 逻辑与表现分离:PHP 程序员可以专注于业务逻辑,前端开发者可以专注于页面布局和样式,两者互不干扰,协作效率高。
- 安全性增强:模板引擎在输出变量时,通常会默认进行
htmlspecialchars转义,可以有效防止跨站脚本攻击。 - 易于维护和扩展:修改页面样式只需修改 HTML 模板文件,无需触碰核心 PHP 逻辑,降低了出错的概率。
需要注意的点:
- 模板引擎的局限性:复杂的业务逻辑如果强行塞进模板里(滥用
{eval}),会让模板变得难以维护,违背了“分离”的初衷。 - 编译目录权限:
template/cache/目录必须具有可写权限,否则模板无法编译,网站会报错。 - 现代对比:这种编译型模板引擎在 PHP 早期非常流行(如 Smarty, Discuz! 引擎),现代的 PHP 框架(如 Laravel 的 Blade, Symfony 的 Twig)虽然也采用类似的思想,但语法更简洁、功能更强大,并且通常利用 PHP 的 OPcache 机制来进一步提升性能,省去了文件写入的步骤,但其核心的“预编译”思想与 Discuz! 是一脉相承的。
