PHP 静态分析与代码质量:PHPStan、Psalm、Rector 与 CI 门禁

PHP 静态分析与代码质量工程实战:类型推断与 PHPStan 配置、错误等级体系、Psalm 污点分析、Rector 自动重构、编码规范(PHP-CS-Fixer)、代码复制度量、静态分析接入 CI、增量扫描与基线管理、常见误报处理。

引言

PHP 是动态类型语言,「类型错误直到运行时才炸」是它最大的痛点。静态分析在 CI 阶段就能发现类型不匹配、空值访问、未定义方法等隐患——不用跑起来就能抓住一大批 bug。本文以 PHPStan 为主线(业界事实标准),覆盖 Psalm、Rector 自动重构、PHP-CS-Fixer 规范,以及如何把质量门禁接进 CI。

前置:/php-testing-practice/(测试体系)、/php8-modern-features/(类型系统)、/php-composer-package-development/(工程化)。


目录


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 文档

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「php」更多文章

  1. PHP 面向对象与设计模式:SOLID、常用模式与 Laravel 实践
  2. PHP 部署运维实战:Nginx、PHP-FPM、Docker 与 CI/CD
  3. PHP 缓存与 Redis 实战:缓存模式、数据结构与一致性