关于 phpstan-fixer
指导 Agent 通过类型注解、PHPDoc 和代码改进修复 PHPStan 静态分析错误。重点是解决根因、遵守项目配置,并避免擅自抑制错误或修改第三方代码。
主要功能
- 01分析 PHPStan 错误上下文
- 02修复类型和 PHPDoc
- 03遵守已配置的严格级别
- 04避免未经同意抑制错误
- 05用存根处理第三方类型
- 06运行 PHPStan 验证修改
适用场景
- 01补全缺失的类型声明
- 02修正参数和返回值类型
- 03处理框架相关类型问题
- 04应对 vendor 库类型错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ marcelorodrigo/ agent-skills --skill phpstan-fixer安装后在 Claude Code 中输入/phpstan-fixer确认已加载。
详细介绍
PHPStan 错误修复器
通过适当的类型注解、PHPDocs 和代码改进来修复 PHPStan 静态分析错误。本技能将教会 Agent 如何在不抑制错误的情况下解决问题,并遵守项目配置的严格程度。
核心原则
- 不要首先抑制错误 — 使用适当的类型和注解修复根本原因
- 遵守用户配置 — 切勿修改
phpstan.neon设置(级别、路径、参数) - 不要悄悄忽略错误 — 未经用户明确批准,切勿在配置中添加
ignoreErrors - 结合上下文修复 — 在提出解决方案前,先了解项目类型(Laravel、Symfony、原生 PHP)
- 忽略错误前先征求同意 — 如果确实需要忽略错误,请说明原因并先征得用户批准
- 不要修改第三方代码 — 切勿修改
vendor/中的文件。请改用存根文件来覆盖错误的类型
工作流
步骤 1:了解项目上下文
修复错误前,先识别项目类型:
# Check for Laravel
grep laravel/framework composer.json
# Check for Symfony
grep symfony/symfony composer.json
# Check for PHPStan extensions
grep phpstan composer.json
# Read PHPStan config
cat phpstan.neon
# Check project guidelines
cat AGENTS.md
需要提取的关键信息:
- PHPStan 级别(0-10,或 max)
- 已安装的 PHPStan 扩展(larastan、phpstan-strict-rules 等)
- 框架专用辅助工具(Laravel IDE Helper、Symfony 插件)
- 项目专属的类型约定
步骤 2:分析错误
PHPStan 错误的结构如下:
------ ----------------------------------------------
Line /path/to/File.php
------ ----------------------------------------------
42 Parameter $user of method foo() has invalid
type App\User.
💡 Identifier: parameter.type
------ ----------------------------------------------
提取以下信息:
- 错误标识符(例如
parameter.type、missingType.return) - 错误位置(文件、行号)
- 上下文(代码试图做什么?)
步骤 3:应用正确的修复
根据错误标识符确定修复策略:
步骤 4:验证修复
应用修复后,再次运行 PHPStan 进行确认:
vendor/bin/phpstan analyse
重要事项:
- 如果出现新错误,说明修复可能有误。重新分析错误并尝试其他方法。
- 如果同一错误仍然存在,说明修复未正确应用。仔细检查代码。
- 如果错误已解决,则将修复标记为成功并处理下一个错误。
常见错误修复
类型相关错误
missingType.parameter — 缺少参数类型
错误:
Parameter $name has no type specified.
修复 — 添加原生类型:
// Before
function greet($name) {
return "Hello, $name";
}
// After
function greet(string $name): string {
return "Hello, $name";
}
修复 — 对复杂类型使用 PHPDoc:
// Before
function processUsers($users) { ... }
// After
/**
* @param array<int, User> $users
*/
function processUsers(array $users): void { ... }
missingType.return — 缺少返回类型
错误:
Method foo() has no return type specified.
修复 — 添加原生返回类型:
// Before
public function getUser() {
return $this->user;
}
// After
public function getUser(): User {
return $this->user;
}
修复 — 对联合类型/交集类型使用 PHPDoc:
// Before
public function findUser($id) { ... }
// After
/**
* @return User|null
*/
public function findUser(int $id): ?User { ... }
argument.type — 参数类型错误
错误:
Parameter #1 $id of method find() expects int, string given.
修复 — 转换参数类型:
// Before
$user = $repository->find($request->input('id'));
// After
$user = $repository->find((int) $request->input('id'));
修复 — 提前缩小类型范围:
// Better approach
$id = $request->integer('id'); // Laravel helper
$user = $repository->find($id);
return.type — 返回类型错误
错误:
Method foo() should return User but returns User|null.
修复 — 调整返回类型:
// Before
public function getUser(): User {
return $this->user ?? null;
}
// After
public function getUser(): ?User {
return $this->user ?? null;
}
修复 — 使用断言确保值非 null:
public function getUser(): User {
assert($this->user !== null);
return $this->user;
}
属性错误
property.notFound — 访问未定义的属性
错误:
Access to an undefined property User::$name.
修复 — 声明属性:
class User {
private string $name;
public function __construct(string $name) {
$this->name = $name;
}
}
修复 — 为魔术属性添加说明:
/**
* @property string $name
*/
class User {
public function __get($key) { ... }
}
修复(Laravel)— 使用 IDE Helper:
# Generate PHPDocs for Eloquent models
php artisan ide-helper:models
property.onlyWritten — 属性被写入但从未读取
错误:
Property User::$name is never read, only written.
修复 — 删除未使用的属性或添加 getter:
// If truly unused, remove it
// If needed, add usage:
public function getName(): string {
return $this->name;
}
方法错误
method.notFound — 调用了未定义的方法
错误:
Call to an undefined method App\User::getFullName().
修复 — 添加方法:
class User {
public function getFullName(): string {
return $this->first_name . ' ' . $this->last_name;
}
}
修复 — 为魔术方法添加说明:
/**
* @method string getFullName()
*/
class User {
public function __call($method, $args) { ... }
}
修复(Laravel)— 为查询构建器添加到 @mixin:
/**
* @mixin \Illuminate\Database\Eloquent\Builder
*/
class User extends Model { ... }
数组/偏移量错误
offsetAccess.notFound — 数组偏移量未定义
错误:
Offset 'email' does not exist on array.
修复 — 使用数组形状 PHPDoc:
/**
* @param array{email: string, name: string} $data
*/
function createUser(array $data): void {
echo $data['email']; // PHPStan knows this exists
}
修复 — 添加存在性检查:
if (isset($data['email'])) {
echo $data['email'];
}
修复 — 使用 null 合并运算符:
$email = $data['email'] ?? '[email protected]';
泛型错误
missingType.generics — 缺少泛型类型
错误:
Class Collection has @template T but does not specify it.
修复 — 在 PHPDoc 中指定泛型类型:
// Before
/** @var Collection $users */
$users = User::all();
// After
/** @var Collection<int, User> $users */
$users = User::all();
修复(Laravel)— 对集合使用 IDE Helper 存根。
死代码错误
deadCode.unreachable — 无法到达的代码
错误:
Unreachable statement - code above always terminates.
修复 — 删除死代码:
// Before
function foo() {
return true;
echo "This never runs"; // Error
}
// After
function foo() {
return true;
}
identical.alwaysTrue / identical.alwaysFalse — 条件始终为 true/false
错误:
Strict comparison using === between int and string will always evaluate to false.
修复 — 移除无用条件或修正类型:
// Before
if ($id === '123') { ... } // $id is int
// After
if ($id === 123) { ... }
特定框架的修复方法
Laravel
安装 Larastan,以支持针对 Laravel 的分析:
composer require --dev larastan/larastan
检查 phpstan.neon 是否包含 Larastan(如果缺少,请让用户添加):
includes:
- vendor/larastan/larastan/extension.neon
常见的 Laravel 修复方法:
// Eloquent relationships - use @property PHPDoc
/**
* @property-read \Illuminate\Database\Eloquent\Collection<int, Post> $posts
*/
class User extends Model {
public function posts() {
return $this->hasMany(Post::class);
}
}
// Collections - specify generic types
/** @var \Illuminate\Support\Collection<int, User> $users */
$users = User::all();
// Request input - use typed helpers
$id = $request->integer('id'); // Not $request->input('id')
$email = $request->string('email')->toString();
Symfony
安装 Symfony PHPStan 扩展:
composer require --dev phpstan/phpstan-symfony
检查 phpstan.neon 是否包含 Symfony 扩展(如果缺少,请让用户添加):
includes:
- vendor/phpstan/phpstan-symfony/extension.neon
parameters:
symfony:
containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
常见的 Symfony 修复方法:
// Service container - use proper type hints
public function __construct(
private UserRepository $userRepository, // Not mixed
) {}
// Forms - type the data
/** @var array{email: string, password: string} $data */
$data = $form->getData();
何时可以忽略(最后手段)
有时确实需要忽略某个问题。务必先使用 Question 工具询问用户:
步骤 1:说明情况
I found a PHPStan error that cannot be easily fixed:
Error: [describe error]
Location: [file:line]
Reason: [explain why it can't be fixed]
步骤 2:使用 Question 工具征求用户选择
Use the Question tool with these options:
- Header: "PHPStan Error Resolution"
- Question: "How would you like to handle this error?"
- Options:
1. "Use @phpstan-ignore with comment" - description: "Add inline ignore with explanation (recommended for third-party type issues)"
2. "Add to baseline" - description: "Generate baseline file (recommended for legacy code migration)"
3. "Refactor code" - description: "Modify code to satisfy PHPStan (most robust but may require significant changes)"
4. "Skip for now" - description: "Leave unfixed and continue with other errors"
Question 工具用法示例:
{
"questions": [{
"header": "PHPStan Error Resolution",
"question": "File src/Service.php:42 has argument type mismatch with third-party API. How should I handle this?",
"options": [
{
"label": "Use @phpstan-ignore (Recommended)",
"description": "Add inline ignore with explanation"
},
{
"label": "Add to baseline",
"description": "Include in baseline file for tracking"
},
{
"label": "Refactor code",
"description": "Modify to satisfy PHPStan"
},
{
"label": "Skip for now",
"description": "Continue with other errors"
}
]
}]
}
可以忽略的合理原因:
- 第三方库的类型有误(且没有可用的存根文件)
- 基于反射的代码本身正确,但 PHPStan 无法理解
- 复杂的业务逻辑在运行时类型安全,但无法通过静态分析证明
- 大规模重构期间的临时处理(使用 baseline)
忽略方法(若获批准):
// Inline ignore with explanation
/** @phpstan-ignore argument.type (API returns string|int, we handle both) */
$result = $api->getValue();
// Baseline for legacy code
vendor/bin/phpstan analyse --generate-baseline
未经批准,绝不要这样做:
# Don't add this to phpstan.neon without user consent
parameters:
ignoreErrors:
- '#.*#' # NEVER
调试类型
使用 \PHPStan\dumpType() 查看 PHPStan 对类型的判断:
$user = User::find($id);
\PHPStan\dumpType($user); // Reports: App\User|null
// Remove before committing!
故障排除
PHPStan 无法识别有效类型
请检查:
- 类是否可自动加载?(
composer dump-autoload) - PHPStan 是否扫描了该文件?(检查
phpstan.neon中的paths) - 命名空间中是否有拼写错误?
类型推断不起作用
请检查:
- 是否过多使用内联
@var?(应从源头修复) - 函数/方法是否指定了返回类型?
- 是否使用了 PHPStan 无法分析的动态特性?
无法识别 Laravel 魔术方法
安装并运行:
composer require --dev barryvdh/laravel-ide-helper
php artisan ide-helper:generate
php artisan ide-helper:models --write
php artisan ide-helper:meta
错误标识符参考
完整列表:https://phpstan.org/error-identifiers
最常见的类别:
argument.*— 函数/方法参数问题return.*— 返回类型不匹配missingType.*— 缺少类型声明property.*— 属性访问/声明问题method.*— 方法调用问题offsetAccess.*— 数组/ArrayAccess 问题class.*— 类继承/使用问题deadCode.*— 不可达代码identical.*/equal.*— 比较问题