团队自定义编码规范如何继承 PSR?

解读

国内中大型团队普遍会基于 PSR 制定“企业级规范”,既要保持与社区接轨,又要解决业务痛点(如多模块并行、老代码兼容、CI 门禁)。面试官想确认两点:

  1. 候选人是否真正读过 PSR 原文,知道哪些条款是“必须”、哪些是“推荐”;
  2. 能否把“纸面规范”落地为“工程化动作”,让新老代码无缝过渡,而不是口头喊口号。

知识点

  1. PSR 分层:基础风格(PSR-1/12)、自动加载(PSR-4)、接口契约(PSR-3/6/7/11/14/16/18)、项目结构(PSR-13/15)。
  2. 规范继承的三种手段:
    a) 扩展:在 PSR 条款外追加“增量规则”(如命名空间必须带公司域名倒序、类后缀强制加 Service/Repository)。
    b) 替换:对 PSR 中“SHOULD”级条款升级成“MUST”,或把 120 字符行宽收紧到 100。
    c) 豁免:对历史目录声明豁免名单,并给出技术债偿还计划。
  3. 配套工具链:
    • PHP_CodeSniffer + phpcs.xml 规则集,自定义 ruleset.xml 通过 <rule ref="PSR12"> 继承,再用 <exclude name="PSR12.Files.FileHeader"/> 局部排除。
    • PHP-CS-Fixer 的 .php-cs-fixer.dist.php->setRules(['@PSR12' => true, 'array_syntax' => ['syntax' => 'short']]) 追加。
    • Composer scripts 加 lint 命令,配合 pre-push Git Hook 强制阻断。
    • GitLab-CI 或 GitHub Actions 里跑 phpcs --report=checkstyle,MR 页面直接展示违规行。
  4. 版本演进策略:
    • 先锁定 PSR-12 为 baseline,老代码打 tag,新代码必须 100 % 合规;
    • 每季度 review 一次,逐步把豁免目录缩小;
    • 重大框架升级时(PHP 8+),同步把规则集升级到 PER-CS2.0,减少二次迁移成本。

答案

“我们采用‘继承+增量’方式落地。
第一步,在工程根目录放一份 ruleset.xml,先 <rule ref="PSR12"/> 整体继承,保证与社区同频;
第二步,用 <rule ref="Generic.Arrays.DisallowLongArraySyntax"/> 追加公司级硬性要求,再把历史遗留目录 legacy/<exclude-pattern> 豁免,并注释 TODO 与偿还排期;
第三步,把 phpcs、php-cs-fixer 配置到 Composer scripts 与 Git Hook,CI 阶段强制 -n 模式,零警告才能合并;
第四步,规范文档用语雀统一维护,每次升级先提 RFC,评审通过再更新 ruleset 版本号,确保全员可回溯。这样既尊重 PSR 的开放性,又满足内部一致性,两年来我们 120 万行代码新模块合规率 100 %,老模块技术债下降 38 %。”

拓展思考

  1. 如果团队决定把 PSR-12 的“side effect 文件必须纯声明”条款降级为“推荐”,如何向社区回馈经验?
    可在内部先行 A/B:对 10 % 模块放宽限制,收集两个月故障率、回滚次数,用数据写博客反向输出,争取写进未来的 PER 讨论。
  2. 微服务拆分后,各仓库规范版本漂移怎么办?
    把 ruleset.xml 打成独立 Composer 包 company/coding-standard,版本号语义化,各服务 composer require --dev 固定小版本,升级时统一 MR 批量改。
  3. 当 PHP-FIG 发布 PER-CS2.0 并废弃 PSR-12 部分条款时,如何低成本切换?
    提前在 CI 加一条 allow_failure 任务跑新规则集,生成差异报告,评估自动修复比例;超过 90 % 可自动修复则直接全量跑 php-cs-fixer,剩余手工修改,整体迁移控制在一次迭代内完成。