简介
本文件系统性地梳理 DouPHP 模板引擎中的“过滤器”能力,涵盖两类机制:
- 修饰器(运行时变量过滤器):在模板渲染时,对变量值进行链式处理,如转义、截取、格式化等。
- 前置过滤器(编译前源过滤):在模板源码进入编译器之前,对模板文本做全局替换或增强,如主题资源路径改写、注释标签还原等。
文档将说明数据管道、执行顺序、上下文传递、标准过滤器用法、自定义开发流程、性能优化策略以及调试技巧。
项目结构
模板过滤器相关代码集中在 core/web/template 目录下,围绕 DouView 主类构建“注册表 + 标准实现 + 运行期分发”的清晰分层:
- 入口与编排:DouView(负责变量作用域、预处理器、渲染上下文、编译缓存)
- 运行时过滤器:FilterRegistry(名称到可调用对象的映射)、StandardFilters(内置过滤器集合)、FilterInterface(可选的面向对象接口)
- 编译期预处理:PrefilterContext(仅读上下文契约)、FrontPrefilter(前台主题资源路径改写)
- 编译管线:DouViewCompiler(Lexer → Parser → CodeGenerator),其中 Lexer 负责词法切分,为后续解析与生成做准备
graph TB
A["DouView<br/>模板引擎主类"] --> B["FilterRegistry<br/>运行时过滤器注册表"]
A --> C["RenderContext<br/>渲染上下文持有过滤器引用"]
A --> D["DouViewCompiler<br/>编译流水线"]
D --> E["Lexer<br/>词法切分"]
A --> F["CompileCache<br/>编译产物缓存"]
A --> G["PrefilterContext<br/>编译前上下文契约"]
G --> H["FrontPrefilter<br/>前台模板预处理器"]
核心组件
- 运行时过滤器注册表 FilterRegistry:维护“过滤器名 → callable”的映射,提供 register/has/get 方法,供渲染期按名查找并调用。
- 标准过滤器 StandardFilters:集中实现常用修饰器(如 truncate、escape、nl2br、strip_tags、date_format、default、indent、stringformat、strip、capitalize、cat、count*、lower、upper、replace、spacify、wordwrap),并提供统一注册入口 registerInto(FilterRegistry)。
- 过滤器接口 FilterInterface:可选的面向对象约定,定义 apply(value, args) 以支持面向对象的过滤器实现风格。
- 前置过滤器上下文 PrefilterContext:限定预处理器只能读取已 assign 的上下文(如 site、theme_path),不暴露引擎内部细节。
- 前台预处理器 FrontPrefilter:在编译前将相对主题资源路径改写为绝对路径,并清理多余 meta 与注释包裹的标签。
架构总览
模板渲染与过滤的数据流如下:
- 渲染入口:DouView::fetch/display/renderResource
- 编译阶段:compileSource 先执行 runPrefilter(应用前置过滤器),再交由 DouViewCompiler 完成词法→语法→代码生成
- 运行阶段:RenderContext 通过 FilterRegistry 查找并调用对应过滤器,形成“|过滤器”链式处理
sequenceDiagram
participant App as "业务代码"
participant View as "DouView"
participant Cache as "CompileCache"
participant Compiler as "DouViewCompiler"
participant Lexer as "Lexer"
participant Prefilter as "FrontPrefilter"
participant RenderCtx as "RenderContext"
participant Filters as "FilterRegistry"
App->>View : fetch("模板名")
View->>View : renderResource()
View->>Cache : needsRecompile?
alt 需要重编译
View->>View : compileSource()
View->>Prefilter : runPrefilter(source)
Prefilter-->>View : 预处理后的源码
View->>Compiler : compile(资源名, 源码)
Compiler->>Lexer : tokenize()
Lexer-->>Compiler : TokenStream
Compiler-->>View : PHP 字符串
View->>Cache : write(编译产物)
end
View->>View : include 编译产物
Note over RenderCtx,Filters : 渲染期 | 过滤器链由 RenderContext 驱动
详细组件分析
运行时过滤器:FilterRegistry 与 StandardFilters
- 职责
- FilterRegistry:维护过滤器名到 callable 的映射;提供 has/get/register。
- StandardFilters:集中实现常用过滤器,并通过 registerInto 批量注册到 FilterRegistry。
- 执行模型
- 模板中形如 value|filter:param1:param2 的表达式,会在渲染期由 RenderContext 解析并按序调用对应过滤器。
- 每个过滤器接收“上一个结果”作为首参,其余参数来自冒号分隔的附加参数。
- 扩展点
- 可通过 FilterRegistry::register 动态注册新过滤器。
- 也可实现 FilterInterface 以面向对象方式封装复杂逻辑。
classDiagram
class FilterRegistry {
-filters : callable[]
+register(name, callable) void
+has(name) bool
+get(name) callable|null
}
class StandardFilters {
+registerInto(registry) void
+truncate(...)
+escape(...)
+nl2br(...)
+stripTags(...)
+dateFormat(...)
+defaultValue(...)
+indent(...)
+stringFormat(...)
+strip(...)
+capitalize(...)
+cat(...)
+countCharacters(...)
+countParagraphs(...)
+countSentences(...)
+countWords(...)
+lower(...)
+upper(...)
+replace(...)
+spacify(...)
+wordwrap(...)
}
class FilterInterface {
<<interface>>
+apply(value, args) mixed
}
StandardFilters --> FilterRegistry : "批量注册"
前置过滤器:PrefilterContext 与 FrontPrefilter
- 职责
- PrefilterContext:限制预处理器仅能读取已 assign 的上下文(如 site、theme_path),避免耦合具体引擎实现。
- FrontPrefilter:在编译前对模板源码进行主题资源路径改写、清理多余 meta、还原注释中的标签等。
- 执行时机
- 在 DouView::compileSource 中,先 runPrefilter 再交给编译器。
- 典型行为
- 根据全局 _THEME_PATH、assign 的 theme_path 或站点配置推导主题根路径,并将 images/css/js 引用改写为绝对路径。
- 清理重复的 meta 声明,将 <!-- ...{...}...--> 形式的注释内容还原为可直接解析的标签。
flowchart TD
Start(["开始:模板源码"]) --> ReadCtx["读取上下文<br/>site / theme_path / _THEME_PATH"]
ReadCtx --> ComputePath{"是否得到主题路径?"}
ComputePath -- 否 --> EndNoop["返回原源码"]
ComputePath -- 是 --> Rewrite["正则改写 images/css/js 为绝对路径"]
Rewrite --> CleanMeta["清理多余 meta 声明"]
CleanMeta --> RestoreTags["还原注释中的标签"]
RestoreTags --> End(["结束:预处理后的源码"])
编译流水线:DouViewCompiler 与 Lexer
- 职责
- DouViewCompiler:协调 Lexer、Parser、CodeGenerator,完成“源码 → PHP 字符串”的转换;注入全局自动转义开关与定界符。
- Lexer:单趟状态机扫描,将模板源切分为 TokenStream,识别注释、literal、php、普通标签等受保护区,防止注入。
- 关键点
- 词法层只做小块锚定正则,不做整段表达式 lowering,保证性能与兼容性。
- 编译产物由 CompileCache 管理,依据 COMPILE_REVISION 控制失效。
sequenceDiagram
participant V as "DouView"
participant C as "DouViewCompiler"
participant L as "Lexer"
participant P as "Parser"
participant G as "CodeGenerator"
V->>C : compile(资源名, 源码)
C->>L : tokenize(源码, 定界符)
L-->>C : TokenStream
C->>P : parse(TokenStream, 资源名)
P-->>C : AST
C->>G : generate(AST, 原文片段, 资源名)
G-->>C : PHP 字符串
C-->>V : 编译结果
依赖关系分析
- 松耦合设计
- 运行时过滤器通过 FilterRegistry 解耦“名称到实现”的绑定,便于扩展与测试。
- 前置过滤器通过 PrefilterContext 只暴露“读取上下文”的能力,屏蔽引擎细节。
- 关键依赖链
- DouView 依赖 FilterRegistry、DouViewCompiler、CompileCache、PrefilterContext。
- StandardFilters 依赖 FilterRegistry 进行批量注册。
- FrontPrefilter 依赖 PrefilterContext 获取 site/theme_path。
- DouViewCompiler 依赖 Lexer、Parser、CodeGenerator 及 Tag 编译器注册表。
graph LR
DV["DouView"] --> FR["FilterRegistry"]
DV --> DC["DouViewCompiler"]
DV --> CC["CompileCache"]
DV --> PC["PrefilterContext"]
SF["StandardFilters"] --> FR
FP["FrontPrefilter"] --> PC
DC --> LX["Lexer"]
性能与缓存
- 编译缓存
- 编译产物写入 CompileCache,依据 COMPILE_REVISION 与版本头自动失效,切换主题或升级引擎后无需手动清理。
- 词法扫描优化
- Lexer 采用单趟状态机扫描,避免多次正则遍历,减少 CPU 开销。
- 过滤器链
- 运行时过滤器为纯函数式调用,建议保持轻量;复杂计算可考虑在控制器或服务层预处理,减少模板侧负担。
- 全局转义
- 可通过 DouView::setEscapeHtml 开启全局自动 HTML 转义,减少模板中重复转义调用。
故障排查指南
- 模板未生效或路径错误
- 检查是否启用了前置过滤器,确认 theme_path 是否正确推导;必要时清理编译缓存(COMPILE_REVISION 变更会自动失效)。
- 过滤器未找到
- 确认已通过 FilterRegistry::register 或 StandardFilters::registerInto 注册;检查过滤器名大小写与拼写。
- 输出异常或 XSS 风险
- 启用全局自动转义 setEscapeHtml(true),或在模板中对用户输入使用 escape 过滤器。
- 性能问题
- 避免在模板中进行重型计算;将复杂逻辑下沉至服务层;合理使用 wordwrap/truncate 等轻量过滤器。
结论
DouPHP 模板过滤器系统通过“运行时修饰器 + 编译期预处理器”的双通道设计,既保证了模板渲染时的灵活性与安全性,又提供了编译期的全局处理能力。借助清晰的注册表模式与最小化上下文契约,系统具备良好的可扩展性、可测试性与性能表现。
附录:使用示例与最佳实践
- 标准过滤器调用(运行时)
- 字符串截取:value|truncate:50:"..."
- HTML 转义:value|escape:html
- 日期格式化:timestamp|date_format:"%Y-%m-%d"
- 默认值:empty_value|default:"占位文本"
- 空白压缩:text|strip:" "
- 大小写:title|upper / title|lower
- 拼接:name|cat:" 公司"
- 统计:content|count_words
- 换行:text|nl2br
- 去标签:html_content|strip_tags:false
- 替换:url|replace:".cn", ".com"
- 字符间隔:code|spacify:"-"
- 自动换行:long_text|wordwrap:80
- 自定义过滤器
- 过程式:通过 FilterRegistry::register('my_filter', function($value, ...$args){...}) 注册。
- 面向对象:实现 FilterInterface::apply(value, args),并在合适位置注册到 FilterRegistry。
- 自定义前置过滤器
- 实现 callable 签名 function($source, PrefilterContext $ctx): string,通过 DouView::registerPrefilter 注册。
- 典型用途:主题资源路径改写、敏感信息脱敏、模板语法增强等。
- 安全与性能建议
- 对用户输入一律使用 escape 或 strip_tags。
- 开启全局自动转义以减少遗漏。
- 将复杂逻辑移出模板,保持过滤器轻量。
- 利用编译缓存与合理的过滤器组合提升渲染性能。