Z-Blog 模板编译:{$var} 是怎么一步步变成 PHP 的
编译缓存位置、十二步流水线的固定顺序、四种高频标签的真实展开,以及改模板不生效等三个实战坑。
你在 Z-Blog 模板里写的 {$article.Title}、{if ...}、{foreach ...},前台并不会直接执行——它们要先被 Template 类逐条替换成 PHP 源码,写进缓存文件,之后再被 include 执行。搞不清这条链路,遇到「改了模板没反应」「模板报错但看不出错在哪」就只能瞎猜。
一、编译产物在哪,什么时候生成
编译结果不是放在主题目录里,而是落在缓存目录:
zb_users/cache/compiled/{主题ID}/
index.php
single.php
list.php
...
每个模板文件对应一个同名的 .php。生成时机由 BuildTemplate() 控制:目录不存在就建,存在就先删掉旧的 .php 再全部重新编译。后台「清空缓存并重新编译模板」触发的就是这一步。
关键点:编译缓存和数据库缓存是两回事。你改了 template/single.php,如果没触发重编译,前台还在跑旧的那份 PHP——这跟文章数据变没变完全无关。
二、十二步编译流水线
CompileFile() 按固定顺序处理模板文本,顺序不能乱,否则前面替换出来的内容会被后面的规则二次匹配:
| 步骤 | 方法 | 做的事 |
|---|---|---|
| 1 | remove_php_blocks | 清掉裸写的 <?php ?> |
| 2 | parse_uncompile_code | 把 {php}{/php} 块抽走暂存,换成占位符 |
| 3 | parse_template | {template:xxx} → include |
| 4 | parse_module | {module:navbar} → echo $modules[...] |
| 5 | parse_comments | {*注释*} → PHP 注释 |
| 6 | parse_option | {#ZC_XXX#} → 常量或 $option |
| 7 | parse_vars | {$a.b} → $a->b |
| 8 | parse_function | {func(args)} → 函数调用 |
| 9-12 | if / foreach / for / switch | 控制结构 |
| N | parse_back_uncompile_code | 把暂存的 {php} 块还原回 <?php ?> |
第 2 步和第 N 步是一对:{php} 里的代码不参与任何标签替换,原样保留。所以插件里想写 PHP 逻辑又要避开编译,必须用 {php}...{/php} 包起来,直接写 <?php ?> 会被第 1 步抹掉。
三、四种高频标签的真实展开
{$article.Title} → 属性访问
{$article.Title}
// 编译后
<?php echo $article->Title; ?>
点号被替换成对象操作符,所以模板里不能写 {$article.Title|htmlspecialchars} 这种管道——Z-Blog 模板没有过滤器语法。
{if ...}{elseif ...}{else}
{if $article->Status == 0}
草稿
{elseif $article->Status == 1}
已发布
{else}
其它
{/if}
编译时先处理 {elseif},再处理 {else},最后套 if。写嵌套 if 时记得每层都要有对应的 {/if},少一个会整页白屏且不给行号。
{template:header}
{template:header}
// 编译后
<?php include $this->GetTemplate('header'); ?>
走的是 GetTemplate(),所以插件可以挂 Filter_Plugin_Template_GetTemplate 把某个子模板换掉——这是做「只替换评论模板」这类局部改造的正规入口。
{module:navbar}
{module:navbar}
// 编译后
<?php if(isset($modules['navbar'])){echo $modules['navbar']->Content;} ?>
带 isset 保护,模块不存在时静默跳过。侧栏模块内容本身是存库的,改了模块要在后台重建模块缓存。
四、自动补标签:为什么你没写也有
addNonexistentTags() 会在编译前检查几个模板,缺了就自动补:
comments模板没有AjaxCommentBegin/AjaxCommentEnd→ 前后各插一个 labelcomment模板没有id="cmt{ID}"→ 补一个锚点 labelcommentpost没有验证码区域 → 在</form>前插验证码模板header没有{$header}→ 插到</head>前footer没有{$footer}→ 插到</body>前
这就是为什么自己写的模板没放 {$header},插件注入的 CSS 还是出来了。但也意味着:如果你的模板里 {$header} 位置放错了(比如塞在 <body> 里),自动补齐不会救你。
五、三个实战坑
坑一:模板改了不生效。先去 zb_users/cache/compiled/ 看对应 .php 的时间戳。是旧的就去后台清缓存重编译;如果重编译后还是旧的,检查是不是插件挂了 Filter_Plugin_Template_Compiling_End 又把内容改回去了。
坑二:模板里写 JS 的 { } 被当成标签。Z-Blog 只认 {$、{if、{template: 这类固定前缀,普通 { } 一般不受影响。但 {func(...)} 这条规则很宽——JS 里出现 {foo(bar)} 形式会被当函数调用编译。用 {php}{/php} 包起来或改写法即可。
坑三:子模板 include 路径。{template:xxx} 找的是编译目录下的 xxx.php,不是主题源码目录。所以在子目录里的模板引用兄弟模板时,路径相对于编译根目录,不是当前文件所在目录。
小结
记住三句话就够:模板是先编译后执行的两段式;编译产物在 cache/compiled/,改模板必须重编译;{php}{/php} 是唯一能逃过替换的写法。下一篇讲 Metas——不建表怎么给文章塞自定义字段。