引言
PHP 是动态类型语言,「类型错误直到运行时才炸」是它最大的痛点。静态分析在 CI 阶段就能发现类型不匹配、空值访问、未定义方法等隐患——不用跑起来就能抓住一大批 bug。本文以 PHPStan 为主线(业界事实标准),覆盖 Psalm、Rector 自动重构、PHP-CS-Fixer 规范,以及如何把质量门禁接进 CI。
前置:/php-testing-practice/(测试体系)、/php8-modern-features/(类型系统)、/php-composer-package-development/(工程化)。
目录
- 1. 为什么静态分析是 PHP 的救命稻草
- 2. PHPStan 安装与初体验
- 3. 错误等级:从宽松到严格
- 4. PHPDoc 与类型标注的威力
- 5. PHPStan 高级配置
- 6. Psalm:污点分析与安全扫描
- 7. Rector:自动升级与重构
- 8. PHP-CS-Fixer 与编码规范
- 9. 接入 CI:基线、增量与门禁
- 10. 速查表与一句话记忆
- 延伸阅读
1. 为什么静态分析是 PHP 的救命稻草
1.1 动态类型的代价
// 运行时才炸:$user 可能为 null,->email 直接 fatal
function sendEmail(User $user) { $user->email ...; }
静态分析不运行代码,靠类型推断就能发现 $user 可能是 null。
1.2 静态分析能抓什么
✓ 未定义方法/属性
✓ 可能为 null 的访问(空值安全)
✓ 类型不匹配(int 传给 string 参数)
✓ 死代码 / 未使用的参数
✓ 未定义变量
✓ 不可达分支
记忆:PHP 动态类型让类型错误拖到运行时才炸;静态分析不跑代码就能抓未定义方法、空值访问、类型不匹配——CI 第一道防线。
2. PHPStan 安装与初体验
2.1 安装
composer require --dev phpstan/phpstan
2.2 第一次扫描
./vendor/bin/phpstan analyse src --level 1
--level 是严格度。初次跑通常是「错误一大片」,用 --generate-baseline 收敛:
./vendor/bin/phpstan analyse src --level 5 --generate-baseline
# 生成 phpstan-baseline.neon,存量错误进基线,新错误继续拦
记忆:PHPStan 一条命令扫描、–level 控制严格度;初次引入用 –generate-baseline 把存量错误收进基线,后续只拦新增错误。
3. 错误等级:从宽松到严格
3.1 level 0-9 的含义
| Level | 含义 |
|---|---|
| 0-1 | 基础语法/未知符号 |
| 2-3 | 基本类型检查 |
| 4 | 更严格类型 + 未定义方法 |
| 5-6 | 参数类型、返回值 |
| 7-8 | 空值安全、泛型 |
| 9 | 极致严格(多数项目少见) |
3.2 推荐策略
新项目:目标 level 6-8
存量项目:先 level 4-5 + 基线,逐月升级
# phpstan.neon
parameters:
level: 6
paths:
- src
记忆:level 越高越严——常见推荐 6-8;存量项目从低 level + 基线起步,逐月往上升,别一次打满 9。
4. PHPDoc 与类型标注的威力
4.1 泛型/集合标注
PHPStan 靠 PHPDoc 理解复杂类型:
/**
* @param array<string, int> $counts
* @return list<Post>
*/
function loadPosts(array $counts): array { ... }
4.2 空值标注
/** @return User|null */
function findUser(int $id) { ... }
// 调用方
$user = findUser(1);
if ($user === null) { return; } // PHPStan 认可空值处理
记忆:PHPDoc 是 PHPStan 的眼睛——泛型 array<string,int>、返回值 @return User|null、参数 @param 都让类型推断更准。
5. PHPStan 高级配置
5.1 phpstan.neon 完整示例
parameters:
level: 7
paths:
- src
- tests
excludePaths:
- src/legacy
treatPhpDocTypesAsCertain: false # 减少误报
checkGenericClassInNonGenericObjectType: true
includes:
- phpstan-baseline.neon # 基线
5.2 忽略特定错误
// 代码内注释忽略
/** @phpstan-ignore-next-line */
$value = $someUnsafe->call();
// 或 phpstan.neon 里按路径忽略
parameters:
ignoreErrors:
- '#Call to an undefined method#': src/legacy/*
记忆:phpstan.neon 配置 level/paths/exclude 与基线;单个误报用 @phpstan-ignore-next-line,整片遗留用 ignoreErrors 规则。
6. Psalm:污点分析与安全扫描
6.1 安装与使用
composer require --dev vimeo/psalm
./vendor/bin/psalm --init
./vendor/bin/psalm
6.2 污点分析:追踪不安全数据
Psalm 能追踪「用户输入」流向敏感位置(SQL/输出/命令),发现注入类风险:
/** @psalm-taint-sink sql $query */
function runQuery(string $query) { ... }
// 用户输入直接进 SQL → Psalm 报警
$query = $_GET['q']; // taint source
runQuery("SELECT * FROM t WHERE x = '$query'"); // 报警!
记忆:Psalm 的杀手锏是污点分析——标记用户输入为 taint source、敏感操作为 sink,追踪不安全流向发现注入风险。
7. Rector:自动升级与重构
7.1 自动规则集
// rector.php
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPhpSets(php83: true) // 自动升级到 PHP 8.3 语法
->withSets([
LaravelLevelSetList::UP_TO_LARAVEL_11, // Laravel 升级规则
])
->withPaths([__DIR__ . '/app']);
7.2 干跑与应用
./vendor/bin/rector process src --dry-run # 预览改动
./vendor/bin/rector process src # 应用
常见自动重构:数组函数化、?-> 空安全、match 替换 switch、构造函数提升。
记忆:Rector 按规则集自动重构代码(PHP 版本升级、Laravel 升级、现代语法);–dry-run 预览再应用,大型升级的省力神器。
8. PHP-CS-Fixer 与编码规范
8.1 安装与规则
composer require --dev friendsofphp/php-cs-fixer
// .php-cs-fixer.php
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
])
->setFinder(PhpCsFixer\Finder::create()->in(__DIR__.'/src'));
8.2 与静态分析分工
| 工具 | 管什么 |
|---|---|
| PHP-CS-Fixer | 风格(空格/引号/导入顺序) |
| PHPStan/Psalm | 类型与逻辑正确性 |
记忆:PHP-CS-Fixer 管风格规范(PSR-12 + 团队自定义),PHPStan/Psalm 管类型正确性——一个管「长得好不好看」,一个管「对不对」。
9. 接入 CI:基线、增量与门禁
9.1 GitHub Actions 流水线
- name: PHPStan
run: ./vendor/bin/phpstan analyse src --no-progress --memory-limit=1G
- name: PHP-CS-Fixer (dry-run)
run: ./vendor/bin/php-cs-fixer fix --dry-run --diff
- name: Rector (dry-run)
run: ./vendor/bin/rector process src --dry-run
9.2 增量扫描:只查改动文件
大项目全量扫描慢,PR 只查改动行:
- name: PHPStan changed files
run: |
CHANGED=$(git diff --name-only origin/main...HEAD -- '*.php')
./vendor/bin/phpstan analyse $CHANGED --level 6
9.3 门禁策略
✓ CI 必跑:PHPStan + CS-Fixer
✓ 失败即拦截 PR(required check)
✓ 基线随季度下降(逐月删 baseline 条目)
记忆:CI 门禁 = PHPStan + PHP-CS-Fixer +(可选 Rector dry-run)全量/增量扫描,失败拦截 PR;基线逐季收敛,让存量错误只减不增。
10. 速查表与一句话记忆
| 工具 | 职责 | 命令 |
|---|---|---|
| PHPStan | 类型/逻辑分析 | analyse src –level 6 |
| Psalm | 类型 + 污点分析 | psalm |
| Rector | 自动重构/升级 | rector process –dry-run |
| PHP-CS-Fixer | 编码规范 | php-cs-fixer fix |
| 基线 | 存量错误收敛 | –generate-baseline |
一句话记忆:PHP 静态分析 = PHPStan 做类型/逻辑检查(–level 6-8,初次用 –generate-baseline 收存量错误)+ Psalm 加污点分析追踪注入风险 + Rector 自动升级/重构(–dry-run 预览)+ PHP-CS-Fixer 管编码规范(PSR-12);PHPDoc 是分析的「眼睛」(泛型/空值/返回类型);接入 CI 做全量或增量(git diff 只查改动文件)门禁、失败拦截 PR,基线逐季收敛——让「类型错误运行时才炸」变成「CI 就拦住」。
延伸阅读
- /php-testing-practice/ — 测试体系与 CI 门禁
- /php8-modern-features/ — 类型系统与强类型
- /php-composer-package-development/ — 工程化与 PSR 标准
- /php-oop-design-patterns/ — 代码结构设计
- /php-security-hardening/ — 安全加固(与污点分析互补)
- [[testing]] — 测试工程实践
- [[tools]] — 开发工具链
- PHPStan 文档
- Rector 文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。