文档目录
循环遍历标签

简介

本文件面向 DouPHP 模板引擎的循环遍历能力,系统化说明 {foreach}、{list}、{category} 等循环标签的语法与运行机制,覆盖数组遍历、键值对访问、循环计数器、变量作用域与数据绑定机制;并提供商品列表展示、用户评论循环、分类导航生成等业务场景的使用建议。同时给出大数据集处理与分页加载的性能优化策略,以及 {break}、{continue} 的控制用法与嵌套规则、最佳实践。

项目结构

DouPHP 的模板编译采用“词法分析 → 语法解析 → 代码生成”的分层架构:

  • 编译器入口负责装配 Lexer、Parser、CodeGenerator 与标签编译器注册表。
  • 标签编译器将模板中的循环标签(如 {foreach}、{list}、{category})编译为 PHP 控制结构与运行时上下文操作。
  • 运行期通过 Portal 门面获取数据,并在模板渲染时执行生成的 PHP 代码。
graph TB
A["模板源文件<br/>*.dwt"] --> B["词法分析器<br/>Lexer"]
B --> C["语法解析器<br/>Parser"]
C --> D["代码生成器<br/>CodeGenerator"]
D --> E["标签编译器注册表<br/>TagCompilerRegistry"]
E --> F["foreach 编译器<br/>ForeachTagCompiler"]
E --> G["list/category 基类<br/>AbstractPortalLoopTagCompiler"]
G --> H["list 编译器<br/>ListTagCompiler"]
G --> I["category 编译器<br/>CategoryTagCompiler"]
D --> J["生成的 PHP 代码"]
J --> K["运行期执行<br/>Portal 取数 + foreach 循环"]

核心组件

  • {foreach}:通用数组/对象遍历标签,支持 item、key、name、limit、offset、else 分支,自动维护循环元信息(total、iteration)。
  • {list}:基于 Portal 的数据列表标签,按模块白名单取数,支持 limit(SQL LIMIT)、sort、catId、excerpt 等属性,同样支持 key/name/offset/else。
  • {category}:栏目型分类树标签,支持 with="items" 模式,返回分类树或带内容的分类树,循环属性与 {list} 一致。
  • {break}/{continue}:在循环体内控制流程,分别映射到 break/continue。

架构总览

下图展示了从模板到运行期的关键路径:编译器装配标签编译器、解析 AST、生成 PHP,并在运行期调用 Portal 取数后执行 foreach 循环。

sequenceDiagram
participant T as "模板文件"
participant C as "DouViewCompiler"
participant R as "TagCompilerRegistry"
participant L as "List/Category 编译器"
participant P as "Portal(运行期)"
participant V as "视图执行"
T->>C : 编译模板源
C->>R : 查找对应标签编译器
R-->>C : 返回 Foreach/List/Category 编译器
C->>L : 生成 foreach/list/category 代码片段
L->>P : 运行期调用 listFor/categoryFor 取数
P-->>L : 返回数据数组
L->>V : 进入 foreach 循环并输出内容

详细组件分析

{foreach} 标签

  • 功能要点
    • from:必填,指定要遍历的数组/对象表达式。
    • item:必填,当前项变量名(标识符校验)。
    • key:可选,当前键变量名(标识符校验)。
    • name:可选,循环名称;未提供时自动生成唯一名。
    • limit/offset:可选,用于切片限制;内部使用 array_slice。
    • else:当 total=0 时执行的空态分支。
    • 循环元数据:$ctx->loops[name] 包含 total、iteration;$ctx->loopVarmap[item]=name 用于 @first/@last 等属性解析。
  • 数据绑定与作用域
    • 每次迭代将 $ctx->vars[item] 赋值为当前元素;key 存在时以 key=>value 形式遍历。
    • offset/limit 在循环前对数据进行切片,避免大数组全量遍历。
  • 典型用法
    • 遍历商品图片集合、优惠券列表、自定义字段等。
flowchart TD
Start(["进入 {foreach}"]) --> CheckFrom["校验 from 与 item"]
CheckFrom --> ParseKey{"是否提供 key?"}
ParseKey --> |是| BuildKey["构建 key => value 遍历片段"]
ParseKey --> |否| NoKey["仅 value 遍历"]
BuildKey --> Slice["应用 offset/limit 切片"]
NoKey --> Slice
Slice --> Meta["初始化 loops[name] 与 loopVarmap"]
Meta --> Loop{"total > 0 ?"}
Loop --> |是| Iterate["foreach 迭代并递增 iteration"]
Loop --> |否| ElseBranch["执行 {foreachelse}"]
Iterate --> End(["结束"])
ElseBranch --> End

{list} 标签

  • 功能要点
    • module:必填,且必须为字面量标识符(编译期强制),防止注入。
    • item/key/name/offset:同 {foreach}。
    • 其他属性(如 limit/sort/catId/excerpt)作为 props 透传给 Portal 方法 listFor。
    • 数据源:Portal::listFor(module, props),根据模块类型分流 columnList/singleList。
    • else:当返回空数组时执行 {listelse}。
  • 适用场景
    • 商品列表、文章列表、下载列表等“可列表化”模块的数据展示。
  • 注意事项
    • limit 语义为 SQL LIMIT(由 Portal 侧实现),不同于 {foreach} 的切片 limit。
    • offset 在取数后进行 array_slice,保证与 {foreach} 一致的偏移行为。
sequenceDiagram
participant M as "模板"
participant LC as "ListTagCompiler"
participant AP as "AbstractPortalLoopTagCompiler"
participant PL as "Portal : : listFor"
participant PH as "PHP 执行"
M->>LC : 解析 {list ...}
LC->>AP : 调用 compileStart()
AP->>PL : 传入 module 与 props
PL-->>AP : 返回数据数组
AP->>PH : 生成 foreach 代码并执行

