Files
ulthon_admin/.agents/rules/ulthon-file-override-mechanism.md
augushong 46f0d62852 docs(rules): 新增文件级覆盖机制规则并修正索引职责互斥
- 新增 ulthon-file-override-mechanism.md:阐述 app/ 覆盖 extend/base/ 的三类机制(视图模板、include 标签、app_file_path)与不支持覆盖的反例清单
- ulthon-base-app-architecture:升格原孤立描述为独立小节"5. 文件级覆盖机制"
- ulthon-timer:在 app_file_path 覆盖说明处增加规则引用
- AGENTS.md:登记新规则到框架内置规则索引
- ulthon-rules-manager:修订索引职责(AGENTS.md 仅 ulthon-、PROJECT.md 仅 project-,互斥不重复维护);索引格式从 4 列改为 3 列与项目实际一致;维护规则补充对称约束;泛化"迁移现有内容"章节
2026-07-19 00:30:05 +08:00

95 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 文件级覆盖机制app/ 覆盖 extend/base/
> 来源框架内置ulthon-
> 作用域:所有"框架默认文件 → 业务同名文件"的覆盖场景视图模板、include 标签、`app_file_path()` 加载的 PHP/配置/模板)
> 触发条件:需要替换框架内置视图/模板/跳转页/异常页/定时任务配置/CURD 模板/版本升级钩子时加载
## 规则内容
### 概述
Ulthon Admin 在"类继承覆盖"之外,还提供"文件级覆盖"能力:业务侧在 `app/` 下放置与 `extend/base/` 同路径、同名的文件,框架运行时会优先加载 app 版本。
该机制只对**特定类型**的文件生效,不是所有文件都支持。覆盖能力分三类:
- 机制一:视图模板覆盖(最核心、最隐蔽,控制器 `fetch()` 自动生效)
- 机制二:模板内 `{include}` 标签覆盖(与机制一同源)
- 机制三:`app_file_path()` 辅助函数显式覆盖(需调用方主动使用)
### 机制一:视图模板覆盖
- 实现位置:`extend/think/view/driver/Think.php:160-257``parseTemplate()` 方法
- 不走 `app_file_path`,由视图驱动自身完成三级回退查找:
1. `app/<module>/view/<template>.html`最高优先app 应用视图)
2. `extend/base/<module>/view/<template>.html`(框架兜底视图)
3. `<root>/view/<template>.html`(根目录全局视图,最低优先)
- 触发场景:所有控制器 `fetch()` 渲染视图时(经 `ViewBase::fetch()``think.View::fetch()``Think::fetch()``parseTemplate()`
- 业务侧用法:在 `app/<module>/view/` 下放同名 `.html` 即可覆盖框架视图
### 机制二:模板内 `{include}` 标签覆盖
- 实现位置:`extend/think/Template.php:1192-1248``parseTemplateFile()` 方法
- 查找逻辑:与机制一相同的三级查找(`app_path` / `base_app_path` / `view_app_path`,由 `Think.php:196-198` 注入到 Template 实例)
- 前缀规则:
- `@/` 开头:从根目录定位(三级路径都设为同一个 `view_root_path`
- `@` 开头:从控制器视图目录定位(用 `base_view_path`
- `./` 开头:相对当前模板文件目录
- 无前缀:走完整三级查找
- 触发场景:模板编译时解析 `{include file="..."}` 标签
### 机制三:`app_file_path()` 辅助函数
- 定义位置:`extend/base/helper.php:392-400`
- 加载入口:`app/common.php:6`(唯一 include 点;这是 AGENTS.md 中明确的全局辅助函数唯一特许)
- 查找层级(两级回退):
1. `app/<相对路径>`(最高优先)
2. `extend/base/<相对路径>`(兜底)
- 函数签名约束(来自源码注释):
- 第一个参数 `$file_path` 不要以 `/` 开头
- 不需要以 `app` 开头
- 函数内部会自动定位 `app/``extend/base/`
#### 已知调用点(业务侧可在 app/ 同路径下覆盖其中任一)
| 调用点 | 相对路径参数 | 文件类型 | 业务意义 |
|---|---|---|---|
| `config/app.php:35` | `common/tpl/think_exception(_debug).tpl` | 异常模板 | 调试异常页 |
| `config/app.php:37,39` | `common/tpl/dispatch_jump.tpl` | 跳转模板 | success/error 跳转页 |
| `TimerServiceBase:19,71` | `common/command/timer/config.php` | 配置数组 | 定时任务列表(可被业务定制) |
| `AdminUpdateCodeServiceBase:23` | `admin/service/adminUpdateCodeData/{version}.php` | PHP 钩子文件 | 版本升级时执行的迁移代码 |
| `MigrateBase:207` | `common/command/curd/migrate.tpl` | 视图模板 | CURD 生成迁移代码的模板 |
### 反例:以下场景不支持文件级覆盖
下列路径与机制都不走 app/base 覆盖,业务侧在 `app/` 下放同名文件**不会**生效:
- `AdminInitServiceBase::requireData()``extend/base/admin/service/AdminInitServiceBase.php:139`):用 `__DIR__` 直接定位 `extend/base/admin/service/adminInitData/`,不支持 app 覆盖
- 命令注册(`config/console.php`):空 `commands` 数组,命令通过 SPI 自动扫描 `app/common/command/`,不走覆盖
- 语言包(`config/lang.php`):标准 ThinkPHP 机制,不支持 app/base 覆盖
- 路由(`route/app.php`):空文件,无覆盖机制
- 静态资源(`public/static/`Filesystem disk 加载,无覆盖机制
- 配置目录(`config/*`):标准 ThinkPHP 配置加载,无 app/base 覆盖机制
### 与"类继承覆盖"的关系
类继承层面的 Base/App 覆盖(如 `app\<Class>` extends `base\<Class>Base`)属于**另一套机制**,详见技能:[ulthon-base-app-architecture](../skills/ulthon-base-app-architecture/SKILL.md)。本规则只讲"文件级"覆盖,不涉及类的继承重写。
### 业务侧使用范例
- 覆盖异常页样式:在 `app/common/tpl/think_exception.tpl` 放同名文件
- 覆盖定时任务配置:在 `app/common/command/timer/config.php` 放同名文件(实际项目已在用)
- 覆盖某控制器视图:在 `app/admin/view/<controller>/<action>.html` 放同名文件
- 覆盖 CURD 迁移模板:在 `app/common/command/curd/migrate.tpl` 放同名文件
## 相关文件
- `extend/base/helper.php``app_file_path()` 定义)
- `app/common.php`(辅助函数唯一加载入口)
- `extend/think/view/driver/Think.php`(视图驱动 `parseTemplate()`
- `extend/think/Template.php`(模板编译 `parseTemplateFile()`
## 相关技能
- [ulthon-base-app-architecture](../skills/ulthon-base-app-architecture/SKILL.md)
- [ulthon-timer](../skills/ulthon-timer/SKILL.md)(使用 `app_file_path` 加载定时器配置)