简介
本指南面向需要在 DouPHP 中构建模块化界面的开发者,系统阐述“组件化”的设计思想与在 DouPHP 中的落地方式。文档围绕以下目标展开:
- 解释组件化的设计理念与在 DouPHP 中的实现路径
- 给出自定义组件的创建步骤(文件结构、数据绑定、事件处理)
- 说明内置组件的使用方法与扩展机制
- 提供可复用的 UI 组件与业务组件示例
- 介绍组件间通信模式与状态管理策略
- 总结性能优化技巧与调试方法
项目结构
DouPHP 的组件化体现在三层协同:
- 模板引擎层:编译式模板引擎负责将模板资源解析为可执行代码,并提供变量作用域、过滤器、预处理器等能力,支撑“片段/区块”级别的复用。
- 业务服务层:以 Fragment 为代表的“内容碎片”服务,提供树形组装、附件处理、关联盒子等能力,是“内容型组件”的数据与行为中心。
- 前台渲染层:通过模型与语言包将碎片数据按唯一标记组织,供模板按需引用,形成“可插拔的内容块”。
graph TB
A["控制器/路由"] --> B["模板引擎<br/>DouView"]
B --> C["编译产物缓存"]
B --> D["模板资源<br/>.dwt/.htm/.tpl"]
A --> E["业务服务<br/>FragmentService"]
E --> F["ORM/数据库"]
E --> G["附件服务"]
A --> H["前台模型<br/>Fragment"]
H --> I["语言包/国际化"]
D --> J["主题/模板目录"]
核心组件
- 模板引擎组件(DouView)
- 职责:模板解析、编译、缓存、上下文变量注入、全局 HTML 转义开关、前置过滤器扩展点。
- 关键点:fetch/display/renderResource/compileSource;编译修订号控制缓存失效;模板路径白名单与安全校验。
- 内容碎片组件(Fragment 系列)
- 后台服务:FragmentService 负责碎片树构建、新增/更新/删除、图片附件存储、与“内容盒子”的关联切换。
- 前台模型:Fragment::fragment() 按 mark 索引返回成品数组,支持多语言与文本行拆分,便于模板直接消费。
- 模板视图组件(fragment.htm)
- 职责:后台碎片的列表展示、表单编辑、与内容盒子的关联操作入口。
架构总览
下图展示了从请求到页面渲染的完整链路,以及“碎片组件”在其中的角色。
sequenceDiagram
participant U as "用户"
participant R as "路由/控制器"
participant S as "FragmentService"
participant M as "Fragment(前台模型)"
participant T as "DouView(模板引擎)"
participant V as "模板(.dwt/.htm)"
U->>R : 访问页面
R->>M : 获取碎片数据 fragment()
M-->>R : 按 mark 索引的碎片数组
R->>T : assign(变量) + fetch("模板名")
T->>T : 解析/编译/缓存命中判断
T->>V : 渲染模板并输出HTML
V-->>U : 最终页面
详细组件分析
模板引擎组件(DouView)
- 设计要点
- 编译管线:Prefilter → Lexer → Parser → CodeGenerator,运行时通过 RenderContext 暴露变量与作用域。
- 安全与隔离:模板路径白名单、路径穿越防护、可选的全局 HTML 转义。
- 缓存策略:基于源文件与编译产物时间戳、版本头(COMPILE_REVISION/VERSION)判定是否重编。
- 关键流程
- fetch:准备上下文、开启缓冲、调用 renderResource 并返回结果。
- renderResource:定位模板、检查是否需要重编译、写入编译产物、include 执行。
- compileSource:执行预处理器后交由编译器生成 PHP。
- 扩展点
- registerPrefilter:在编译前对源做变换,适合做模板级增强或动态注入。
- setEscapeHtml:统一控制输出转义,提升安全性。
flowchart TD
Start(["进入 fetch"]) --> Ctx["准备上下文与循环状态"]
Ctx --> Render["renderResource 解析模板"]
Render --> NeedRecomp{"需要重编译?"}
NeedRecomp -- 否 --> Include["include 编译产物"]
NeedRecomp -- 是 --> Compile["compileSource 编译"]
Compile --> WriteCache["写入编译缓存"]
WriteCache --> Include
Include --> End(["返回渲染结果"])
内容碎片组件(Fragment 系列)
- 后台服务(FragmentService)
- 树形构建:根据 parent_id 递归组装 child,同时拼装 content(图片/文本)与 box_list(关联盒子)。
- 增删改:insert/update/delete 结合附件服务完成图片上传/重命名/删除,审计日志记录。
- 关联切换:toggleLinkBox 在碎片与“内容盒子”之间切换关联。
- 前台模型(Fragment)
- 接口:静态方法 fragment() 返回按 mark 索引的成品数组,关闭特性时返回空数组。
- 数据处理:多语言适配、文本按行拆分、附件 URL 转换。
- 后台视图(fragment.htm)
- 列表与编辑:展示碎片树、编辑表单字段(名称、标记、图片、文本、链接、首页显示、父级分组),提供与内容盒子关联的操作入口。
classDiagram
class FragmentService {
+buildFragmentList(parent_id, current_id, home) array
+insert(data) int
+update(data) void
+delete(id, data) array
+toggleLinkBox(id) void
}
class Fragment_前台模型 {
+fragment() array
}
class DouView {
+assign(var, value) void
+fetch(template) string
+display(template) void
+compileSource(name, source) string
}
FragmentService --> Fragment_前台模型 : "读取/写入数据"
DouView --> Fragment_前台模型 : "模板侧消费数据"
自定义组件创建步骤
- 文件结构建议
- 模板资源:放在主题或公共模板目录,使用 .dwt/.htm/.tpl 后缀,遵循模板引擎白名单。
- 数据模型:若涉及持久化,参考 Fragment 模型,定义表映射、类型转换、预取器。
- 业务服务:封装复杂逻辑(如树形组装、附件处理、关联关系),对外暴露清晰方法。
- 视图模板:提供列表、表单、详情等视图,使用模板标签进行条件渲染与循环。
- 数据绑定
- 控制器/门面通过模板引擎的 assign 将数据注入上下文,模板中以 $ctx-> 或模板变量形式消费。
- 对于“碎片类”组件,推荐按 mark 索引组织数据,便于模板按需引用。
- 事件处理
- 前端交互通过模板内联脚本或外部 JS 触发 AJAX,调用后端服务接口,再由服务更新数据并返回结果。
- 可在服务层抛出领域异常并重定向,配合模板提示用户。
- 示例:创建一个“卡片组件”
- 模板:card.dwt,包含标题、图片、描述、链接。
- 模型:Card 模型,定义字段与附件映射。
- 服务:CardService,提供列表、详情、创建、更新、删除等方法。
- 视图:后台管理页使用 fragment.htm 的模式组织卡片列表与编辑表单。
- 集成:在页面模板中通过模板引擎引入 card.dwt,并传入数据。
内置组件使用方法与扩展机制
- 内置组件
- 内容碎片:通过前台模型 Fragment::fragment() 获取并按 mark 引用,适合首页内容、公告、轮播等场景。
- 模板引擎:通过 DouView 提供的 assign/fetch/display 进行变量注入与模板渲染,支持预处理器扩展。
- 扩展机制
- 模板预处理器:registerPrefilter 在编译前对模板源做变换,可用于注入通用片段或动态语法。
- 过滤器注册:StandardFilters 已注册常用过滤器,可按需扩展更多过滤逻辑。
- 模块开关:通过 module.php 配置启用/禁用模块,控制菜单与导航显示。
组件间通信模式与状态管理策略
- 通信模式
- 模板到服务:通过表单提交或 AJAX 调用后端服务,服务更新数据后返回结果。
- 服务到模板:服务将数据组织为模板友好的结构(如按 mark 索引),由模板渲染。
- 跨组件共享:通过模板上下文变量或全局配置(如 features.*)控制组件行为。
- 状态管理
- 前端状态:建议在模板中使用局部变量与循环状态,避免全局污染。
- 后端状态:在服务层维护事务性操作,确保数据一致性;必要时使用缓存或队列异步处理。
- 特性开关:通过配置项(如 features.fragment)控制组件是否启用,便于灰度发布与回滚。
依赖关系分析
- 组件耦合
- 模板引擎与模板资源:强耦合,但通过白名单与路径校验保障安全。
- 服务与模型:服务依赖模型进行数据读写,模型负责数据映射与预处理。
- 视图与服务:视图仅负责展示,逻辑下沉至服务,降低耦合。
- 外部依赖
- 附件服务:用于图片上传、重命名、删除。
- 语言包:支持多语言内容。
- 配置系统:控制模块开关与功能特性。
graph LR
Tpl["模板引擎<br/>DouView"] --> |渲染| View["模板资源<br/>.dwt/.htm/.tpl"]
Svc["业务服务<br/>FragmentService"] --> |读写| Model["数据模型<br/>Fragment"]
Svc --> |调用| Attach["附件服务"]
Model --> |使用| Lang["语言包"]
Svc --> |读取| Config["配置系统"]
性能考虑
- 模板编译缓存:利用 DouView 的编译缓存机制,减少重复编译开销;合理设置 COMPILE_REVISION 控制缓存失效。
- 数据查询优化:在服务层使用合适的查询条件与排序,避免全表扫描;对大对象使用分页或懒加载。
- 附件处理:批量上传时注意磁盘 IO 与网络延迟,必要时使用异步任务。
- 模板渲染:减少模板中的复杂逻辑,将计算下沉至服务层;合理使用过滤器与预处理器。
- 特性开关:通过配置项关闭不需要的组件,减少不必要的查询与渲染。
故障排查指南
- 常见问题
- 模板未找到:检查模板路径是否在白名单内,且位于 template_dir 下。
- 编译失败:查看编译错误日志,确认模板语法正确;必要时临时关闭缓存以定位问题。
- 数据为空:检查特性开关(如 features.fragment)是否启用;确认数据库记录是否存在。
- 附件异常:检查附件服务配置与权限;确认上传目录可写。
- 调试方法
- 启用调试模式:在开发环境开启详细错误信息。
- 打印上下文:在服务层打印关键变量,确认数据流转。
- 逐步验证:从模板到服务再到模型,逐层验证数据与逻辑。
结论
DouPHP 的组件化以模板引擎为核心,结合服务层与模型层,实现了“内容碎片”的可插拔与复用。通过清晰的职责划分、安全的模板解析、灵活的服务扩展,开发者可以高效构建模块化界面。建议在实际项目中遵循本文档的结构与最佳实践,持续提升代码质量与可维护性。
附录
- 快速开始
- 启用碎片特性:在配置中开启 features.fragment。
- 创建碎片:通过后台视图添加碎片,设置名称、标记、图片、文本、链接。
- 模板引用:在前台模板中通过 Fragment::fragment() 获取并按 mark 引用。
- 参考路径
- 模板引擎:core/web/template/DouView.php
- 碎片服务:admin/service/fragment/FragmentService.php
- 碎片模型:front/model/fragment/Fragment.php
- 后台视图:admin/view/fragment.htm
- 模块配置:config/module.php