文档目录
服务基类设计

简介

本文件围绕 DouPHP 框架的服务基类 BaseService 展开,系统阐述其设计理念与在业务服务层中的使用方式。重点包括:

  • 依赖注入与静态门面模式在服务层的结合使用
  • 如何在服务中访问数据库、请求上下文、语言包、视图等系统资源
  • ORM 的静态门面调用模式、事务管理与错误处理
  • 如何继承 BaseService 创建自定义服务类,并正确使用依赖注入与静态门面进行业务开发
  • 新增:后台菜单注册表与导航解析器的声明式配置模式

项目结构

DouPHP 将"服务"放在 core/service 下,BaseService 作为所有领域服务的抽象基类;ORM 模型位于 core/orm,静态门面对应 core/facade。典型调用路径为:控制器/入口 → 服务(继承 BaseService)→ ORM 静态门面或 DB 门面 → 数据持久化。

graph TB
A["控制器/入口"] --> B["服务(BaseService 子类)"]
B --> C["ORM 模型(静态门面)"]
B --> D["DB 门面"]
B --> E["Request 门面"]
B --> F["View 门面"]
B --> G["AdminMenuRegistry"]
B --> H["AdminNavResolver"]
C --> D
G --> H

核心组件

  • BaseService:服务基类,定义服务层对系统资源的访问约定与 ORM 使用规范。
  • AdminMenuRegistry:后台菜单注册表,采用声明式配置管理子菜单族与侧栏节点。
  • AdminNavResolver:后台活跃态解析器,基于路由名计算菜单激活状态。
  • AdminMenuService:后台菜单元数据服务,提供基础菜单键列表。
  • ORM Model:轻量级 ActiveRecord,提供静态门面式查询与写入能力。
  • DB 门面:底层数据库连接容器单例,支持查询构建器与事务。
  • Request 门面:HTTP 请求上下文封装,用于获取输入、路由信息、IP 等。
  • View 门面:模板引擎封装,用于渲染视图。

架构总览

服务层通过 BaseService 统一接入系统资源,遵循"读多写少用 ORM 静态门面,复杂聚合/原生 SQL 走 DB 门面"的原则。请求上下文由 shell 层解析后以参数形式传入服务,避免服务直接耦合 HTTP。新增的后台菜单系统采用声明式配置模式,通过注册表与解析器分离关注点,实现菜单数据的集中管理与动态解析。

sequenceDiagram
participant C as "控制器"
participant S as "服务(BaseService 子类)"
participant M as "ORM 模型"
participant DB as "DB 门面"
participant R as "Request 门面"
participant V as "View 门面"
participant MR as "AdminMenuRegistry"
participant NR as "AdminNavResolver"
C->>S : 调用业务方法(传入请求上下文参数)
S->>R : 读取必要上下文(如 ip/routeAction)
S->>MR : 获取菜单注册表
S->>NR : 解析导航状态
S->>M : 静态门面读写(如 create/find/paginate)
M-->>DB : 执行查询/更新
DB-->>M : 返回结果
S-->>C : 返回业务结果
Note over S,V : 如需渲染视图,可通过 View 门面

详细组件分析

