Composer 的 require-dev 不适合放 PHPStan/PSALM,因其会导致依赖膨胀、autoload 冲突及 CI 版本不统一;应改用 create-project 独立安装并指定版本,配合 --load-from=. 和显式 autoloader 配置确保正确加载。

为什么 composer.json 的 require-dev 不适合放 PHPStan/PSALM?
直接把 phpstan/phpstan 写进 require-dev 会导致项目依赖膨胀,尤其当多个子包都这么做时,vendor/ 里会重复安装不同版本的扫描器,还可能因 autoload 冲突报 Class not found。更关键的是,CI 流程里你通常需要固定扫描器版本、统一配置,而不是随项目开发依赖一起浮动。
推荐做法是用 Composer 的 bin 功能配合独立工具包管理:
- 在根目录创建专用配置文件(如
phpstan.neon),不放进vendor/ - 用
composer create-project --no-install phpstan/phpstan:1.10.5 tools/phpstan单独拉取指定版本到tools/目录 - 通过
composer.json的scripts定义命令:"phpstan": "php tools/phpstan/bin/phpstan"
如何让 psalm 在 Composer 脚本中自动加载项目 autoloader?
默认执行 vendor/bin/psalm 时,它只认自己的 psalm.xml 和内置 autoloader,不会自动包含你的 composer.json 中定义的 autoload 或 autoload-dev 规则——结果就是扫到 Tests* 类时直接报 Cannot resolve stub class。
必须显式告诉 Psalm 加载路径:
- 确保
psalm.xml里有<projectfiles></projectfiles>段,并包含<directory name="src"></directory>和<directory name="tests"></directory> - 在
composer.json的scripts中加--load-from=.参数:"psalm": "vendor/bin/psalm --load-from=." - 如果项目用了自定义命名空间映射(比如
"MyLib\": "library/"),还得在psalm.xml的<autoloader></autoloader>下写死路径:<autoloader>vendor/autoload.php</autoloader>
php-cs-fixer 配置文件不生效?检查 .php-cs-fixer.dist 和工作目录
Composer 执行脚本时,php-cs-fixer 默认只读当前工作目录下的 .php-cs-fixer.dist(注意不是 .php-cs-fixer.php)。如果你把它放在项目根目录,但通过 Makefile 或 CI 脚本 cd 进某个子目录再调 composer run fix,就会静默失败——没报错,但什么都没修。
安全做法是强制指定配置路径:
- 把配置文件统一命名为
.php-cs-fixer.php(PHP 格式更灵活) - 在
composer.json的scripts中写全路径:"cs-fix": "php-cs-fixer fix --config=.php-cs-fixer.php --path-mode=intersection" -
--path-mode=intersection很重要:它让工具只处理既在配置finder范围内、又在当前命令所给路径下的文件,避免误扫vendor/
CI 中并行跑多个扫描器,怎么避免 composer install 重复耗时?
GitHub Actions 或 GitLab CI 里,如果每个扫描步骤都执行一次 composer install,光 autoload 生成和依赖解析就能吃掉 30 秒以上。实际没必要——所有扫描器共享同一套 vendor/autoload.php,只要保证它们能访问就行。
最轻量的解法是分两步:
- 第一步:运行
composer install --no-progress --prefer-dist --optimize-autoloader,并缓存vendor/目录 - 第二步:各扫描命令全部走
vendor/bin/xxx,不额外 install;若用了前面说的tools/独立安装方式,则跳过第一步,直接缓存tools/ - 注意:PHPStan 1.10+ 默认启用内存缓存,CI 中建议加
--no-cache参数,否则不同 job 之间可能读到脏状态
工具链越往底层走,越容易卡在路径、autoload 和缓存这三个点上。别信“装上就能用”,先确认它到底加载了哪些文件、用了哪个 autoloader、缓存存在哪。


















