API 契约与代码生成
api 应用的接口以 api/contracts/*.php 为源。lumnd/plato-api-contract 生成控制器与 OpenAPI,
api/app/logic/ 是项目代码,生成器只在文件不存在时创建骨架,之后不会覆盖。
目录
| 位置 | 归属 | 说明 |
|---|---|---|
api/contracts/*.php |
人写 | DSL 契约与轻量 schema |
api/app/logic/*.php |
人写 | 业务入口,接收验证后的数组和 ApiContext |
api/app/control/ctl_*.php |
生成 | 路由、输入读取、验证与 Logic 调用 |
docs/api/openapi.json |
生成 | OpenAPI 3.1 文档 |
docs/api/index.html |
人写 | 读上面那份文档的 Swagger UI 页,开发期给前端调试用 |
api/manifest.json |
生成 | 生成物指纹和所有权 |
工具专用目录只有 contracts。项目不再维护 app/contract、app/dto 或 app/generated,也不覆盖
生成器模板——响应形状是配置回答的问题,不是抄一份模板改(见下)。
配置与命令
三个命令共用根 plato.config.php 的 api_contract:
'api_contract' => [
'contracts' => $root . '/api/contracts',
'output' => $root,
'controller-namespace' => 'api\\control',
'logic-namespace' => 'api\\logic',
'controller-dir' => 'api/app/control',
'logic-dir' => 'api/app/logic',
'exception' => common\exception\biz_exception::class,
'openapi' => 'docs/api/openapi.json',
'manifest' => 'api/manifest.json',
],
docker exec php83 sh -lc 'cd /data/web/platoadmin && vendor/bin/plato api:lint'
docker exec php83 sh -lc 'cd /data/web/platoadmin && vendor/bin/plato api:generate'
docker exec php83 sh -lc 'cd /data/web/platoadmin && vendor/bin/plato api:check'
独立的 vendor/bin/plato-api 仍支持 --config=/任意位置/options.php。显式配置文件可直接返回
api_contract 的选项数组,也可返回包含 api_contract 的完整 Plato 配置。
Schema 与 Logic
本项目使用 rules() 直接声明输入输出,写法与 Laravel FormRequest 一致,不再为纯数据搬运创建
DTO。控制器把读到的输入投影成数组传给 Logic,Logic 返回符合响应 schema 的数组。确实需要强类型
领域边界的项目仍可把 readonly DTO class 传给 endpoint,生成器会继续负责构造和投影。
请求的每个字段必须声明 required、nullable 或 default: 之一,否则 lint 报
rules.presence_undeclared——「字段缺席时 Logic 拿到什么」要写出来,不能靠默认行为猜。布尔字段
不能 required:validate 把 false 读成没传,两者无法区分,只能给 default: 或 nullable。
投影的来源是控制器读到的 $input,不是 $validator->validated():后者只返回带了规则的字段并且丢掉
null,拿它投影会让「没有任何规则要检查」的字段静默消失。
生成的控制器怎么答
参数错误抛 biz_exception。 plato.config.php 的 exception 注册了它,生成的 action 因此写成
throw \common\exception\biz_exception::refuse($validator->errors());——参数错误和业务拒绝走同一条
路,都由 common\middleware\catcher 渲染成 {code:-2, msg, data:{errors}},本项目没有第二种失败
机制,控制器里也不再有信封。这条契约是 Lumnd\PlatoApiContract\Runtime\Refusal,biz_exception
实现的那个静态工厂;api:generate 在写任何文件之前先检查它,类名写错是配置错误而不是运行期崩溃。
成功答 resp::response(0, $data)。 框架自己的信封,msg 交给框架填 successful,不再是
code::message(code::OK)——成功的那句话在生成期没法多语言化,要么写死一种语言,要么交给框架。要拿回
本地化就得覆盖 action 模板,那是另一件事,本项目目前不做。
认证
契约只声明 auth,取值 required(默认)、optional、none。公开接口显式写 auth: 'none'。
该值直接生成到控制器 $actions:
'logout' => [
'methods' => ['POST'],
'auth' => 'required',
],
PlatoPHP 按这个值决定 check_purview_handle 怎么跑:none 根本不调用,optional 调用但允许答
「没人」,required 调用且必须拿到身份,拿不到框架自己答 401。本项目 api 只用 none 与
required。因此没有 purview 配置、api_access.php、permission DSL 或控制器内的重复鉴权。
当前契约包含:
| 方法与路径 | 需要登录 | Logic |
|---|---|---|
POST /auth/send_code |
否 | api\\logic\\auth_send_code |
POST /auth/login |
否 | api\\logic\\auth_login |
POST /auth/refresh |
否 | api\\logic\\auth_refresh |
POST /auth/logout |
是 | api\\logic\\auth_logout |