简介
本章节面向 DouPHP 模板语言(DWT)的基础语法,聚焦变量输出、数据类型字面量、注释、转义与安全过滤、以及常见模板文件的编码与命名约定。文档基于模板引擎的词法分析器、语法分析器与渲染上下文实现进行说明,并结合实际模板示例展示用法。
项目结构
DouPHP 的模板系统位于 core/web/template 目录,采用“词法分析 → 语法分析 → 代码生成 → 运行时渲染”的分层架构:
- 词法分析(Lexer):将模板源切分为 Token 流,识别文本块、注释、字面量区段、PHP 区段与普通标签。
- 语法分析(Parser):将 Token 流构建为 AST,校验控制结构配对,并记录文本块顺序以还原空白处理。
- 编译与缓存(DouView + CompileCache):负责模板路径解析、编译产物缓存、变量作用域注入与全局 HTML 转义开关。
- 运行期(RenderContext + Tag 编译器):执行 AST 对应的 PHP 代码,输出最终 HTML。
graph TB
A["模板源(.dwt/.tpl/.htm/.html)"] --> B["词法分析 Lexer"]
B --> C["语法分析 Parser"]
C --> D["代码生成 CodeGenerator"]
D --> E["编译产物(可执行PHP)"]
E --> F["运行期 RenderContext"]
F --> G["HTML 输出"]
核心组件
- 词法器(Lexer):单趟扫描,识别注释 {... }、字面量区段 {literal}...{/literal}、PHP 区段 {php}...{/php} 与普通标签 {...};对文本块中的疑似 PHP 起止符进行转义,防止注入。
- 语法分析器(Parser):将 Token 流转为 AST,支持三元表达式、命令/修饰符/参数三段式切分,循环块(foreach/list/category)与条件块(if/elseif/else)的结构校验。
- 视图入口(DouView):提供 assign/fetch/display、模板路径解析、编译缓存、全局 HTML 转义开关、前置过滤器等能力。
- Token:定义 TEXT/TAG/COMMENT/LITERAL/PHP 五种单元类型,承载值与行号信息。
架构总览
模板从源到输出的关键流程如下:
- 资源解析:校验扩展名白名单与路径安全,定位模板文件。
- 编译管线:Prefilter → Lexer → Parser → CodeGenerator,产出可执行 PHP。
- 运行期:通过 RenderContext 访问变量与作用域,执行指令并输出 HTML。
- 缓存策略:按版本头与修订号管理编译产物,支持强制重编与源文件变更检测。
sequenceDiagram
participant App as "应用"
participant View as "DouView"
participant Cache as "CompileCache"
participant L as "Lexer"
participant P as "Parser"
participant CG as "CodeGenerator"
participant RT as "RenderContext"
App->>View : fetch("模板名")
View->>View : resolveTemplatePath()
View->>Cache : needsRecompile()
alt 需要重新编译
View->>View : compileSource()
View->>L : tokenize()
L-->>View : TokenStream
View->>P : parse(TokenStream)
P-->>View : AST
View->>CG : generate(AST)
CG-->>View : PHP代码
View->>Cache : write(编译产物)
end
View->>RT : include 编译产物
RT-->>App : HTML字符串
详细组件分析
变量输出与数据类型字面量
- 变量输出语法:使用 {$var} 形式在模板中输出变量。该语法由词法器识别为 TAG,随后由语法分析器判定为“直接输出”节点,并在代码生成阶段转换为运行期读取与输出逻辑。
- 数组与对象访问:在 {$var.key} 或 {$var[index]} 等表达式中,运行期通过上下文变量解析访问数组键或对象属性。
- 数字与布尔字面量:在表达式中使用数字与布尔常量参与比较或三元运算,语法分析器将其归类为“命令/参数”,由代码生成器生成相应 PHP 表达式。
- 示例参考:首页模板中多处使用 {$keywords}、{$description}、{$page_title} 等变量输出。
注释语法
- 单行/多行注释:使用 { ... } 包裹的内容作为注释,词法器会将其识别为 COMMENT 令牌,语法分析器构建注释节点,运行期丢弃不输出。
- 使用场景:用于开发调试、隐藏片段、添加说明等。
字面量区段与 PHP 区段
- 字面量区段:{literal}...{/literal} 内的内容原样输出,不进行模板语法解析。适用于嵌入脚本或包含特殊字符的静态内容。
- PHP 区段:{php}...{/php} 在语法分析阶段被拒绝,禁止在模板中直接编写 PHP 代码,提升安全性。
转义机制与安全过滤(防 XSS)
- 全局自动 HTML 转义:可通过设置全局开关启用自动转义,所有输出默认进行 HTML 实体编码,降低 XSS 风险。
- 内置过滤器:标准过滤器注册于过滤器注册表,可在表达式链中使用过滤器进行数据清洗与格式化。
- 文本块内 PHP 注入防护:词法器对文本块中的疑似 PHP 起止符进行替换与转义,避免模板注入。
- 最佳实践:
- 始终对来自用户输入的数据使用过滤器或开启全局转义。
- 避免在模板中直接拼接未过滤的用户输入。
- 对富文本内容使用专门的白名单过滤后再输出。
循环与迭代(foreach/list/category)
- 循环标签:支持 foreach、list、category 等循环结构,具备 else 分支与闭合标签校验。
- 属性校验:模块名、项名、键名等属性需为标识符或字面量,确保模板安全与可维护性。
- 使用建议:在列表页、分类页中遍历数据集合,结合变量输出与条件判断渲染条目。
模板文件编码与命名约定
- 扩展名白名单:允许 tpl、htm、html、dwt 四种扩展名,便于统一管理与工具识别。
- 路径安全:禁止路径穿越与绝对路径,限制模板文件必须在配置的模板根目录下。
- 命名建议:
- 页面级模板使用功能名 + .dwt(如 index.dwt)。
- 公共片段放入 inc 目录并使用语义化命名(如 header.tpl)。
- 保持 UTF-8 编码,避免混用不同编码导致乱码。
依赖关系分析
模板系统的内部依赖关系如下:
- DouView 依赖 Lexer、Parser、CodeGenerator、CompileCache、RenderContext。
- Lexer 产生 TokenStream,供 Parser 消费。
- Parser 产出 AST,供 CodeGenerator 生成 PHP 代码。
- RenderContext 在运行期提供变量访问与作用域管理。
classDiagram
class DouView {
+assign()
+fetch()
+display()
+renderResource()
+compileSource()
}
class Lexer {
+tokenize()
}
class Parser {
+parse()
+getTexts()
}
class CodeGenerator
class CompileCache
class RenderContext
DouView --> Lexer : "调用"
DouView --> Parser : "调用"
DouView --> CodeGenerator : "调用"
DouView --> CompileCache : "读写"
DouView --> RenderContext : "创建/使用"
性能考虑
- 编译缓存:通过版本头与修订号管理编译产物,减少重复编译开销。
- 单趟词法分析:Lexer 采用状态机单趟扫描,避免多次正则匹配带来的性能损耗。
- 文本块优化:TEXT 与指令严格交替,保留空白位置信息,减少运行时空白处理成本。
- 建议:
- 在生产环境启用编译检查与缓存,关闭强制重编。
- 合理拆分模板片段,减少大模板的编译与渲染时间。
故障排查指南
- 未闭合标签错误:当存在未匹配的 if/foreach/list/category 等块时,语法分析器会在对应打开行报错。
- 无法识别的标签:若标签格式不符合规范,解析阶段会报告“无法识别的标签”。
- 模板路径非法:路径穿越、非白名单扩展名或越界路径会被拒绝,返回空结果。
- 调试技巧:
- 临时启用强制重编以排除缓存干扰。
- 使用注释 { ... } 逐步缩小问题范围。
- 检查变量是否已通过 assign 注入上下文。
结论
DouPHP 模板语言通过严格的词法与语法分析、安全的转义机制与灵活的过滤器体系,提供了稳定高效的模板渲染能力。遵循本文档的语法规范与安全实践,可有效避免 XSS 等安全风险,并提升模板的可维护性与性能。
附录
- 常用语法速查:
- 变量输出:{$var}
- 注释:{ 注释内容 }
- 字面量区段:{literal}...{/literal}
- 条件与循环:{if}...{/if}、{foreach}...{/foreach}、{list}...{/list}、{category}...{/category}
- 过滤器:在表达式链中使用内置过滤器进行数据清洗与格式化
- 示例参考:
- 首页模板中的变量输出与片段包含