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} 循环与两种模板复用方式。