文档目录
模型层设计

简介

本文件面向后端开发者,系统化说明 DouPHP 前台模型层的数据访问模式与 ORM 使用方式。内容涵盖:

  • 数据表映射、关联关系、查询构建器用法
  • 业务规则验证与数据转换(casts/accessors)
  • 事件机制与钩子函数
  • 批量操作、分页与预加载
  • 数据缓存策略与性能优化建议

项目结构

前台模型位于 front/model,按业务域分目录组织;核心 ORM 能力由 core/orm 提供,包括 Model 基类、Builder 查询构造器、集合与关系等。前端的 Concerns 以可复用行为(如时间戳 accessor、URL 生成、分类信息注入等)增强模型。

graph TB
subgraph "前台模型"
P["Product"]
PC["ProductCategory"]
C1["HasAddTimeAccessors"]
C2["HasUrlAccessor"]
C3["HasCateInfoAccessor"]
C4["HasContentLift"]
C5["HasRelatedQuery"]
end
subgraph "核心ORM"
M["Model(基类)"]
B["Builder(查询构造器)"]
end
P --> M
PC --> M
P --> C1
P --> C2
PC --> C3
P --> C4
P --> C5
M --> B

核心组件

  • 模型基类 Model:提供 ActiveRecord 风格的数据访问、属性读写、类型转换、关联关系、全局作用域、模型事件、时间戳、多语言字段等能力。
  • 查询构造器 Builder:封装链式查询、预加载 with()、全局作用域应用、水合为模型集合、聚合方法透传等。
  • 前台 Concerns:通过 trait 形式为模型附加通用能力(如自动填充创建时间、生成 URL、分类信息注入、内容权重、关联查询辅助等)。

架构总览

前台业务模型继承核心 ORM 的 Model,并通过 Concerns 组合能力;查询通过 Builder 完成,支持 with 预加载、全局作用域、聚合方法与原生 SQL 共存。

classDiagram
class Model {
+string table
+string primary
+array fillable
+array casts
+array prefetchers
+array with
+array appends
+array translatable
+query()
+create(attributes)
+destroy(id)
+getTable()
+getKeyName()
+newFromBuilder(attributes)
+setAttribute(key,value)
+getAttribute(key)
+boot()
+deleting(callback)
+deleted(callback)
}
class Builder {
-Model model
-Connection query
-array eagerLoad
+with(relations)
+whereKey(id)
+orderBy(field,direction)
+get()
+first(field)
+find(id,field)
+paginate(...)
+__call(method,params)
}
class Product
class ProductCategory
class HasAddTimeAccessors
class HasUrlAccessor
class HasCateInfoAccessor
class HasContentLift
class HasRelatedQuery
Product --> Model : "继承"
ProductCategory --> Model : "继承"
Product ..> HasAddTimeAccessors : "使用"
Product ..> HasUrlAccessor : "使用"
ProductCategory ..> HasCateInfoAccessor : "使用"
Product ..> HasContentLift : "使用"
Product ..> HasRelatedQuery : "使用"
Model --> Builder : "newQuery()"

详细组件分析

模型基类 Model:数据访问与生命周期

  • 数据表映射:通过 $table 声明主表名;未声明时按类名蛇形推导。
  • 主键与实例键:$primary 定义主键字段;getKey()/getKeyName() 暴露键名与值。
  • 批量写入白名单:$fillable 控制 create/fill 的可写字段。
  • 类型转换:$casts 声明字段类型转换(如 datetime、json、int 等),读取时经 CastResolver 转换,写入时反向转换。
  • 默认预加载:$with 指定默认预加载的关系。
  • 声明式预热:$prefetchers 用于批量预热派生字段或附件。
  • 多语言字段:$translatable 与 $translatableModule 在 toArray 时按当前语言覆写。
  • 属性读写:getAttribute/setAttribute 支持 accessor/mutator 与 cast;getDirty/isDirty 跟踪变更。
  • 静态入口:query/create/destroy/__callStatic 统一对外 API。
  • 全局作用域:addGlobalScope/getGlobalScopes/withoutGlobalScope/withoutGlobalScopes。
  • 模型事件:deleting/deleted 注册监听,fireModelEvent 派发。

查询构造器 Builder:链式查询与预加载

  • 连接与表:构造时基于 Model::getConnection()->table(model->getTable()) 建立底层查询。
  • 全局作用域:在 get/paginate 等读路径前应用全局 scope,聚合方法也受保护。
  • 预加载 with:支持单关系、多关系、嵌套关系与闭包约束,归一化为 eagerLoad。
  • 终结方法:get/first/find/paginate 返回水合后的 Collection/Model;count/sum/value/exists 等聚合原样透传。
  • 水合流程:hydrateRows 将原始行转为模型集合,执行 eager load 与 PrefetchRunner,再触发 afterHydrate 回调。
  • 关系加载:eagerLoadRelations/loadRelation 根据 with 配置批量加载并匹配结果。
sequenceDiagram
participant U as "调用方"
participant Q as "Model : : query()"
participant B as "Builder"
participant DB as "底层Connection"
participant C as "Collection"
U->>Q : 调用 where(...)->with(...)->get()
Q-->>B : new Query 实例
B->>DB : 组装链式条件
B->>B : applyGlobalScopes()
B->>DB : select()
DB-->>B : 原始行数组
B->>B : hydrateRows()
B->>B : eagerLoadRelations()
B->>B : PrefetchRunner : : run()
B-->>U : Collection(已水合+预加载)

