Files
cursor/rules/php-clean-code.mdc
ray zhou 1bcb6120dd ok
2026-05-29 17:23:17 +08:00

319 lines
12 KiB
Plaintext
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.

---
description: PHP clean code rules for readable, maintainable and clear backend code
globs:
- "**/*.php"
alwaysApply: true
---
# PHP Clean Code Rules
目标:生成和修改 PHP 后端代码时,优先保证 **可读、可维护、清晰、简单、易排查**。
分层职责遵循 `Backend Layering Rules`。本规则只约束代码写法质量。
---
## 1. 核心原则
- 优先清晰,不写炫技代码。
- 一个方法只表达一个明确动作。
- 代码应像业务流程一样容易阅读。
- 复杂逻辑必须拆成有业务语义的私有方法。
- 不要为了减少行数牺牲可读性。
- 不允许用注释弥补糟糕命名。
- 禁止无意义的 `Helper`、`Util`、`CommonService`、`Manager`。
- 禁止创建只做转发、没有业务价值的中间层。
---
## 2. 命名规则
禁止用模糊变量名承载核心业务数据:
- `$data`
- `$list`
- `$info`
- `$tmp`
- `$row`
- `$result`
- `$res`
- `$arr`
变量、方法、类名必须表达业务含义。
推荐命名风格:
- `$userInfo`
- `$rebateConfig`
- `$walletTransaction`
- `$validBetAmount`
- `$claimResult`
- `getEnabledUser()`
- `ensureCanClaim()`
- `createWalletTransaction()`
- `claimRebateInTransaction()`
布尔变量必须表达真假语义,例如 `$isEnabled`、`$isClaimed`、`$hasPermission`、`$canWithdraw`。
避免无意义方法名:`handle()`、`process()`、`doSomething()`、`getData()`、`getList()`。
如果必须使用 `handle()` / `process()`,需要有明确上下文,例如 `handlePaymentCallback()`、`processSettledBet()`。
---
## 3. 方法规则
- 方法长度建议不超过 50 行。
- 方法嵌套不超过 3 层。
- 优先使用 early return / early throw。
- 方法参数一般不超过 3 个。
- 参数超过 3 个时,优先使用 DTO、Value Object 或专门数据结构。
- 核心业务入参不要直接使用含糊的 `array`。
- 返回 `array` 时必须结构明确,并用 PHPDoc 描述结构。
- 私有方法也必须有清晰业务语义。
- 复杂条件必须封装成有语义的方法。
- 魔法数字必须变成常量或枚举。
- 业务规则判断优先使用 `ensureXxx()` 方法。
- 不要把查询、判断、写入、返回组装全部堆在一个大方法里。
### 参数与日志
- **禁止**为打日志、排障单独增加与该方法业务无关的参数(反例:`findPlatformById($platformId, $gameCode)` 且 `$gameCode` 只出现在 `Log::error`)。
- **查询 / 校验类方法**`find*`、`ensure*`)的参数必须等于该步骤的查询条件或判断依据。
- 跨多步共享的排障字段(如 `game_code`)应在 **用例编排方法**public Logic 入口或 `resolveXxx` 编排 private集中记录子步骤日志只写本子步骤真实使用的字段`platform_id`、`provider_code` 等)。
- 若多步都需要同一追溯上下文且步骤 ≥4再引入 readonly `XxxResolveContext`**禁止**向每个 private 方法重复挂相同标量。
### 编排上下文与聚合 DTO
- **禁止**编排方法先把子 DTO / Context 字段拆成局部变量,再原样传入聚合 DTO双份拷贝。反例`$uid = $dto->uid` 后 `new StartDTO($dto, $ctx, $uid, ...)`。
- **禁止**聚合 DTO 构造函数同时接收「可从已有只读字段推导」的重复标量。反例:同时存 `gameLaunchDto` 与 `gameCode`。
- 聚合上下文 DTO 应 **只组合** 子对象;派生值用 `uid()` / `providerCode()` 等 accessor 从子对象读取,或在用例方法内直接使用子 DTO 字段(二选一,不并存)。
- 子步骤 private 方法若需多字段,优先传 **一个** 聚合 DTO而不是再拆 3 个标量参数。
---
## 4. Logic / Service / Model 写法
### Logic
- Logic 是业务用例入口,一个 public 方法通常对应一个业务用例。
- Logic 主方法要像读业务流程。
- Logic 私有方法必须有业务语义。
- Logic 可以查业务主体、调用 Model、调用公共 Service、做业务判断、控制事务、写业务日志、组装返回结果。
- 复杂查询条件应沉淀到 Model。
- 涉及多表写入、钱包变动、订单状态变更、活动领取、返水领取、游戏下注 / 派奖时,事务边界必须放在 Logic。
- 事务内禁止请求第三方接口、发送 MQ、发送短信、发送邮件、大量循环处理、复杂远程调用。
### Service
- Service 只放公共能力,例如第三方 SDK、center 调用、公共配置、公共计算、跨业务复用逻辑、基础设施封装。
- Service 禁止承载单业务用例编排。
- Service 禁止只做 Model 转发或 Logic 转发。
- Service 禁止为每张表创建一个 Service。
- 如果一个 Service 方法没有封装公共能力,不应创建。
### Model
- Model 负责数据查询和写入。
- Model 可以包含查询条件封装、scope、搜索器、关联关系、简单数据写入、常用查询方法。
- Model 禁止写复杂业务流程、控制事务、调用第三方接口、编排多个业务步骤。
- Model 方法命名必须表达查询或写入意图,例如 `findEnabledById()`、`existsClaimedRecord()`、`sumValidBetAmount()`、`createClaimRecord()`。
- 避免 `getData()`、`getList()`、`handle()`、`process()`。
#### Model 类 PHPDoc新建 / 大改必须)
凡 **新建** 或 **大改** `app/model/**/*Model.php`(含 `app/**/model/` 下 Model类上必须有完整 PHPDoc**禁止** 只写类型、不写字段说明。
1. **类注释**`表名` + 一行业务说明(对齐 DDL `COMMENT` 或需求文档)。
2. **`@property`**:每个持久化字段一行,格式 `@property type $field 中文说明`(对齐表字段 `COMMENT`)。
3. **易混字段必须区分**:例如 `id`自增主键vs `session_id`对外标识vs `launch_request_id`(链路追踪)。
4. **状态字段**:写清「见 `STATUS_*` 常量」或列出枚举含义(与常量注释一致)。
5. **时间字段** `created_at` / `updated_at` 可简写为「创建时间」「更新时间」。
6. **状态类常量**`STATUS_*` 等):每个常量一行 `/** 中文说明 */`,与 `@property status` 描述一致。
反例(禁止):
```php
/**
* @property int $id
* @property string $session_id
*/
class GameLaunchSessionModel extends Model
```
正例(参照 `GGameModel`、`GameLaunchSessionModel`
```php
/**
* game_launch_session 游戏启动 Session 表。
*
* @property int $id 自增主键(表内行 ID非对外 session_id
* @property string $session_id 对外 Session 标识(如 sess_*
* @property int $status Session 状态,见 STATUS_* 常量
*/
class GameLaunchSessionModel extends Model
{
/** Session 有效 */
public const STATUS_ACTIVE = 1;
}
```
---
## 5. PHPDoc 与注释
### 默认规则(必须)
**Logic / Service / Model 的 `public` 方法必须写 PHPDoc**,首行用**中文**说明业务动作或结果(很多人看不懂英文方法名,英文命名不能替代 PHPDoc
`protected` 方法若承载业务步骤(非纯 getter/setter同样必须写中文 PHPDoc。
最小合格格式:
```php
/**
* 按对外 session_id 查询未过期且有效的 Launch Session。
*
* @throws BusinessException 会话不存在或已关闭时
*/
public static function findActiveBySessionId(string $sessionId): ?self
```
- 首行必须是中文,说明「做什么 / 业务结果」。
- 禁止只写 `/** get user */`、只有 `@param`/`@return` 类型而无中文业务说明。
- 禁止与签名完全重复的英文复述(无信息增量 → 不合格)。
### 唯一豁免:纯 getter / setter
仅以下方法可省略 PHPDoc
- 无业务分支、无事务、无外部调用、无查库写库的 `getXxx()` / `setXxx()`。
- 只读或只写入**单个**属性或 DTO 字段。
```php
// 可豁免
public function getUid(): int
{
return $this->uid;
}
// 不可豁免 — 必须中文 PHPDoc虽像查询但含业务条件与持久化
public static function findActiveBySessionId(string $sessionId): ?self
```
**不可豁免**(即使名称像 getter也必须写中文 PHPDoc
- `find*` / `create*` / `mark*` / `ensure*` / `is*` / `has*` / `launch*` / `close*` 等业务方法。
- 任何访问数据库、Redis、第三方接口、抛业务异常的方法。
### 附加要求(在通用中文 PHPDoc 之上)
除满足上文「默认规则」外,以下场景还须**额外**写明对应标签或结构(不在列表里也不能省略通用 PHPDoc
- 返回 `array`:必须用 `@return array{...}` 写清结构。
- 参数为复杂 `array`:必须用 `@param array{...} $name` 写清结构。
- 可能抛出业务异常:必须写 `@throws`(中文说明触发条件)。
- 涉及状态流转、钱包、订单、活动、游戏交易:首行中文说明中须点明状态/资金影响。
返回 array 结构示例:
```php
/**
* 查询用户钱包汇总。
*
* @return array{
* user_id: int,
* nickname: string,
* balance: int
* }
*/
```
复杂 array 参数示例:
```php
/**
* @param array{
* user_id: int,
* amount: int,
* order_no: string,
* remark?: string
* } $walletChangePayload
*/
```
### 废话与行内注释
- **禁止废话 PHPDoc**:无信息增量、纯英文复述签名 → 不合格。
- **除纯 getter/setter 外一律必须**:不得以「方法名够清晰」为由省略 PHPDoc。
- **行内注释**解释为什么,不解释做什么;方法 PHPDoc 首行仍要写「做什么」(中文)。复杂业务、幂等、分布式锁、状态流转、第三方兼容、历史兼容逻辑须在行内注释说明原因。
---
## 6. 异常、日志、返回
- 禁止返回 `false` 表示业务失败。
- 业务失败应抛出业务异常。
- 禁止吞异常。
- 捕获异常后必须记录必要上下文;如无法处理,必须继续抛出。
- 业务异常和系统异常要区分。
- 异常信息必须清晰,不能只写 `error`、`failed`。
- 关键业务必须打日志,例如钱包变动、游戏下注、游戏派奖、支付回调、活动领取、返水领取、第三方接口异常、风控命中、重要状态变更。
- 日志必须包含关键上下文,例如 `user_id`、`order_no`、`provider_code`、`provider_tx_id`、`round_id`、`amount`、`status`、`error`。
- 日志禁止记录密码、token、secret、私钥、支付密钥、银行卡完整号码、用户隐私数据、第三方签名密钥。
- 返回结构必须明确。
- 不直接返回 Model 对象。
- 不返回临时调试字段、SQL、异常堆栈、内部配置、secret、token、sign。
---
## 7. 幂等要求
涉及重复请求风险必须考虑幂等包括第三方回调、钱包加减款、订单支付、活动领取、返水领取、游戏下注、游戏派奖、MQ 消费。
优先使用唯一索引保证幂等,并在代码中保留明确幂等判断。
---
## 8. Agent 自查
完成代码前必须自查:
- 命名是否清晰?
- 方法是否过长?
- 嵌套是否过深?
- 是否有魔法数字或魔法字符串?
- Logic 是否表达清晰业务流程?
- Service 是否确实是公共能力?
- Model 是否只做数据访问?
- 新建/大改 Model 是否有类注释 + 带中文说明的 `@property`易混字段、status 是否写清)?
- `STATUS_*` 等状态常量是否有中文注释?
- 多表写入是否有事务?
- 是否需要幂等?
- 是否吞异常?
- 日志是否有关键上下文?
- 是否泄露敏感信息?
- 返回数据是否明确?
- 是否直接返回 Model
- Logic / Service / Model 的 `public` 方法是否均有**中文** PHPDoc 首行(纯 getter/setter 除外)?
- 是否存在只有 `@param`/`@return` 类型、无中文业务说明的 PHPDoc
- 返回 array / 复杂数组参数 / 抛异常的方法,是否在通用 PHPDoc 之上补全了 `array{...}` 结构与 `@throws`
- 是否存在「仅用于日志」的多余参数?
- 是否存在「局部变量 + 聚合 DTO」双份承载同一业务字段
- 聚合 DTO 是否包含可推导的重复标量?
---
## 9. 优先级
当规则冲突时,按以下优先级处理:
1. 正确性
2. 安全性
3. 数据一致性
4. 可读性
5. 可维护性
6. 简洁性
7. 性能
除非已有明确性能瓶颈,否则不要为了性能牺牲可读性。