--- 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`。 - **Logic / Service 禁止返回 `array`**(含 `@return array{...}`、`@return XxxEntity[]`);固定形状业务结果必须返回 **Entity**(见 §4「返回值规则」)。 - Model 若返回 `array`,必须结构明确,并用 PHPDoc 描述结构。 - 私有方法也必须有清晰业务语义,并写中文 PHPDoc(见 §5;纯 getter/setter 除外)。 - 复杂条件必须封装成有语义的方法。 - 魔法数字必须变成常量或枚举;**所有类常量必须写中文注释**(见下文「常量规则」)。 - 业务规则判断优先使用 `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 个标量参数。 ### 常量规则 - 魔法数字、魔法字符串必须提为 `const` 或枚举,禁止硬编码在业务逻辑中。 - **所有类常量**(`public` / `protected` / `private` const)必须有一行 `/** 中文说明 */`,**无豁免**。 - 注释须说明业务含义、取值语义或用途;禁止纯英文复述常量名(无信息增量 → 不合格)。 - Model 中状态类常量(`STATUS_*` 等)还须与 `@property status` 描述一致,列出各枚举值含义。 反例(禁止): ```php class GameServerLogic extends BaseLogic { private const SOURCE_CODE_LENGTH = 6; } ``` 正例: ```php class GameServerLogic extends BaseLogic { /** 渠道编号长度(位) */ private const SOURCE_CODE_LENGTH = 6; /** Session 有效 */ public const STATUS_ACTIVE = 1; } ``` --- ## 4. Logic / Service / Model 写法 ### Logic - Logic 是业务用例入口,一个 public 方法通常对应一个业务用例。 - Logic 主方法要像读业务流程。 - Logic 私有方法必须有业务语义与中文 PHPDoc(纯 getter/setter 除外)。 - Logic 可以查业务主体、调用 Model、调用公共 Service、做业务判断、控制事务、写业务日志、组装返回结果。 - 复杂查询条件应沉淀到 Model。 - 涉及多表写入、钱包变动、订单状态变更、活动领取、返水领取、游戏下注 / 派奖时,事务边界必须放在 Logic。 - 事务内禁止请求第三方接口、发送 MQ、发送短信、发送邮件、大量循环处理、复杂远程调用。 - **Logic 禁止返回 `array`**;业务结果组装为 Entity 后返回,由 Controller 再转为 HTTP 响应(如 `activeData()`)。 ### Service - Service 只放公共能力,例如第三方 SDK、center 调用、公共配置、公共计算、跨业务复用逻辑、基础设施封装。 - Service 禁止承载单业务用例编排。 - Service 禁止只做 Model 转发或 Logic 转发。 - Service 禁止为每张表创建一个 Service。 - 如果一个 Service 方法没有封装公共能力,不应创建。 - **Service 禁止返回 `array`**;对外暴露的结构化结果同样使用 Entity。 ### 返回值规则(Logic / Service) - **禁止** Logic / Service 方法声明返回 `array`(含关联数组、索引数组、`array{...}` 形状数组)。 - **必须** 使用 `app/entity/{业务域}/XxxEntity`(优先 `extends app\entity\BaseEntity`)承载固定形状业务数据;命名表达业务语义,如 `GrantSettleResultEntity`、`WalletStatisticsEntity`。 - Entity 使用 **public 属性** + 中文属性注释;字段名与接口/库表约定一致(通常 camelCase);`new XxxEntity([...])` 由 `BaseEntity` 填充。 - **多条记录**:禁止 `@return XxxEntity[]` 或裸 `array`;使用列表/汇总 Entity(如 `PlayerTaskListEntity`),在 Entity 内用 `/** @var XxxEntity[] */ public array $items` 等**有命名语义**的属性承载;Logic/Service 方法本身仍返回该 Entity 对象。 - **允许** 返回:`void`、标量(`bool` / `int` / `string` / `float`)、`?XxxEntity`(可空实体)、单个 Entity。 - **禁止** 直接返回 Model 对象;Model 查询结果须在 Logic 内映射为 Entity 后再返回。 - Controller 负责把 Entity 转为统一 HTTP 响应;**禁止** 在 Controller 里拼装本应由 Logic 产出的业务结构数组。 - `verify-slot-backend.sh` 对 git diff **新增行** 自动拦截 Logic/Service 的 `: array`、`@return array{...}`、`@return Xxx[]`;历史存量方法改 body 不触发。确需临时豁免的文件可在类 PHPDoc 首段标注 `@logic-service-array-exempt`(仅迁移期,须注明原因)。 ### Model - Model 负责数据查询和写入。 - Model 可以包含查询条件封装、scope、搜索器、关联关系、简单数据写入、常用查询方法。 - Model 禁止写复杂业务流程、控制事务、调用第三方接口、编排多个业务步骤。 - Model 方法命名必须表达查询或写入意图,例如 `findEnabledById()`、`existsClaimedRecord()`、`sumValidBetAmount()`、`createClaimRecord()`。 - 避免 `getData()`、`getList()`、`handle()`、`process()`。 #### Model 类 PHPDoc(必须) 凡 `app/model/**/*Model.php`(含 `app/**/model/` 下 Model),类上**必须**有完整 PHPDoc;**禁止**只有一行类说明、无 `@property`,或 `@property` 只有类型无中文说明。 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` / `create_time` / `update_time` 可简写为「创建时间」「更新时间」。 6. **类常量**:遵守上文「常量规则」,每个 `const` 一行 `/** 中文说明 */`。 **触达 Model 文件时**(新建、小改、补方法):若类 PHPDoc 缺 `@property` 或不完整,须在本次改动中补全,不得留空。 反例(禁止): ```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` / `protected` / `private` 方法必须写 PHPDoc**,首行用**中文**说明业务动作或结果(很多人看不懂英文方法名,英文命名不能替代 PHPDoc)。 **所有类常量**(`public` / `protected` / `private` const)必须有一行 `/** 中文说明 */`,见 §3「常量规则」,**无豁免**。 - `public`:一律必须(纯 getter/setter 除外,见下文)。 - `protected` / `private`:Logic 与 Service 中凡承载业务步骤的方法必须写;Model 的 `private` 若仅为薄封装且无语义增量可省略,但 `find*` / `mark*` / 访问 DB/Redis 的 `private` 仍必须写。 最小合格格式: ```php /** * 按对外 session_id 查询未过期且有效的 Launch Session。 * * @throws BusinessException 会话不存在或已关闭时 */ public static function findActiveBySessionId(string $sessionId): ?self ``` - 首行必须是中文,说明「做什么 / 业务结果」。 - 禁止只写 `/** get user */`、只有 `@param`/`@return` 类型而无中文业务说明。 - 禁止与签名完全重复的英文复述(无信息增量 → 不合格)。 ### 唯一豁免:纯 getter / setter 以下方法(含 `private`)可省略 PHPDoc: - 无业务分支、无事务、无外部调用、无查库写库的 `getXxx()` / `setXxx()`。 - 只读或只写入**单个**属性或 DTO 字段。 - 仅做字符串/ key 拼接、无业务判断的极简 accessor(如 `return $uid . '_' . $roundId`)。 ```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): - Logic / Service 返回 Entity:`@return` 写具体 Entity 类名(如 `@return GrantSettleResultEntity`),**禁止** `@return array{...}`。 - Model 返回 `array`:必须用 `@return array{...}` 写清结构(Logic / Service 不适用)。 - 参数为复杂 `array`:必须用 `@param array{...} $name` 写清结构。 - 可能抛出业务异常:必须写 `@throws`(中文说明触发条件)。 - 涉及状态流转、钱包、订单、活动、游戏交易:首行中文说明中须点明状态/资金影响。 Logic 返回 Entity 示例: ```php /** * 执行直接发奖入账并返回账单结果。 * * @return GrantSettleResultEntity * @throws BusinessException 钱包不可用或入账失败时 */ public function grantSettle(GrantSettleDTO $grantSettleDto): GrantSettleResultEntity ``` Model 返回 array 结构示例(仅 Model 层): ```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 外一律必须**:`public` / `protected` / `private` 均不得以「方法名够清晰」或「仅内部调用」为由省略 PHPDoc。 - **行内注释**解释为什么,不解释做什么;方法 PHPDoc 首行仍要写「做什么」(中文)。复杂业务、幂等、分布式锁、状态流转、第三方兼容、历史兼容逻辑须在行内注释说明原因。 --- ## 6. 异常、日志、返回 - 禁止返回 `false` 表示业务失败。 - 业务失败应抛出业务异常。 - 禁止吞异常。 - 捕获异常后必须记录必要上下文;如无法处理,必须继续抛出。 - 业务异常和系统异常要区分。 - 异常信息必须清晰,不能只写 `error`、`failed`。 - 关键业务必须打日志,例如钱包变动、游戏下注、游戏派奖、支付回调、活动领取、返水领取、第三方接口异常、风控命中、重要状态变更。 - 日志必须包含关键上下文,例如 `user_id`、`order_no`、`provider_code`、`provider_tx_id`、`round_id`、`amount`、`status`、`error`。 - 日志禁止记录密码、token、secret、私钥、支付密钥、银行卡完整号码、用户隐私数据、第三方签名密钥。 - 返回结构必须明确;Logic / Service 以 Entity 表达,不以裸 `array` 表达。 - 不直接返回 Model 对象。 - 不返回临时调试字段、SQL、异常堆栈、内部配置、secret、token、sign。 - Controller 将 Entity 转为 HTTP 响应;Logic / Service 不返回 HTTP Response。 --- ## 7. 幂等要求 涉及重复请求风险必须考虑幂等,包括第三方回调、钱包加减款、订单支付、活动领取、返水领取、游戏下注、游戏派奖、MQ 消费。 优先使用唯一索引保证幂等,并在代码中保留明确幂等判断。 --- ## 8. Agent 自查 完成代码前必须自查: - 命名是否清晰? - 方法是否过长? - 嵌套是否过深? - 是否有魔法数字或魔法字符串?是否已提为常量? - **所有类常量是否均有中文注释**(`/** 中文说明 */`)? - Logic 是否表达清晰业务流程? - Service 是否确实是公共能力? - Model 是否只做数据访问? - **Model 类 PHPDoc 是否含完整 `@property`(每个持久化字段 + 中文说明)?触达 Model 时缺则须补全。** - 所有 `const`(含 Logic / Service / Model 的 `private const`)是否均有中文注释? - 多表写入是否有事务? - 是否需要幂等? - 是否吞异常? - 日志是否有关键上下文? - 是否泄露敏感信息? - 返回数据是否明确? - Logic / Service 是否**未返回 `array`**,且固定形状结果已用 Entity? - 列表/汇总场景是否用列表 Entity 包装,而非 `@return XxxEntity[]` 或裸数组? - 是否直接返回 Model? - Logic / Service / Model 的 `public` / `protected` / `private` 方法是否均有**中文** PHPDoc 首行(纯 getter/setter 除外)? - 是否存在只有 `@param`/`@return` 类型、无中文业务说明的 PHPDoc? - Model 返回 array / 复杂数组参数 / 抛异常的方法,是否在通用 PHPDoc 之上补全了 `array{...}` 结构与 `@throws`? - Logic / Service 返回 Entity 的方法,`@return` 是否为具体 Entity 类名? - 是否存在「仅用于日志」的多余参数? - 是否存在「局部变量 + 聚合 DTO」双份承载同一业务字段? - 聚合 DTO 是否包含可推导的重复标量? --- ## 9. 优先级 当规则冲突时,按以下优先级处理: 1. 正确性 2. 安全性 3. 数据一致性 4. 可读性 5. 可维护性 6. 简洁性 7. 性能 除非已有明确性能瓶颈,否则不要为了性能牺牲可读性。