# MVC 框架层契约

## 职责边界

本层（`mvc/v2/`）只放**框架核心**，不放业务代码。
业务代码在 `pdc/`，见 `pdc/.Codex/AGENTS.md`。

## 核心文件

| 文件 | 职责 |
|------|------|
| `Mvc.php` | 路由解析、参数绑定、dispatch |
| `Action.php` | 控制器基类，JSON 响应、登录校验钩子 |
| `GridActions.php` | DevExtreme CustomStore 三端点 trait |
| `Model.php` | 数据模型基类，grid/save/remove 通用实现 |
| `View.php` | 视图渲染 |
| `Lang.php` | 多语言加载与取值 |
| `mvcFunction.php` | 全局函数：`dd()`, `dump()`, `lg()`, `viewSet()`, `viewLoad()`, `clientIp()`, `getSemiangle()`, `formatNum()` |

## 修改原则

- 改框架前先确认：业务层实现不了才改框架
- 框架方法签名变更必须同步检查所有 `pdc/` 里的调用点
- `Action::json()` / `success()` / `error()` 响应格式不得改动，业务层依赖此格式

## PHP 版本兼容

**框架层按 PHP 8.1 编写**，生产环境为 PHP 8+，8.0 及以下不再支持。

下限由 composer 依赖锁死：`phpoffice/phpspreadsheet` 要求 `^8.1`、`workerman/workerman` 要求 `>=8.1`。

可以放心使用：
- 构造函数属性提升（`public function __construct(private $x)`）
- 联合类型（`int|string`）、`mixed` 类型
- `match` 表达式
- Nullsafe operator `?->`
- Named arguments 调用
- `str_contains()` / `str_starts_with()` / `str_ends_with()`
- 枚举 `enum`、`readonly` 属性、`never` 返回类型（8.1）

不要使用 8.2 及以上的语法（`readonly class`、DNF 类型、8.3 类型化类常量、8.4 属性钩子等）。
动态属性必须显式声明在类体里（8.2 起已弃用）。

版本口径以根契约 [../../.claude/CLAUDE.md](../../.claude/CLAUDE.md) 为准，要放宽先改那里。

## 路由规则

URL 格式：`/{模块名}/{方法名}`

- PATH_INFO 解析：最后一段是方法名，倒数第二段是模块名
- 类名自动拼接 `Action` 后缀：`users` → `usersAction`
- 支持 `/v/值` 位置传参和 `/k/键/值` 键值传参
- 支持 `.html` 伪静态后缀

## Grid 协议（框架侧）

`GridActions` trait 的三个方法是固定协议，不得修改方法签名：
- `grid()` → 调 `$this->gridModel()->grid($this->request)`
- `save()` → body 含 `key` 走 update，不含 key 走 insert
- `remove()` → body `{"key": 值}` 走 delete

`Model::grid()` 返回固定结构 `["data" => [...], "totalCount" => N]`。
