简介
本文件面向 DouPHP 模板系统的控制流标签,系统性说明条件判断、循环遍历、跳转控制等标签的语法、嵌套规则、编译行为与性能优化建议。内容基于模板编译器源码与实际模板使用片段,帮助开发者在商品列表展示、用户权限判断、动态菜单生成等场景中正确高效地使用控制流标签。
项目结构
DouPHP 模板系统采用“词法分析 → 语法解析 → 代码生成”的三段式编译管线,控制流标签由独立的 TagCompiler 实现,并在统一注册表中装配。
graph TB
A["模板源"] --> B["词法分析 Lexer"]
B --> C["语法解析 Parser"]
C --> D["代码生成 CodeGenerator"]
D --> E["运行时 PHP 代码"]
subgraph "标签编译器"
F["IfTagCompiler"]
G["ForeachTagCompiler"]
H["BreakTagCompiler"]
I["ContinueTagCompiler"]
J["AbstractPortalLoopTagCompiler"]
end
D --> F
D --> G
D --> H
D --> I
D --> J
核心组件
- 条件分支:{if} / {elseif} / {else} / {/if}
- 循环遍历:{foreach} / {/foreach}(含 {foreachelse})
- 数据块循环:{list} / {category}(通过 Portal 取数并复用 foreach 语义)
- 循环控制:{break} / {continue}
- 辅助能力:循环元信息(如迭代次数)、offset/limit 切片、键值对访问
架构总览
控制流标签在编译期被转换为原生 PHP 控制结构,运行期以最小开销执行。
sequenceDiagram
participant T as "模板"
participant L as "Lexer"
participant P as "Parser"
participant CG as "CodeGenerator"
participant REG as "TagCompilerRegistry"
participant IF as "IfTagCompiler"
participant FE as "ForeachTagCompiler"
participant BR as "BreakTagCompiler"
participant CO as "ContinueTagCompiler"
T->>L : 扫描模板源
L-->>P : TokenStream
P->>CG : AST(含控制流节点)
CG->>REG : 查找对应标签编译器
alt 条件分支
REG-->>IF : 编译 if/elseif/else
IF-->>CG : 输出 <?php if/elseif/else : ... endif; ?>
else 循环
REG-->>FE : 编译 foreach
FE-->>CG : 输出 foreach/endforeach + 循环元信息
end
CG-->>T : 生成可执行的 PHP 代码
详细组件分析
条件判断标签 {if}/{elseif}/{else}
- 语法要点
- {if 条件} ... {/if}
- {elseif 条件} ... {/if}
- {else} ... {/if}
- 编译行为
- 按分支顺序逐条 emit,最终闭合为 endif
- 条件表达式交由表达式层编译,支持复杂逻辑组合
- 嵌套规则
- 可与 {foreach} 等任意嵌套;注意每个分支仅包含其子树
- 常见用法
- 权限判断、功能开关、多分支展示
flowchart TD
Start(["进入 {if}"]) --> Branch{"分支关键字"}
Branch --> |if| EmitIf["emit if(...)"]
Branch --> |elseif| EmitElseif["emit elseif(...)"]
Branch --> |else| EmitElse["emit else"]
EmitIf --> Children["渲染子节点"]
EmitElseif --> Children
EmitElse --> Children
Children --> End(["endif 结束"])
循环遍历标签 {foreach}
- 语法要点
- {foreach from=数组 item=变量 key=键名 name=循环名 limit=数量 offset=偏移}
- 支持 {foreachelse} 空态分支
- 关键特性
- 自动将非数组/对象转为数组,避免类型错误
- 支持 offset/limit 切片,减少前端渲染压力
- 循环元信息写入 $ctx->loops[name],提供 total、iteration 等
- 支持 key/value 双变量访问
- 典型模式
- 商品列表、图片画廊、属性选项、优惠券列表等
flowchart TD
S(["开始 {foreach}"]) --> Prep["准备 $_from = from<br/>类型归一化"]
Prep --> Slice{"需要切片?"}
Slice --> |是| DoSlice["array_slice(offset, limit)"]
Slice --> |否| Next["继续"]
DoSlice --> Next
Next --> Meta["初始化循环元信息<br/>total/iteration"]
Meta --> Loop{"total > 0 ?"}
Loop --> |否| ElseBranch["渲染 {foreachelse}"]
Loop --> |是| ForEach["foreach 遍历"]
ForEach --> Body["渲染循环体"]
Body --> Inc["iteration++"]
Inc --> ForEach
ElseBranch --> End(["endforeach 结束"])
ForEach --> End
数据块循环 {list}/{category}
- 语义
- 通过 Portal 白名单入口获取数据,再复用 foreach 循环机制
- 支持 item/key/name/offset 及 {listelse}/{categoryelse}
- module 必须为字面量标识符,防止注入
- 适用场景
- 栏目分类树、模块列表等结构化数据展示
classDiagram
class AbstractPortalLoopTagCompiler {
+compile(node, ctx)
-compileStart(tagArgs, ctx) string
#tagName() string
#portalMethod() string
#extraReservedAttrs() array
#buildExtraProps(attrs, ctx) array
}
class ListTagCompiler
class CategoryTagCompiler
AbstractPortalLoopTagCompiler <|-- ListTagCompiler
AbstractPortalLoopTagCompiler <|-- CategoryTagCompiler
跳转控制 {break}/{continue}
- 作用范围
- 仅在循环体内有效,分别等价于 PHP 的 break/continue
- 典型用途
- 提前终止列表渲染、跳过不符合条件的条目
实际模板中的使用片段
- 首页功能开关与条件包含
- 使用 {if} 控制推荐模块的显示
- 商品详情页的图片画廊与属性选项
- 使用 {foreach} 遍历图片与属性值
- 后台表单中的下拉选择
- 使用 {foreach} + {if} 高亮当前选中项
依赖关系分析
- 编译管线依赖
- DouViewCompiler 负责组装 Lexer、Parser、CodeGenerator 与 TagCompilerRegistry
- 控制流标签编译器在注册表中集中注册,便于扩展与维护
- 标签间耦合
- Foreach 与 Break/Continue 紧密配合
- If 常与 Foreach 嵌套,用于行级条件渲染
- List/Category 复用 Foreach 的循环语义
graph LR
DVC["DouViewCompiler"] --> REG["TagCompilerRegistry"]
REG --> IF["IfTagCompiler"]
REG --> FE["ForeachTagCompiler"]
REG --> BR["BreakTagCompiler"]
REG --> CO["ContinueTagCompiler"]
REG --> AP["AbstractPortalLoopTagCompiler"]
AP --> LT["ListTagCompiler"]
AP --> CT["CategoryTagCompiler"]
性能考虑
- 条件分支
- 尽量将高频命中条件前置,减少不必要的计算
- 复杂条件可在控制器预处理后传入模板,降低模板侧表达式复杂度
- 循环遍历
- 优先使用 {foreach} 的 offset/limit 进行分页或截断,避免一次性渲染大量 DOM
- 合理使用 key/value 访问,减少二次查询或额外处理
- 数据块循环
- {list}/{category} 通过 Portal 白名单取数,确保 module 为字面量,避免运行时注入带来的风险与开销
- 跳转控制
- 合理使用 {break}/{continue} 缩短循环体,但避免滥用导致逻辑不清晰
- 模板缓存
- 利用编译缓存减少重复编译开销(由上层视图层管理)
故障排查指南
- 常见问题
- 缺少必要属性:如 foreach 缺少 from/item,list/category 缺少 module/item
- 非法标识符:item/key/name 必须为合法标识符,不能是动态表达式
- 未闭合标签:{/if}、{/foreach} 缺失会导致编译失败
- 定位方法
- 检查 Lexer 输出的 TokenStream 是否正确切分标签
- 查看 TagCompiler 的错误提示,通常包含标签名与具体原因
- 修复建议
- 补齐缺失属性,修正标识符命名
- 确保标签成对出现,嵌套层级正确
结论
DouPHP 的控制流标签以清晰的编译模型和严格的校验机制,提供了安全、高效的模板控制能力。通过合理组织条件与循环、善用切片与跳转控制,可以在保证可读性的同时获得良好的渲染性能。建议在业务中遵循本文的最佳实践,避免常见陷阱,提升模板质量与可维护性。
附录:业务场景示例与最佳实践
- 商品列表展示
- 使用 {foreach} 遍历商品集合,结合 {if} 判断促销状态或库存
- 使用 offset/limit 做分页或首屏截断
- 参考路径:product.dwt:37-85
- 用户权限判断
- 使用 {if} 判断角色或功能开关,控制按钮或区块可见性
- 参考路径:index.dwt:29-37
- 动态菜单生成
- 使用 {list}/{category} 拉取栏目树,嵌套 {foreach} 渲染多级菜单
- 使用 {if} 高亮当前项,必要时用 {break}/{continue} 优化渲染
- 参考路径:download.htm:131-140、box.htm:51-58
最佳实践清单
- 将复杂业务逻辑下沉到控制器或服务层,模板只负责呈现
- 使用 {foreach} 的 key/value 直接访问键值对,避免二次查找
- 合理使用 {break}/{continue} 精简循环体,保持逻辑清晰
- 对大列表使用 offset/limit 控制渲染规模
- 始终确保标签成对闭合,避免编译期错误