Files
ulthon_admin/.agents/rules/ulthon-file-override-mechanism.md
augushong 600ee5b085 refactor(agents): 删除 tools:agent:publish 命令,补全 helper.php 函数覆盖机制,修正多处文档 bug
- 删除 tools:agent:publish 整套(不再兼容多套 IDE,只保留 .agents 目录)
- helper.php 全部 27 个全局函数加 if (!function_exists()) 包裹,项目侧可在 app/common.php 定义同名函数覆盖
- ulthon-file-override-mechanism.md 新增全局函数覆盖章节
- AGENTS.md 删除智能体指导章节,PHP 8+ 改为 8.0+,MySQL 8+ 改为 5.7+
- AGENTS.md 项目结构树补全 tests/、extend/think/、source/ 子项
- README.md PHP 版本统一为 8.0+
- ulthon-base-app-architecture SKILL 路径补 admin/service/ 段
- ulthon-tools-http-call SKILL 补 --page-data 参数
- ulthon-naming-convention 补元数据头
- ulthon-update-workflow 统一 extend/think/ 归类
2026-07-21 19:35:28 +08:00

115 lines
6.8 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` 里的所有全局函数都有 `if (!function_exists('xxx'))` 包裹。项目侧如需覆盖某个全局函数的行为(例如修复框架 bug 或调整实现),只需在 `app/common.php``include helper.php` 语句**之前**定义同名函数即可。
加载顺序:`app/common.php` 先执行 → 项目侧函数先定义 → `include helper.php``if (!function_exists())` 检查发现已存在 → 跳过框架版本。
示例:
```php
// app/common.php
function sysconfig($group, $name = null, $default = null) {
// 项目侧自定义实现
return my_custom_logic($group, $name, $default);
}
include App::getRootPath() . '/extend/base/helper.php';
```
注意:该机制是"函数级覆盖",与本文档前述三类"文件级覆盖"是独立机制。helper.php 中已包裹的函数清单见源码(截至当前版本共 27 个:`__url``flow_url``password``xdebug``sysconfig``array_format_key``auth``get_session_admin``read_header_token``json_message``unparse_url``build_upload_url``event_handle_result``event_handle_string``event_view_content``event_view_replace``event_view_replace_js``event_response``app_file_path``array_to_table``format_bytes``parse_bytes``get_store_value``set_store_value``get_site_version_key``format_null_value``ctype_numeric`)。
## 相关文件
- `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` 加载定时器配置)