如何配置 PHP-CS-Fixer 自动修复风格?
解读
面试官抛出这个问题,通常不是想听你背命令,而是考察三点:
- 你是否真正在团队里落地过代码规范,而不是“本地跑一下”就结束;
- 你是否了解国内主流研发流程(GitLab MR、钉钉/飞书机器人、CI 镜像源加速、内网 Composer 源);
- 你是否能把“个人工具”升级为“团队纪律”,即让风格检查成为门禁,而不是建议。
因此,回答时要体现“本地→Git 钩子→CI→团队共识”四级闭环,并给出可落地的文件模板和国内网络环境下的踩坑细节。
知识点
- PHP-CS-Fixer 与 PHPUnit 的版本兼容性矩阵(PHP 8.3 需要 3.46+)
- Composer 脚本钩子(scripts.post-install-cmd)自动下发 git hooks
- 国内镜像:hirak/prestissimo 已过时,应使用 repo.packagist 镜像 + cgr 或 composer config -g
- .php-cs-fixer.dist.php 与 .php-cs-fixer.php 的区别(后者可被 IDE 索引,前者轻量)
- 规则集:@PSR12、@PhpCsFixer、@Symfony 三者的差异与性能开销
- 增量检查:--using-cache=no 与 --diff 在 GitLab CI 中的耗时对比
- 危险规则:native_function_invocation、self_accessor 可能破坏旧项目
- 与 PhpStorm 集成:EditorConfig 优先级高于 IDE 设置,但低于 fixer
- 合规性:国内金融/政务项目需保留 <?php 头部注释,禁止 header_comment 自动覆盖
答案
我按“本地一键格式化→Git 提交拦截→CI 门禁→团队复用”四步落地,全部文件放在仓库根目录,新人 5 分钟完成。
-
安装与版本锁定
composer require --dev friendsofphp/php-cs-fixer:^3.46把 composer.lock 进版本库,避免 CI 拉取到新版导致规则差异。
-
配置规则(.php-cs-fixer.dist.php)
<?php declare(strict_types=1); $finder = PhpCsFixer\Finder::create() ->in([__DIR__.'/app', __DIR__.'/tests']) ->exclude('storage') ->name('*.php') ->notName('*.blade.php'); // 排除 Laravel 视图 return (new PhpCsFixer\Config()) ->setRiskyAllowed(true) ->setRules([ '@PSR12' => true, '@PhpCsFixer' => true, 'array_syntax' => ['syntax' => 'short'], 'native_function_invocation' => false, // 国内老旧扩展可能找不到函数 'header_comment' => ['header' => ''], // 政务项目禁止覆盖版权头 ]) ->setFinder($finder) ->setCacheFile(__DIR__.'/.php-cs-fixer.cache');缓存文件写进项目目录,Docker CI 时可挂载加速。
-
本地脚本(composer.json)
"scripts": { "cs-fix": "php-cs-fixer fix --config=.php-cs-fixer.dist.php", "cs-dry": "php-cs-fixer fix --dry-run --diff", "post-install-cmd": [ "@php -r \"file_exists('.git/hooks/pre-commit') || copy('build/pre-commit', '.git/hooks/pre-commit'); chmod('.git/hooks/pre-commit', 0755);\"" ] }新人执行 composer install 后自动装上 Git 钩子。
-
Git 钩子(build/pre-commit)
#!/usr/bin/env bash ROOT=$(git rev-parse --show-toplevel) LIST=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.php$') if [ -z "$LIST" ]; then exit 0; fi echo "Running PHP-CS-Fixer on staged files..." docker run --rm -v "$ROOT":/code -w /code \ registry.cn-shanghai.aliyuncs.com/acs/php:8.3-cli-alpine \ vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no $LIST git add $LIST使用阿里云镜像加速,保证内网无 VPN 也能 30 秒完成。
-
CI 门禁(.gitlab-ci.yml)
stages: [style, test] php-cs-fixer: stage: style image: registry.cn-shanghai.aliyuncs.com/acs/php:8.3-cli-alpine before_script: - composer config repo.packagist composer https://mirrors.aliyun.com/composer/ - composer install --prefer-dist --no-progress script: - composer cs-dry allow_failure: false任何风格错误直接阻断合并请求,MR 页面可见 diff。
-
团队共识 在 README 中写明“提交前只需 composer cs-fix”,并把 .php-cs-fixer.dist.php 纳入代码评审必看文件;每季度评估一次规则集,升级前先在 feature 分支跑全量测试,避免“风格升级”带来的伪回归。
拓展思考
-
微服务场景:多仓库如何复用一份规则? 把 .php-cs-fixer.dist.php 做成私有 Composer 包(company/coding-standard),各仓库 require --dev,规则更新只需改一处,CI 镜像层缓存也能复用。
-
大仓 monorepo:增量检查耗时仍超过 1 分钟? 使用 --path-mode=intersection 结合 git merge-base,让 fixer 只检查“与目标分支的差异文件”,在 500+ 模块下可把耗时降到 10 秒以内。
-
老旧项目:一次全量格式化导致 git blame 失效? 先创建 .git-blame-ignore-revs 文件,把“风格格式化”commit SHA 写进去,GitLab 15+ 与 PhpStorm 2023.2 均已支持识别,历史责任人可正常追溯。
-
合规审计:如何证明“上线代码一定经过风格检查”? 在 CI 里把 composer cs-dry 结果生成 junit.xml,上传到 GitLab 合并请求页面;再对接内部审计系统,MR 合入时自动写入“代码风格合规”电子水印,满足国内金融上线检查要求。