BaseService 设计与职责

  • 角色定位:服务层抽象基类,不承载具体实现,仅约定资源访问与 ORM 使用方式。
  • 依赖注入:复杂查询或跨域逻辑建议通过构造函数注入专用 Reader/Query/Core 服务,保持服务薄而稳定。
  • 静态门面:数据库、视图、请求等系统能力通过门面就近解析,降低耦合。
  • ORM 约定:
    • 写:新增统一走 Xxx::create();按主键更新优先 hydrated 实例 fill()->save(),或直接 whereKey()->update()。
    • 读:with('relation')->find()/first()/get()/paginate(),命中 hydrated 实例后可直接使用关系与访问器。
    • 复杂查询:抽取到 Reader/Query/*Core 服务,常规 DI 注入。

后台菜单注册表与导航解析器

更新 新增的后台菜单系统采用声明式配置模式,将菜单数据与解析逻辑分离。

AdminMenuRegistry - 菜单注册表

  • 声明式配置:通过数组定义子菜单族与侧栏节点,支持条件显示、权限控制、徽章等特性。
  • 模块扩展:支持通过 admin/nav/*.php 文件动态加载已安装模块的菜单定义。
  • 条件求值:内置 passWhen() 方法支持 sign、feature、pure_mode 等多种条件判断。
  • 会员中心集成:通过 userCenterFamilies() 方法映射会员中心入口到对应菜单族。

AdminNavResolver - 导航解析器

  • 路由匹配:基于 routeMatches() 方法实现段边界通配匹配,支持精确匹配与模糊匹配。
  • 激活状态计算:根据当前路由名计算子菜单和侧栏节点的激活状态。
  • 页面级覆盖:支持 overrides 参数实现页面级别的菜单覆盖。
  • 无状态设计:纯函数实现,结果仅取决于入参,便于测试和复用。
classDiagram
class AdminMenuRegistry {
+subMenus() array
+sideNodes() array
+userCenterFamilies() array
+passWhen(array) bool
}
class AdminNavResolver {
+resolve(string, array) array
+emptyNav() array
+routeMatches(string, string) bool
+moduleSideNodes() array
}
class AdminMenuService {
+basicMenu() array
}
AdminMenuRegistry --> AdminNavResolver : "被解析器使用"
AdminMenuService --> AdminMenuRegistry : "获取基础菜单"

ORM 访问模式与静态门面

  • 入口双轨:默认静态为主(Model::where()/with()/query()/create()),new Model() 或容器 DI 仍合法。
  • 读路径:统一经 query()->find()/first()/get()/with() 获取 hydrated Model/Collection。
  • 写路径:统一经 fill()->save()/create()/query()->whereKey($id)->update()/destroy()。
  • 与 DB 共存:实体映射读写走 ORM;聚合、泛化、元信息、原生 SQL 走 DB 门面。
classDiagram
class Model {
+table : string
+primary : string
+fillable : array
+casts : array
+prefetchers : array
+with : array
+translatable : array
+bootIfNotBooted()
+__construct(attributes)
}
class DB_Facade {
+table(table) Connection
+beginTransaction()
+commit()
+rollback()
}
Model --> DB_Facade : "内部使用"

事务管理与错误处理

  • 事务边界:在涉及多表或多步写操作时,使用 DB 门面开启事务,确保原子性。
  • 异常回滚:捕获异常后执行回滚,并记录错误日志,保证一致性。
  • 推荐模式:try/catch 包裹事务块,finally 或 catch 中统一 rollback,成功则 commit。
flowchart TD
Start(["开始"]) --> Begin["开启事务"]
Begin --> WriteA["执行写操作A"]
WriteA --> CheckA{"是否成功?"}
CheckA --> |否| Rollback["回滚事务"]
Rollback --> EndErr(["结束(失败)"])
CheckA --> |是| WriteB["执行写操作B"]
WriteB --> Commit["提交事务"]
Commit --> EndOk(["结束(成功)"])

请求上下文与语言包

  • 请求上下文:shell 层负责从 Request 取 ip、routeAction、baseUrl 等,并以方法参数显式传入服务;服务自身不直接读取 Request。
  • 语言包:通过 helper 函数 locale()/lang()/lang_set()/lang_has/lang_all 访问当前语言与译串表。

视图与资源

  • 视图:通过 View 门面进行 assign/display/fetch,便于在需要时渲染模板。
  • 资源:Storage/attachment/Image/Zip/route 等资源能力通过对应门面或 helper 就近获取。

示例:继承 BaseService 创建自定义服务

  • 目标:创建一个文章服务,包含发布与详情查询。
  • 要点:
    • 继承 BaseService。
    • 复杂查询通过 DI 注入 Reader/Query 服务。
    • 写操作使用 ORM 静态门面 create()。
    • 读操作使用 with('relation')->find()/first()。
classDiagram
class BaseService
class ArticleService {
-reader
+publish(payload)
+detail(id)
}
class ArticleReader
class ArticleModel
ArticleService --|> BaseService
ArticleService --> ArticleReader : "构造注入"
ArticleService --> ArticleModel : "静态门面调用"

依赖关系分析

  • 松耦合:服务通过门面与 ORM 交互,避免直接依赖底层实现。
  • 内聚性:BaseService 聚焦于约定与编排,具体能力下沉至子服务与 Reader/Query。
  • 可测试性:通过 StaticFacade::swap()/clearResolvedInstance() 替换门面根对象,便于单元测试。
  • 新增:菜单系统通过注册表与解析器解耦,支持独立测试和维护。
graph LR
BaseService["BaseService"] --> ORM["ORM 模型"]
BaseService --> DBF["DB 门面"]
BaseService --> ReqF["Request 门面"]
BaseService --> ViewF["View 门面"]
BaseService --> MenuReg["AdminMenuRegistry"]
BaseService --> NavRes["AdminNavResolver"]
ORM --> DBF
MenuReg --> NavRes

性能考量

  • 预加载关系:使用 with('relation') 减少 N+1 查询。
  • 分页:列表场景使用 paginate,控制单次返回量。
  • 只读优化:复杂聚合/统计尽量放入 Reader/Query 服务,避免在 Service 中堆积重查询。
  • 事务粒度:缩小事务范围,减少锁竞争与超时风险。
  • 新增:菜单注册表使用静态缓存,避免重复加载模块文件。

故障排查指南

  • 事务未提交/回滚:检查 try/catch 是否正确包裹事务,并在异常分支执行 rollback。
  • 静态门面不可用:确认已引入对应门面类或使用命名空间别名。
  • 请求上下文为空:确认 shell 层已将必要字段作为参数传入服务方法。
  • 视图渲染异常:检查模板路径与变量赋值,必要时使用 fetch 调试输出。
  • 新增:菜单显示异常:检查 AdminMenuRegistry 的条件配置和 AdminNavResolver 的路由匹配规则。

结论

BaseService 为 DouPHP 服务层提供了统一的资源访问约定与 ORM 使用范式。通过依赖注入与静态门面相结合,既保证了服务的高内聚与低耦合,又提升了可测试性与可维护性。新增的后台菜单系统采用声明式配置模式,通过 AdminMenuRegistry 和 AdminNavResolver 实现了菜单数据的集中管理和动态解析,进一步增强了系统的可扩展性和可维护性。在实际开发中,建议遵循"简单 CRUD 走 ORM 静态门面,复杂查询下沉到 Reader/Query,事务边界清晰、异常回滚完备"的最佳实践。

附录

  • 快速参考:
    • 数据库:DB::table(...)->where(...)->select()/insert()/update()
    • 事务:DB::beginTransaction(); ... DB::commit(); DB::rollback();
    • 请求:request() 或 Request::...(由 shell 层传入参数)
    • 视图:View::assign()/fetch()/display()
    • ORM:Xxx::with('rel')->find()/first()/get()/paginate();Xxx::create()/fill()->save()
    • 新增:菜单注册:AdminMenuRegistry::subMenus() / sideNodes()
    • 新增:导航解析:AdminNavResolver::resolve(routeName, overrides)
添加日期:2026-10-05