文档目录
条件判断标签

简介

本文件面向 DouPHP 模板引擎的条件判断标签,系统性说明 {if}、{elseif}、{else} 的语法、执行逻辑、表达式支持(布尔、比较、逻辑)、嵌套与优先级规则、控制器数据绑定方式、常见错误与调试方法,以及性能优化建议。文档基于模板编译期实现进行解析,确保与实际运行产物一致。

项目结构

条件判断标签由“标签编译器 + 表达式编译器”协作完成:

  • 标签层:IfTagCompiler 负责将 {if}/{elseif}/{else} 分支组织为 PHP 控制流片段。
  • 表达式层:ExpressionCompiler 作为门面,委托 ExprParser 将条件串 token 化、规范化并输出为 PHP 条件片段。
  • 渲染管线:DouViewCompiler 负责 Lexer → Parser → CodeGenerator;DouView 负责缓存、预过滤与 include 编译产物。
graph TB
A["模板源<br/>包含 {if}/{elseif}/{else}"] --> B["DouViewCompiler<br/>compile()"]
B --> C["Lexer/Parser<br/>构建 AST"]
C --> D["CodeGenerator<br/>调用 Tag 编译器"]
D --> E["IfTagCompiler<br/>遍历分支 emit"]
E --> F["ExpressionCompiler<br/>compileIfCondition()"]
F --> G["ExprParser<br/>compileCondition()<br/>token 归一化/校验"]
G --> H["生成 PHP 片段<br/><?php if(...): ?> / elseif(...) / else / endif; ?>"]
H --> I["DouView<br/>include 编译产物并渲染"]

核心组件

  • IfTagCompiler:按分支顺序 emit 条件与内容,最终闭合 endif。
  • ExpressionCompiler:对外提供 compileIfCondition、parseVarProps、compileTernary 等稳定 API。
  • ExprParser:条件 token 扫描、括号平衡校验、关键字到 PHP 运算符映射、变量路径与修饰器处理、三元表达式拆分与简化。
  • EchoTagCompiler:输出变量的 HTML 转义策略与 nofilter 标记识别,影响三元输出的转义行为。
  • AbstractPortalLoopTagCompiler:展示 foreach 块中 else 分支的 emit 模式,辅助理解模板块结构的 else 语义。

架构总览

条件判断从模板到 PHP 产物的关键路径如下:

  • 入口:DouView::renderResource 加载模板并触发编译缓存检查。
  • 编译:DouViewCompiler::compile 驱动 Lexer → Parser → CodeGenerator。
  • 标签:IfTagCompiler 遍历分支,对每个分支调用 ExpressionCompiler::compileIfCondition。
  • 表达式:ExprParser::compileCondition 将条件串 token 化、校验括号、归一化关键字为 PHP 运算符,并拼接为 &lt;?php if(...): ?> 或 elseif(...) 片段。
  • 渲染:生成的 PHP 片段被 include 执行,结合 $ctx->vars 中的变量进行运行时判断。
sequenceDiagram
participant V as "DouView"
participant C as "DouViewCompiler"
participant T as "IfTagCompiler"
participant E as "ExpressionCompiler"
participant P as "ExprParser"
V->>C : renderResource(模板名)
C->>C : compile(资源名, 源)
C->>P : parse(词法/语法)
C->>T : generate(AST)
T->>E : compileIfCondition(条件串, 是否elseif)
E->>P : compileCondition(条件串, 是否elseif)
P-->>E : "<?php if(...) : ?>" 或 "<?php elseif(...) : ?>"
T-->>C : emit 分支内容与 endif
C-->>V : 返回 PHP 字符串
V->>V : include 编译产物并渲染

详细组件分析

IfTagCompiler 分析

  • 职责:遍历 AST 分支,依次 emit if/elseif/else 与对应子节点,最后 emit endif。
  • 关键点:
    • 分支 keyword 为 'if'/'elseif'/'else' 时分别处理。
    • 条件通过 ExpressionCompiler::compileIfCondition 委托给表达式层。
    • 所有分支结束后统一 emit endif。
