2.1 变量输出与条件判断
让模板拿到数据,并按条件决定显示什么。
模板里的数据从哪来
第一次打开 Discuz! 模板文件,最容易懵的是:为什么这个 $threadlist 在 PHP 文件里翻不到?
因为模板变量不写在模板文件里,而是在渲染之前由 PHP 侧赋值进去的。模板本质是「拿什么变量、画成什么 HTML」的图纸,通道只有两个:
| 通道 | 写法 | 谁赋值 | 典型用途 |
|---|---|---|---|
全局变量 $_G |
$_G['uid']、$_G['config']、$_G['forum'] |
核心 class_core.php 与各脚本入口 |
登录状态、站点配置、当前版块、用户组 |
模板变量 $xxx |
$threadlist、$post、$nav |
控制器脚本或插件 | 页面主体数据,由业务决定 |
$_G 是 Discuz! 的「万能口袋」:$_G['uid'] 非 0 即已登录,同时给出 $_G['username']、$_G['groupid'](用户组 ID)、$_G['adminid'](0 普通用户、1 论坛管理员、2 超级版主、3 论坛版主)。模板变量的赋值则发生在调用 template() 之前:
// 放在对应脚本文件里(X5 中归到 source/app/xxx/module/ 下)
$threadlist = $query->fetch_all();
template('forumdisplay'); // 这行执行完,模板里就能用 $threadlist
这段代码不需要写进模板。模板侧不碰 PHP,站长升级内核就不会被牵连——这正是模板和插件要分家的原因。
{$var} 与 $var 的差别
两种写法都能打印变量,差别在变量名的边界怎么定。Discuz! 编译模板时,变量名从 $ 开始按字母数字下划线连续匹配,裸写 $var 时后面紧跟字母、数字或下划线会被一起吃进变量名:
<!-- 危险:$name 后面紧跟 s,编译器会去找 $names -->
<span>{$name}s 同学</span>
<!-- 安全:花括号划死了边界 -->
<span>{$name} s 同学</span>
前者编译后仍原样输出,页面上看到的是模板源码,排查很费劲。
打个比方:{$var} 像在变量名后划了道白线,编译器过了线就停手;$var 不划线,撞上了算一个词。所以 Discuz! 官方模板几乎全用花括号,自己写也一律用 {$var}——唯一该裸写的是 {eval} 内部,那里是完整 PHP 语句,边界由 PHP 自己定。
数组下标在模板里可以不写引号
这是编译器的便利特性。PHP 里访问数组要写 $array['key'],模板里可以简写:
{$thread[subject]}
{$forum[name]}
{$member[username]}
编译期自动补成 $thread['subject']、$forum['name']。下标里的标识符被当成字符串字面量处理,不需要也不应该手动加引号。多层同理:$_G[config][sitename] 会补成 $_G['config']['sitename']。原因是编译器要同时匹配「PHP 变量」和「HTML 标签」两套语法,带引号的下标会让匹配产生歧义。
复杂表达式:用花括号锁住整体
带下标、带拼接的输出,一律塞进花括号。花括号内的内容编译后原样成为 PHP 表达式,字符串该有的引号一个都不能少:
{$forum[name]}
{<a href="forum.php?mod=viewthread&tid=".$tid."">'.$thread[subject].'</a>}
凡是希望被 PHP 当代码执行的部分,写在真实 .htm 文件里就是注释包着的 {eval ...}。
条件判断的四个标签
条件分支与 PHP 的 if / elseif / else 一一对应:{if 条件} 成立时执行下面这段,{elseif 条件} 承接上一分支不成立的情况,{else} 兜底,{/if} 结束整个块。
写在真实 .htm 里,每个标签都被一对 HTML 注释包着(注释符号在下文一律写成 < >):
<!--{if $thread[special]}-->
<span class="special">{$thread[specialname]}</span>
<!--{elseif $thread[highlight]}-->
<span class="highlight">{$thread[highlight]}</span>
<!--{else}-->
<span class="normal">{$thread[subject]}</span>
<!--{/if}-->
{if} 后面就是 PHP 表达式,多条件组合官方文档给的例子是 {if empty($_G['forum']['picstyle']) && $_GET['orderby'] == 'lastpost'}。
字符串比较必须写两个等号:{if $a = 'x'} 是赋值,恒为真。存在性判断优先用 isset() 或 empty(),别直接 if($var)——未定义变量在 PHP 8 下直接报 Warning。
写在 HTML 属性里的条件:省掉注释壳
注释壳的作用是标签没被编译时浏览器把它当注释忽略。但标签一旦写进 HTML 属性内部,前后都是引号,壳反而碍事。这种位置 Discuz! 允许省略注释壳:
<a href="{if $tid}forum.php?mod=viewthread&tid={$tid}{else}javascript:;{/if}" class="threadlink">
{$thread[subject]}
</a>
编译器照样识别,编译成 PHP 的 if: / else: / endif;。规则一句话:标签整体位于某个 HTML 标签的属性值内部时可以省壳,一旦跨出属性范围就必须写回。只有两个分支时,三段挤在一个属性里完全可行。
CSS 与 class 里也能判断
同一套规则搬到 style 和 class 上,用来输出不同的值:
<div style="width:{if $width}{$width}px;{else}100%;{/if}">
<span>{if $isnew}新{/if}</span>
</div>
<li class="{if $thread[isattch]}has-attach{/if}">
只输出「值」而不是「标签」时,{if} 和 {else} 之间直接写裸文本即可。用 class 做状态标记比写一堆 new class 再靠 CSS 控制显示省事得多。
常见坑对照表
| 你写的 | 实际结果 | 原因 | 改法 |
|---|---|---|---|
{if $a} 忘了 {/if} |
整页报错或后续内容被吞 | 条件块没闭合 | 数 {if 与 {/if} 是否相等 |
{if $name = 'x'} |
条件恒为真 | 单等号是赋值 | 改成 {if $name == 'x'} |
{if $type == 0} 但值是 '0' |
走进「不为 0」分支 | '0' 在 PHP 里是假值 |
统一类型或用 === |
{if $_GET[action]} 直接用请求参数 |
逻辑可被伪造 | $_GET 未过滤 |
先在 PHP 侧校验 |
{$thread[subject]} 仍输出源码 |
下标名拼错或层数不对 | 编译器找不到就原样输出 | 临时用 {eval print_r($thread);} 看结构,看完删 |
| 模板改了刷新没变化 | 编译缓存没更新 | 产物缓存在 data/cache/ |
后台 → 界面 → 更新缓存 |
模板编译产物缓存在 data/cache/,不是每次请求都重新解析。改完模板看不到变化,先去更新缓存。
小结
变量只有两个来源:$_G 和 PHP 侧注入的模板变量。输出一律用 {$var},数组下标裸写让编译器补引号,跨出 HTML 属性的地方写回注释壳。条件四件套与 PHP 结构一致,字符串比较记得两个等号。
下一篇讲 {loop} 循环与两种模板复用方式。
