mcpField
P
phpstan-fixer

phpstan-fixer

github.com/marcelorodrigo测试与质量19113 次安装8 周前更新
代码仓库

关于 phpstan-fixer

指导 Agent 通过类型注解、PHPDoc 和代码改进修复 PHPStan 静态分析错误。重点是解决根因、遵守项目配置,并避免擅自抑制错误或修改第三方代码。

主要功能

  1. 01分析 PHPStan 错误上下文
  2. 02修复类型和 PHPDoc
  3. 03遵守已配置的严格级别
  4. 04避免未经同意抑制错误
  5. 05用存根处理第三方类型
  6. 06运行 PHPStan 验证修改

适用场景

  1. 01补全缺失的类型声明
  2. 02修正参数和返回值类型
  3. 03处理框架相关类型问题
  4. 04应对 vendor 库类型错误

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/marcelorodrigo/agent-skills --skill phpstan-fixer

安装后在 Claude Code 中输入/phpstan-fixer确认已加载。

详细介绍

PHPStan 错误修复器

通过适当的类型注解、PHPDocs 和代码改进来修复 PHPStan 静态分析错误。本技能将教会 Agent 如何在不抑制错误的情况下解决问题,并遵守项目配置的严格程度。

核心原则

  1. 不要首先抑制错误 — 使用适当的类型和注解修复根本原因
  2. 遵守用户配置 — 切勿修改 phpstan.neon 设置(级别、路径、参数)
  3. 不要悄悄忽略错误 — 未经用户明确批准,切勿在配置中添加 ignoreErrors
  4. 结合上下文修复 — 在提出解决方案前,先了解项目类型(Laravel、Symfony、原生 PHP)
  5. 忽略错误前先征求同意 — 如果确实需要忽略错误,请说明原因并先征得用户批准
  6. 不要修改第三方代码 — 切勿修改 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
------ ----------------------------------------------

提取以下信息:

  1. 错误标识符(例如 parameter.type、missingType.return)
  2. 错误位置(文件、行号)
  3. 上下文(代码试图做什么?)
步骤 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 无法识别有效类型

请检查:

  1. 类是否可自动加载?(composer dump-autoload)
  2. PHPStan 是否扫描了该文件?(检查 phpstan.neon 中的 paths)
  3. 命名空间中是否有拼写错误?
类型推断不起作用

请检查:

  1. 是否过多使用内联 @var?(应从源头修复)
  2. 函数/方法是否指定了返回类型?
  3. 是否使用了 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.* — 比较问题

资源

评论

支持 Markdown