flowchart TD
Start(["进入 IfTagCompiler::compile"]) --> Loop{"遍历分支"}
Loop --> |分支keyword='if'| EmitIf["emit '<?php if (...): ?>'"]
Loop --> |分支keyword='elseif'| EmitElseif["emit '<?php elseif (...): ?>'"]
Loop --> |分支keyword='else'| EmitElse["emit '<?php else: ?>'"]
EmitIf --> Children["emit 子节点"]
EmitElseif --> Children
EmitElse --> Children
Children --> Next{"下一个分支?"}
Next --> |是| Loop
Next --> |否| Endif["emit '<?php endif; ?>'"]
Endif --> Done(["结束"])

表达式编译器与条件解析

  • ExpressionCompiler::compileIfCondition:将 tag_args 与是否 elseif 传入 ExprParser::compileCondition。
  • ExprParser::compileCondition:
    • 使用词法器 token 化条件串。
    • 校验括号平衡。
    • 将 eq/ne/lt/le/gt/ge/and/or/not/mod 等关键字映射为 PHP 运算符。
    • 允许直接透传的运算符与符号包括:!, %, !==, ==, ===, >, &lt;, !=, &lt;>, &lt;&lt;, >>, &lt;=, >=, &&, ||, |, ^, &, ~, ), ,, +, -, *, /, @, (。
    • 遇到函数名或变量函数调用会报错。
    • 变量路径与字面量会被编译为 PHP 表达式。
    • 最终拼接为 "&lt;?php if(...): ?>" 或 "&lt;?php elseif(...): ?>"。
flowchart TD
S(["compileCondition(条件串, 是否elseif)"]) --> T["token 化条件串"]
T --> B{"括号平衡?"}
B --> |否| Err["抛出语法错误"]
B --> |是| Map["遍历 token 归一化"]
Map --> K{"是否为保留运算符/括号?"}
K --> |是| Skip["跳过"]
K --> |否| Switch{"关键字匹配"}
Switch --> |eq/ne/lt/le/gt/ge| Op["替换为 PHP 运算符"]
Switch --> |and/or| Logic["替换为 &&/||"]
Switch --> |not| Not["替换为 !"]
Switch --> |mod| Mod["替换为 %"]
Switch --> |函数名/变量函数| FuncErr["语法错误"]
Switch --> |变量/字面量| CompileVal["编译为 PHP 表达式"]
Op --> Join["拼接 token 序列"]
Logic --> Join
Not --> Join
Mod --> Join
CompileVal --> Join
Skip --> Join
Join --> Out["生成 '<?php if/elseif (...): ?>'"]

三元表达式与 HTML 转义

  • 三元表达式 {$a ? x : y} 由 ExprParser::compileTernary 拆分为 cond/true/false,并对简单比较做优化。
  • EchoTagCompiler 在输出变量或三元表达式时,若全局开启 escapeHtml 且未显式 nofilter 或未使用 |escape html,则自动包裹 htmlspecialchars。
  • 这意味着三元表达式的输出是否转义由上下文与修饰器共同决定。

循环块中的 else 语义参考

  • AbstractPortalLoopTagCompiler 展示了 foreach 块中 else 分支的 emit 模式:当存在 else 时,先 emit endforeach; else:,再 emit else 子节点,最后 endif。
  • 这与 {if} 块的 else 语义一致,便于理解模板块结构中 else 的位置与生命周期。

依赖关系分析

  • IfTagCompiler 依赖 ExpressionCompiler 提供的 compileIfCondition。
  • ExpressionCompiler 依赖 ExprParser 进行条件解析与值编译。
  • DouViewCompiler 注册 IfTagCompiler 到 NodeType::IF_,并在生成阶段调用。
  • DouView 管理编译缓存与 include 编译产物,确保运行时变量作用域 $ctx->vars 可用。
classDiagram
class DouViewCompiler {
+compile(resource, source) string
}
class IfTagCompiler {
+compile(node, ctx) void
}
class ExpressionCompiler {
+compileIfCondition(tag_args, elseif) string
+parseVarProps(val, skip_defaults) string
+compileTernary(raw) array
}
class ExprParser {
+compileCondition(tag_args, elseif) string
+compileValue(val, skipDefaults) string
+compileTernary(raw) array
}
class DouView {
+fetch(template) string
+renderResource(name) void
}
DouViewCompiler --> IfTagCompiler : "注册并调用"
IfTagCompiler --> ExpressionCompiler : "委托条件编译"
ExpressionCompiler --> ExprParser : "委托解析"
DouView --> DouViewCompiler : "驱动编译"

性能与优化

  • 编译缓存:DouView 根据 COMPILE_REVISION 与版本头管理编译产物,避免重复编译。
  • 零正则前端:ExprParser 采用无 preg 的词法与字符串处理,减少开销。
  • 条件归一化:将 eq/ne/lt/... 等关键字映射为 PHP 运算符,减少运行时判断复杂度。
  • HTML 转义策略:默认可关闭全局转义或使用 nofilter/|escape html 精细控制,避免不必要的 htmlspecialchars 调用。
  • 三元优化:对简单比较 {$var op val ? a : b} 进行拆分与优化,减少表达式层级。

故障排查指南

  • 语法错误定位:ExprParser::syntaxError 会抛出包含模板文件名与行号的异常,便于快速定位。
  • 常见错误:
    • 缺少参数:if/elseif 语句需要参数,否则抛出语法错误。
    • 括号不平衡:条件中括号数量不一致会报错。
    • 非法函数调用:条件中不允许函数名或变量函数调用。
    • 未知 token:不支持的标识符会报错。
  • 调试建议:
    • 查看编译产物:在开发环境临时关闭编译缓存,观察生成的 PHP 片段。
    • 检查变量作用域:确认控制器已 assign 所需变量,模板通过 $ctx->vars 访问。
    • 使用 nofilter/|escape html:当输出包含 HTML 或特殊字符时,合理设置转义策略。

结论

DouPHP 的条件判断标签通过 IfTagCompiler 与 ExpressionCompiler/ExprParser 的协作,实现了安全、高效且易于维护的模板条件逻辑。其设计强调编译期 token 归一化、严格的语法校验与灵活的转义策略,适合复杂业务场景下的条件渲染需求。遵循本文的语法与最佳实践,可有效提升模板的可读性与性能。

附录:语法速查与示例

支持的运算符与关键字

  • 比较:==、!=、&lt;>、>=、&lt;=、>、&lt;
  • 逻辑:&&、||、!
  • 取模:%
  • 关键字映射:eq→==、ne/neq→!=、lt→&lt;、le/lte→&lt;=、gt→>、ge/gte→>=、and→&&、or→||、not→!、mod→%

变量与表达式

  • 变量路径:$user.id、$product.status
  • 算术:$a + $b、$a - $b、$a * $b、$a / $b、$a % $b
  • 三元:{$status == 1 ? '上架' : '下架'}
  • 修饰器:nofilter、|escape html

嵌套与优先级

  • 嵌套:{if} 内部可嵌套其他 {if} 块,注意每层都要有对应的 {/if}。
  • 优先级:括号优先,其次逻辑运算(&&、||),比较运算(==、!=、&lt;、>、&lt;=、>=)从左到右,取模 % 与加减乘除遵循 PHP 规则。
  • 关键字:and/or 等价于 &&/||,not 等价于 !。

控制器数据绑定与变量访问

  • 控制器通过 DouView::assign 将变量注入模板上下文。
  • 模板运行时通过 $ctx->vars 访问这些变量。
  • 建议在控制器中准备干净的数据结构,避免在模板中进行复杂计算。

实际业务场景示例(描述性)

  • 用户权限验证:根据当前用户角色显示不同菜单或操作按钮。
  • 商品状态显示:根据商品状态字段显示“在售”、“缺货”、“下架”等文案。
  • 会员等级判断:根据会员等级字段展示不同权益或折扣信息。
  • 订单流程提示:根据订单状态显示“待支付”、“已发货”、“已完成”等步骤。
添加日期:2026-10-05