前台业务模型:产品与分类

  • 产品模型 Product:通常包含商品基础信息、价格、库存、状态等字段;可通过 casts 进行数值/日期/JSON 转换;通过 accessors 计算展示字段(如售价、是否在售);通过 relations 关联分类、品牌、图片等。
  • 产品分类模型 ProductCategory:维护分类层级与排序;常配合 HasCateInfoAccessor 提供分类信息注入;可与商品形成一对多关系。

提示:具体字段与关系请以各模型文件为准。以下为常见实践指引。

Concerns:可复用的模型行为

  • HasAddTimeAccessors:为模型提供创建时间字段的 accessor 与格式化输出。
  • HasUrlAccessor:为模型提供 URL 生成逻辑,便于模板渲染。
  • HasCateInfoAccessor:为分类相关模型注入分类信息到查询结果。
  • HasContentLift:为内容型模型提供权重/置顶等显示逻辑。
  • HasRelatedQuery:提供关联查询辅助方法,简化常见关联场景。

依赖关系分析

  • 模型对 ORM 的依赖:所有前台模型均依赖 core/orm/Model 提供的统一数据访问契约。
  • 查询构造器对底层的依赖:Builder 依赖底层 Connection 完成 SQL 拼装与执行,同时叠加 with 预加载与模型水合。
  • Concerns 对模型的增强:Concerns 以 trait 形式注入方法,不改变模型继承体系,提升内聚性。
graph LR
A["前台模型(Product/Category)"] --> B["核心ORM(Model)"]
B --> C["查询构造器(Builder)"]
C --> D["底层数据库连接(Connection)"]
A --> E["Concerns(时间/URL/分类/权重/关联)"]

性能与缓存策略

  • 预加载优先:使用 with('category','brand') 避免 N+1 查询;嵌套关系可用 'category.parent' 语法。
  • 声明式预热:利用 $prefetchers 批量计算派生字段(如封面图、价格区间),减少循环内重复计算。
  • 只读聚合:count/sum/value/exists 等聚合方法会应用全局作用域,保证统计口径一致。
  • 合理选择字段:first()/paginate() 中按需指定 field,减少不必要列传输。
  • 事务与批量:大批量更新/删除尽量使用 Builder 的 update/delete 链式方法;需要触发事件的批量删除使用 destroy([ids])。
  • 缓存建议:
    • 热点配置与字典数据可使用系统缓存服务(非 ORM 内置)进行缓存。
    • 列表页结果可按“查询条件哈希”作为缓存键,设置合理过期时间。
    • 预加载结果可在内存级缓存(请求级)避免重复计算。

故障排查指南

  • 主键非法传入:find/whereKey 要求标量主键,传入 null/空串/数组/对象会抛出异常,检查调用处参数类型。
  • 全局作用域影响统计:聚合方法会自动应用全局作用域,若发现 count 与 get()->count() 不一致,检查是否有 scope 修改了查询条件。
  • 事件中断删除:deleting 监听器返回 false 会中止删除,确认业务校验逻辑。
  • 脏字段判断异常:有 cast 的字段比较采用宽松比较,确保 isDirty/getDirty 符合预期。
  • 预加载失败:with 指定的关系不存在或返回非 Relation 会被忽略,检查关系方法命名与返回值。

结论

DouPHP 前台模型层以轻量级 ActiveRecord 为核心,结合 Builder 查询构造器与 Concerns 组合能力,提供了统一的 CRUD、关联、预加载、事件与类型转换机制。遵循本文的访问模式与最佳实践,可实现高内聚、低耦合且高性能的数据访问层。

附录:CRUD 与复杂查询示例

以下示例仅描述调用形态与要点,具体实现请参考对应源码位置。

  • 创建记录

    • 使用 Model::create([...]) 批量填充并保存,返回水合后的模型或 null。
    • 参考:core/orm/Model.php:496-508
  • 读取单条

    • 使用 Model::find($id) 或 Model::where(...)->first()。
    • 参考:core/orm/Builder.php:300-306
  • 读取列表与分页

    • 使用 Model::where(...)->with(['category'])->orderBy('sort','DESC')->paginate(10)。
    • 参考:core/orm/Builder.php:338-346
  • 更新记录

    • 使用 Model::whereKey($id)->update([...]) 或直接 $model->save()。
    • 参考:core/orm/Builder.php:422-447
  • 删除记录

    • 单条:Model::destroy($id) 触发 deleting/deleted 事件。
    • 批量:Model::destroy([$id1,$id2,...]) 逐条触发事件。
    • 参考:core/orm/Model.php:510-543
  • 复杂查询

    • 多条件:where/whereIn/order/group/having/join 等链式方法透传到底层 Connection。
    • 聚合:count/sum/avg/max/min/value/exists 等。
    • 参考:core/orm/Builder.php:422-447
  • 预加载与嵌套关系

    • with('category','brand') 或 with('category.parent')。
    • 参考:core/orm/Builder.php:187-230
  • 类型转换与访问器

    • 在模型中声明 $casts 与 get/setXxxAttribute 方法。
    • 参考:core/orm/Model.php:666-729
  • 事件与钩子

    • 使用 static::deleting/static::deleted 注册监听器。
    • 参考:core/orm/Model.php:238-298
  • 全局作用域

    • 使用 addGlobalScope 注册默认过滤条件;查询时使用 withoutGlobalScope 局部跳过。
    • 参考:core/orm/Model.php:300-326
    • 参考:core/orm/Builder.php:94-143
添加日期:2026-10-05