5.1 模板语法与变量输出


5.1 模板语法与变量输出

视图车间的第一课是词汇表:控制器把变量递进车间后,你在模板里用什么记号把它们摆上页面。本节覆盖输出、循环、条件、包含四类日常语法,以及变量出厂前的转义规则——这条规则直接决定全站 XSS 防线的基础强度,第 7 章会从攻击者视角再回收一次。接口为主的读者可以略读语法部分,但转义小节建议逐行读完。

输出标签与转义默认线

模板里最常写的记号是花括号输出。假设控制器交来 $book(图书模型实例)与 $tags(数组):

// 控制器侧 return view('book/detail', [ 'book' => $book, 'tags' => ['后端', '入门'], 'priceText' => sprintf('%.2f 元', $book->price), ]);
<!-- 模板侧:book/detail.html --> <p>书名:{$book.title}</p> <p>价格:{$priceText}</p> <p>标签:{foreach $tags as $t}{$t} {/foreach}</p> <p>库存状态:{$book.stock > 0 ? '现货' : '缺货'}</p>

{$book.title} 会被编译成对变量的安全读取:键不存在不报错,输出前默认经过 htmlspecialchars 转义。这条默认转义线是框架替你把守的第一道闸——任何用户提交的内容(昵称、评论、文件名)经过它输出,<script> 会变成肉眼可见的文本而不是可执行代码。需要输出富文本 HTML 时,用 raw 显式豁免:

<p>{$article.content}</p> <!-- 默认转义:纯文本字段用这个 --> <p>{$article.content|raw}</p> <!-- 显式豁免:仅限经过白名单净化的富文本 -->

纪律只有一条:raw 必须逐个审批。写它的前提是你能说清这个字段的净化链路(见 7.2 的富文本过滤);说不清就退回默认转义。模板里还允许 {:函数调用()} 形式的表达式输出,例如 {:date('Y-m-d', $book->publish_time)},但车间纪律限制它只做展示层格式化——一旦开始在里面写业务计算,就把它搬回控制器或模型。

循环、条件与空值兜底

列表渲染用 foreach 标签,配合 empty 标签处理空数据——搜索无结果、栏目暂无内容都靠它兜底:

{volist name="books" id="vo" empty="暂无符合条件的图书"} <div class="item"> <h3>{$vo.title}</h3> <p>{$vo.author} · ¥{$vo.price}</p> {if $vo.stock > 5} <span class="ok">现货</span> {elseif $vo.stock > 0 /} <span class="warn">仅剩{$vo.stock}本</span> {else /} <span class="off">缺货</span> {/if} </div> {/volist}

volist 自带分块输出能力:mod 属性按取模结果做隔行变色,lengthoffset 做窗口截取,翻页组件未就位时可以临时顶上。条件判断除了 {if},简单场景用三元表达式更紧凑。判断条件里写展示逻辑(库存高亮、奇偶行)是允许的;写业务规则(打折怎么算、会员价怎么来)是越界——判断标准见第 5 章开头的车间纪律。

包含与自定义标签

页头、页脚、翻页条这类重复部件用 include 收进片段文件:

{include file="public/header" title="图书详情" /} <!-- 页面主体 --> {include file="public/footer" /}

被包含片段可以接收额外变量(如上面的 title),片段内部同样遵守转义规则。当 include 传参越传越多、片段内部开始堆条件判断时,就该升级成自定义标签:标签库允许你把「渲染热门图书榜单」这类带取数逻辑的块注册成一个语义化标签,模板里一行 {hotlist limit="5" /} 搞定。自定义标签的本质是「编译期把标签翻译成 PHP 代码」,注册成本低,但可读性代价高——团队约定最多维护一套业务标签库,且每个标签必须有注释说明取数来源。更常见的替代方案是把取数挪到控制器,模板只收现成变量,这也是本教程推荐的主路径。

多主题与语言包的工程要点

同一套业务要出多个皮肤(后台换肤、站点矩阵)或多语言版本时,视图文案不要硬编码。语言包把文案收进字典,模板用 {:lang('book.empty')}{$Think.lang.book_empty} 取值;主题则通过切换模板根目录实现——同一套模板变量,不同目录里各放一份皮肤。工程上的两条经验:其一,语言包按模块拆文件而不是堆一个总表,否则翻译对齐会成为灾难;其二,主题数量控制在个位数,每加一个主题,所有模板改动成本都要乘以主题数——多数项目真正需要的不是多主题,而是把颜色与间距抽成 CSS 变量。这两项都属于「要点」级别:知道入口在哪、成本在哪,需要时再深入。

动手练习:给书店详情页补全转义与空值兜底

背景:练习项目的图书详情页已能显示书名与价格。操作:加上作者、库存状态(三元表达式)、评论列表(foreachempty 兜底);把书名对应的评论者昵称当作不可信输入,确认输出 HTML 源码里脚本片段被转义成文本;再构造一条含 HTML 标签的评论入库,对比加与不加 raw 的渲染差异。结果示例:默认转义下页面显示出 <b>绝版好书</b> 的字面文本,加 raw 后变成粗体——后者仅在你接入 7.2 的白名单净化后才允许保留。解读:转义线的价值不在「防住所有攻击」,而在「默认安全,例外可控」。变式:把评论区块抽成 include 片段并在列表页复用,统计模板里重复 HTML 的行数变化。

⚠️ 常见坑:调试时在模板里 {$Think.get.keyword|raw} 直接回显搜索词,是教科书级的反射 XSS 漏洞——搜索词属于用户输入,必须走默认转义。看到 raw 就问一句「这个字段的净化链路在哪」,应成为团队条件反射。

本节要点回顾

  • 默认转义是底线:花括号输出默认过 htmlspecialcharsraw 是显式豁免,逐个审批。
  • 四类记号够用:输出、volist 循环、if 条件、include 包含,覆盖日常绝大多数页面。
  • 展示判断可,业务判断不可:模板里的条件只决定「怎么显示」,不决定「数据是什么」。
  • 自定义标签是双刃剑:编译期翻译、语义清晰,但取数逻辑进模板会侵蚀分层,团队须约定边界。
  • 多主题多语言先算成本:语言包按模块拆、主题控制在个位数,多数需求 CSS 变量就能解决。

词汇表到手,下一节解决结构问题:怎么让几十个页面共享同一套骨架,而不是互相复制。模板继承与布局见 5.2。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U