简介
本设计文档聚焦 DouPHP 框架的模型层,系统性阐述 ORM 的数据映射、关系定义、查询构建器实现,以及模型关注点(Concerns)复用机制。文档覆盖数据验证规则、业务逻辑封装、事务处理策略,并通过具体代码路径展示模型的 CRUD、关联查询与数据转换。重点解释模型层如何与数据库交互,并给出性能优化建议。
项目结构
DouPHP 的模型层位于 core/orm 与 core/model/concerns 两大区域:
- core/orm:提供轻量级 ActiveRecord 基类 Model、查询构造器 Builder、集合 Collection、关系抽象 Relation 及 HasOne/HasMany 等关系实现,以及字段类型转换器 CastResolver。
- core/model/concerns:以 Trait 形式提供通用能力,如内容字段修改器 ContentMutators、按分类筛选 HasCategoryFilter 等,供业务模型按需混入。
graph TB
subgraph "ORM核心"
M["Model"]
B["Builder"]
C["Collection"]
R["Relation(抽象)"]
H1["HasOne"]
H2["HasMany"]
CR["CastResolver"]
end
subgraph "模型关注点"
CM["ContentMutators"]
HCF["HasCategoryFilter"]
end
M --> B
B --> C
M --> R
R --> H1
R --> H2
M --> CR
M -.-> CM
M -.-> HCF
核心组件
- Model:ActiveRecord 基类,负责表名、主键、批量写入白名单、字段类型转换、预加载、多语言字段、事件、全局作用域、属性访问/修改、保存/删除等。
- Builder:查询构造器,封装链式查询、全局作用域应用、with 预加载、水合为 Model/Collection、prefetch 执行。
- Collection:模型集合,提供 map/filter/pluck/keyBy 等操作,兼容数组/迭代/JSON。
- Relation:关系抽象,定义 eager 加载契约;HasOne/HasMany 实现一对一/一对多。
- CastResolver:字段类型转换,支持基础类型、日期时间、附件、多语言数据对等。
- Concerns:ContentMutators 统一内容字段清洗与格式化;HasCategoryFilter 提供按分类及其子孙过滤的能力。
架构总览
模型层采用“基类 + 构造器 + 关系 + 关注点”的分层组合:
- 读路径:Model::query() → Builder → Connection → hydrateRows → with 预加载 → PrefetchRunner → afterHydrate 回调 → Collection。
- 写路径:Model::create/fill/save → 脏值检测 → 落库 → 事件触发。
- 关系:通过 Relation 抽象统一 eager 加载接口,HasOne/HasMany 分别实现一对一/一对多匹配。
- 类型转换:CastResolver 在读取时进行格式转换,写入时进行存储态转换。
- 关注点:Trait 注入通用行为,避免重复代码。
sequenceDiagram
participant U as "调用方"
participant M as "Model"
participant B as "Builder"
participant Q as "Connection"
participant P as "PrefetchRunner"
U->>M : query()/where()/with()
M->>B : newQuery()
B->>Q : table(...).where(...)
Q-->>B : 原始行集
B->>B : hydrateRows()
B->>P : run(rows, model)
P-->>B : 完成
B-->>U : Collection<Model>
详细组件分析
Model:数据映射与生命周期
- 数据映射:通过 $table/$primary/$fillable/$casts/$translatable 声明表、主键、可写字段、类型转换、多语言字段。
- 属性访问:getAttribute/setAttribute 支持 accessor/mutator、cast、关系懒加载。
- 生命周期:bootIfNotBooted/boot/bootTraits 注册全局作用域与 trait 钩子;deleting/deleted 事件;save/destroy 触发事件。
- 连接:setConnectionResolver/getConnection 对接底层 DB 门面。
- 静态入口:__callStatic 转发到 Builder,便于 Model::where()/with()/count() 等链式调用。
classDiagram
class Model {
-string $table
-string $primary
-array $fillable
-array $casts
-array $prefetchers
-array $with
-array $appends
-array $translatable
-array $attributes
-array $original
-array $relations
-bool $exists
-bool $timestamps
+getTable()
+newQuery()
+create(attrs)
+destroy(id)
+setAttribute(key,val)
+getAttribute(key)
+getDirty()
+syncOriginal()
+fireModelEvent(event)
}
Builder:查询构建器与预加载
- 全局作用域:applyGlobalScopes 在终结读路径一次性应用,聚合方法也受保护。
- 预加载:with('a.b') 支持嵌套与约束闭包;eagerLoadRelations/loadRelation 将结果匹配回父模型。
- 水合:hydrateRows 将原始行转为 Model/Collection,并执行 prefetch 与 afterHydrate 回调。
- 安全:find/whereKey 强制标量主键,防止误用。
flowchart TD
Start(["进入 get/paginate"]) --> ApplyScope["应用全局作用域"]
ApplyScope --> ExecSQL["执行底层查询"]
ExecSQL --> Hydrate["水合为 Model/Collection"]
Hydrate --> Eager{"有 with 预加载?"}
Eager -- 是 --> LoadRel["加载关系并匹配"]
Eager -- 否 --> Prefetch["执行声明式 prefetch"]
LoadRel --> Prefetch
Prefetch --> After["执行 afterHydrate 回调"]
After --> End(["返回 Collection"])
关系系统:Relation、HasOne、HasMany
- Relation:定义 addEagerConstraints/getEager/match/getResults 契约,支持 nested 与 constraint。
- HasOne:一对一,match 时将结果映射为单个 Model。
- HasMany:一对多,match 时将结果映射为 Collection。
classDiagram
class Relation {
-Model $parent
-Model $related
-string $foreignKey
-string $localKey
+setEagerOptions(nested, constraint)
+addEagerConstraints(models)
+getEager() Collection
+match(models, results, name)
+getResults() mixed
}
class HasOne
class HasMany
Relation <|-- HasOne
Relation <|-- HasMany
字段类型转换:CastResolver
- 读取方向:int/float/bool/string/json/array/datetime/date/timestamp/attachment/data_lang 等。
- 写入方向:set_int/set_float/set_bool/set_string/set_json/set_datetime,确保存储态一致。
- 扩展:register(name, callback) 支持自定义 cast。
flowchart TD
In(["写入/读取"]) --> Split["拆分类型与参数"]
Split --> Read{"读取方向?"}
Read -- 是 --> ApplyRead["apply(): 格式化为业务就绪值"]
Read -- 否 --> ApplyWrite["applySet(): 转换为存储态"]
ApplyRead --> Out(["输出"])
ApplyWrite --> Out
模型关注点(Concerns)
- ContentMutators:统一标题、关键词、描述、排序、价格等字段的清洗与格式化,减少重复代码。
- HasCategoryFilter:按分类及其子孙树过滤记录,支持单分类、多分类、逗号字符串、空值透传。
classDiagram
class ContentMutators {
+setTitleAttribute(value,data,mode)
+setSlugAttribute(value,data,mode)
+setDefinedAttribute(value,data,mode)
+setKeywordsAttribute(value,data,mode)
+setDescriptionAttribute(value,data,mode)
+setSortAttribute(value,data,mode)
+setAddTimeAttribute(value,data,mode)
+setPriceAttribute(value,data,mode)
+setPromotePriceAttribute(value,data,mode)
}
class HasCategoryFilter {
+scopeFilterByCategory(query, catIds)
}
依赖关系分析
- Model 依赖 Builder 提供查询能力,依赖 Relation 体系表达关联,依赖 CastResolver 做类型转换。
- Builder 依赖底层 Connection(通过 Model::getConnection),并在终结读路径应用全局作用域与预加载。
- Concerns 通过 Trait 注入 Model,不引入额外耦合。
graph LR
Model --> Builder
Model --> Relation
Relation --> HasOne
Relation --> HasMany
Model --> CastResolver
Model -.uses.-> ContentMutators
Model -.uses.-> HasCategoryFilter
性能考虑
- 使用 with 预加载关系,避免 N+1 查询。
- 合理使用全局作用域,避免不必要的 where 条件叠加。
- 使用 find/whereKey 限定主键为标量,防止误查。
- 使用 CastResolver 统一时间/附件/多语言字段转换,减少业务侧重复处理。
- 使用 hasMany/hasOne 的 eager 匹配,降低内存占用。
- 分页查询结合 Builder::paginate,减少单次数据量。
故障排查指南
- 主键非法:find/whereKey 会抛出异常,检查传入主键是否为标量。
- 预加载未生效:确认关系方法存在且返回 Relation 实例;检查 with 路径是否正确。
- 类型转换异常:检查 casts 声明是否匹配字段实际存储形态;json/array 解析失败会记录警告。
- 事件未触发:确认通过 destroy/find→delete 走 ORM 路径,而非直发 SQL 删除。
结论
DouPHP 模型层以轻量 ActiveRecord 为核心,配合 Builder 查询构建器、Relation 关系体系、CastResolver 类型转换与 Concerns 通用能力,形成高内聚、低耦合的数据访问层。通过预加载、全局作用域、类型转换与事件机制,既保证了易用性,又兼顾了性能与可维护性。建议在业务模型中优先使用 ORM 提供的读写与关系能力,并将通用逻辑下沉至 Concerns,保持代码清晰与可扩展。
附录:CRUD与关联示例路径
- 创建与保存:Model::create / fill/save
- 参考路径:Model.php:497-508、Model.php:707-729
- 查询与分页:Model::where().with().orderBy().paginate()
- 参考路径:Builder.php:262-371、Builder.php:338-346
- 关联查询:HasOne/HasMany 的 with('relation') 与 lazy loading
- 参考路径:Relation.php:65-96、HasOne.php:77-92、HasMany.php:75-90
- 数据转换:casts 声明与 CastResolver
- 参考路径:CastResolver.php:51-138、CastResolver.php:140-174
- 通用字段清洗:ContentMutators
- 参考路径:ContentMutators.php:24-112
- 分类筛选:HasCategoryFilter::scopeFilterByCategory
- 参考路径:HasCategoryFilter.php:47-77