{category} 标签

  • 功能要点
    • module:必填,且为字面量标识符。
    • with="items":可选,返回分类树+每类内容(内容行位于节点 list 字段)。
    • 其余属性与 {list} 一致(item/key/name/offset 及额外 props)。
    • 数据源:Portal::categoryFor(module, props)。
    • else:空分类树时执行 {categoryelse}。
  • 适用场景
    • 分类导航、栏目树展示、带子内容的分类列表。

{break} 与 {continue}

  • {break}:终止当前循环,映射到 PHP 的 break。
  • {continue}:跳过本次迭代继续下一次,映射到 PHP 的 continue。
  • 使用位置:仅在循环体内部有效({foreach}/{list}/{category} 内)。

实际业务示例(引用模板)

  • 商品详情页的图片画廊与属性值列表:使用 {foreach} 遍历 gallery_list、attribute.value_list 等集合。
    • 参考路径:product.dwt:37-39、product.dwt:77-85
  • 评论列表:可在评论模板中使用 {foreach} 遍历评论集合,或使用 {list} 拉取评论数据。
  • 分类导航:使用 {category} 标签拉取栏目树,必要时 with="items" 获取每类内容。

依赖关系分析

  • 编译器装配:DouViewCompiler 将 ForeachTagCompiler、ListTagCompiler、CategoryTagCompiler、BreakTagCompiler、ContinueTagCompiler 等注册到 TagCompilerRegistry。
  • 标签继承:ListTagCompiler 与 CategoryTagCompiler 均继承 AbstractPortalLoopTagCompiler,复用“解析→Portal取数→foreach循环”的骨架。
  • 运行期依赖:list/category 标签依赖 Portal 门面进行数据获取;foreach 直接对 from 表达式结果进行遍历。
classDiagram
class DouViewCompiler {
+compile(resource, source) string
-getTagCompilerRegistry() TagCompilerRegistry
}
class TagCompilerRegistry {
+register(type, compiler) void
}
class ForeachTagCompiler {
+compile(node, ctx) void
}
class AbstractPortalLoopTagCompiler {
<<abstract>>
+compile(node, ctx) void
#tagName() string
#portalMethod() string
}
class ListTagCompiler {
+tagName() string
+portalMethod() string
}
class CategoryTagCompiler {
+tagName() string
+portalMethod() string
}
class BreakTagCompiler {
+compile(node, ctx) void
}
class ContinueTagCompiler {
+compile(node, ctx) void
}
DouViewCompiler --> TagCompilerRegistry : "注册标签编译器"
TagCompilerRegistry --> ForeachTagCompiler
TagCompilerRegistry --> ListTagCompiler
TagCompilerRegistry --> CategoryTagCompiler
TagCompilerRegistry --> BreakTagCompiler
TagCompilerRegistry --> ContinueTagCompiler
ListTagCompiler --|> AbstractPortalLoopTagCompiler
CategoryTagCompiler --|> AbstractPortalLoopTagCompiler

性能考虑

  • 大数据集处理
    • 优先使用 {list} 的 limit(SQL 层限制)减少数据传输与渲染开销。
    • 使用 {foreach} 的 offset/limit 对内存数组进行切片,避免全量遍历。
    • 对复杂集合,尽量在 Portal 层完成过滤、排序与分页,模板只做展示。
  • 分页加载
    • 前端分页:结合 {list} 的 limit/offset 与后端分页参数,逐页拉取数据。
    • 无限滚动:AJAX 追加数据块,模板中用 {foreach} 渲染新增片段。
  • 循环元信息
    • 利用 $ctx->loops[name].total 与 iteration 做条件渲染(如首尾高亮、进度条)。
  • 安全与健壮性
    • {list}/{category} 的 module 必须为字面量,防止注入。
    • 合理使用 {else}/{listelse}/{categoryelse} 处理空数据场景。

故障排查指南

  • 常见错误
    • 缺少必要属性:如 {foreach} 缺少 from/item、{list} 缺少 module,会在编译期抛出语法错误。
    • 非法标识符:item/key/name 必须为合法标识符,否则报错。
    • 模块不可用:卸载模块或 features 关闭时,{list}/{category} 返回空数组,应走 else 分支。
  • 定位方法
    • 查看编译期错误信息,确认属性拼写与类型。
    • 检查 Portal 返回数据是否为空,确保模板 else 分支正确显示占位内容。
    • 对于大数据集,先验证 limit/offset 是否正确生效。

结论

DouPHP 模板引擎通过清晰的编译器分层与标签抽象,提供了强大而安全的循环遍历能力。{foreach} 适用于任意数组/对象的灵活遍历;{list}/{category} 则通过 Portal 白名单与 SQL 级限制,保障数据获取的安全与高效。配合 {break}/{continue}、offset/limit、else 分支与循环元信息,可覆盖绝大多数业务场景。遵循本文的最佳实践,可在保证安全性的前提下获得良好的性能表现。

附录

  • 循环控制标签速查
    • {break}:终止当前循环。
    • {continue}:跳过本次迭代。
  • 嵌套规则与最佳实践
    • 允许嵌套循环,但应避免过深嵌套导致渲染成本上升。
    • 外层循环尽量使用 {list} 的 limit 控制规模,内层再按需 {foreach}。
    • 始终提供 else 分支,提升空数据的用户体验。
    • 使用 name 明确命名循环,便于调试与样式控制。
添加日期:2026-10-05