跳转到主要内容
Laravel 包并不是发一次版本就结束。为了在 Laravel 本体、PHP、依赖包持续更新的同时长期维护,除测试外还需要持续跑静态分析。
本页是 Laravel 包开发使用 Orchestra Testbench 测试 Laravel 包包的版本兼容性管理 的辅助指南。将静态分析加入实现、测试、版本策略之中,包会更利于长期维护。

什么是静态分析

静态分析是在不执行代码的前提下,检测类型不匹配与潜在 Bug 的手段。Laravel 包中有服务容器、Facade、Eloquent 等许多动态机制,仅靠运行验证容易漏掉的问题,静态分析可以在早期捕获。 引入静态分析尤其带来以下好处:
  • 提升类型安全 — 在评审前就发现错误的参数或返回值
  • 提早发现 Bug — 在测试前掌握不存在的方法调用、nullable 遗漏
  • 改进 IDE 补全 — 完善 PHPDoc 与 Generics 后补全更准确
  • 稳定长期维护 — Laravel 或 PHP 升级时能更容易梳理受影响处

PHPStan 的搭建

先用 PHPStan 独立准备分析基础设施。PHPStan 2.x 对 mixed 与 nullable 的处理比以往更严格,也是重整包公共 API 的契机。
1

将 PHPStan 加入开发依赖

composer require --dev phpstan/phpstan:^2.0
2

先直接分析目标目录

包一般先以 srctests 为初始对象。
vendor/bin/phpstan analyse src tests
3

在 Composer script 中固化

为了让 CI 与本地使用同样命令,在 composer.json 中定义 script 更方便运维。
{
    "scripts": {
        "analyse": "phpstan analyse"
    }
}

Larastan 的搭建

只用 PHPStan 无法充分理解容器解析、Facade、Eloquent 关联等 Laravel 特有机制。因此还需要一起使用 PHPStan 的 Laravel 扩展 Larastan。
1

加入 Larastan

当前包名是 larastan/larastan。旧文章中可能出现的 nunomaduro/larastan 已经不用了。
composer require --dev larastan/larastan:^3.0
2

包开发时最好也装 Testbench

Larastan 会启动 Laravel 应用容器来解析类型。单独分析 Laravel 包时可能需要 orchestra/testbench
composer require --dev orchestra/testbench
3

引入 extension.neon

phpstan.neon 中 include Larastan 的配置。
includes:
    - vendor/larastan/larastan/extension.neon

phpstan.neon 的配置

phpstan.neon 是聚合分析等级、目标路径、排除路径、豁免规则的核心配置。对 Laravel 包来说,比起一开始就追求满分,采用一份能持续提升的配置更为现实。 以下是 phpstan.neon 的例子。
includes:
    - vendor/larastan/larastan/extension.neon

parameters:
    level: 5

    paths:
        - src
        - tests

    excludePaths:
        analyse:
            - vendor
            - workbench
            - bootstrap/cache/*

    ignoreErrors:
        -
            message: '#Call to an undefined method Illuminate\\Support\\HigherOrderCollectionProxy::#'
            path: tests/Fixtures/*

level 0〜9 的选择

PHPStan 可以逐步引入。先从能跑通当前代码库的等级开始,随修复推进再逐步提高,是最稳妥的方式。
级别适合阶段
0〜3先减少未知类、函数、方法调用的阶段
4〜6想整理公共 API 参数、返回值、属性类型的阶段
7〜9想严格处理 nullable、union、mixed 的长期维护期
PHPStan 2.x 也有 level 10,但对 Laravel 包而言,先稳定在 5〜7 再迈向 8〜9 更现实。新包如从更高级别开始,返工会更少。

paths

paths 中显式列出要分析的目录。包里不仅是 src,类型也容易崩坏的 tests 也建议纳入。

excludePaths

只排除生成文件、缓存、验证用 Workbench 等静态分析价值较低的位置。过度排除会遮盖本应发现的错误。

ignoreErrors

ignoreErrors 是最后手段。请用正则精确匹配消息,并搭配 path 限制影响范围。这样将来 Laravel 或 Larastan 改进后更容易还原。

常用类型注解

PHPStan 与 Larastan 的准确度很大程度上取决于 PHPDoc 的写法。Laravel 包中,@param@return@var、Generics(@template)的整备尤其有价值。
<?php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

/**
 * @template TModel of Model
 */
final class ModelRepository
{
    /**
     * @param class-string<TModel> $modelClass
     */
    public function __construct(private string $modelClass)
    {
    }

    /**
     * @return TModel|null
     */
    public function find(int $id): ?Model
    {
        return $this->modelClass::query()->find($id);
    }

    /**
     * @return Collection<int, TModel>
     */
    public function all(): Collection
    {
        /** @var Collection<int, TModel> $models */
        $models = $this->modelClass::query()->get();

        return $models;
    }
}
这个例子向 PHPStan 传达了以下信息:
  • @param class-string<TModel> — 表明字符串不是任意字符串,而是 Model 类名
  • @return TModel|null — 告诉 PHPStan find() 返回具体的模型类型
  • @var Collection<int, TModel> — 明确 Collection 的 key/value 类型
  • @template TModel of Model — 表达可复用的通用 repository

Laravel 特有注意点

Laravel 「便捷的 magic」不会自动传达给静态分析。需要在包侧补充类型信息,以让分析器能够理解。

Facade

对自定义 Facade,在 PHPDoc 中补上用户会调用的方法,可同时受益于 IDE 与静态分析。
<?php

namespace Vendor\Package\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * @method static string issueToken(int $userId)
 */
class Tokenizer extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return 'package.tokenizer';
    }
}

魔术方法

依赖 __call() 或 Macroable 的 API 很方便,但类型极易失控。若作为公开 API,最好增加参数与返回值可见的包装方法。
仅仅为了让静态分析通过而不断增加 ignoreErrors,会把 Facade 或 Macro 真正的破损情况掩盖起来。请优先考虑通过明确的方法、类型化的 value object、追加 PHPDoc 来解决。

Eloquent 模型的类型指定

对 Eloquent 关联或动态属性,组合 @property 与关联的 Generics 会很有效。
<?php

namespace Vendor\Package\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
 * @property-read Team $team
 */
class Member extends Model
{
    /**
     * @return BelongsTo<Team, self>
     */
    public function team(): BelongsTo
    {
        return $this->belongsTo(Team::class);
    }
}

在 CI 中自动执行

静态分析不仅要在本地,也务必要在 CI 中执行。特别是支持多个 Laravel 版本的包,请像 包的版本兼容性管理 中的测试矩阵一样运行,可以同时监控兼容性与类型安全。 以下是 .github/workflows/static-analysis.yml 的示例。
name: Static analysis

on:
  push:
  pull_request:

jobs:
  static-analysis:
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        php: [8.2, 8.3, 8.4]
        laravel: ["^12.0", "^13.0"]
        include:
          - laravel: "^13.0"
            testbench: "^10.0"
          - laravel: "^12.0"
            testbench: "^9.0"

    name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - PHPStan

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: dom, curl, libxml, mbstring, zip
          coverage: none

      - name: Install dependencies
        run: |
          composer require "laravel/framework:${{ matrix.laravel }}" \
                           "orchestra/testbench:${{ matrix.testbench }}" \
                           --no-interaction --no-update
          composer update --prefer-dist --no-interaction

      - name: Run static analysis
        run: vendor/bin/phpstan analyse --error-format=github
该例中使用与测试矩阵相同的 Laravel / Testbench 对应关系。测试与静态分析都监控相同组合时,「运行能过但类型已坏」的状态就不易被漏掉。

常见误报与应对

见到静态分析警告时,先怀疑是否「类型信息不足」。看似误报的情况,很多其实只是 PHPDoc 缺失。

@phpstan-ignore-next-line

作为临时规避方式可以使用,但请限制在你能自行解释原因的行。
// @phpstan-ignore-next-line Laravel 的 macro 在运行时注册
$builder->whereLike('name', $keyword);

ignoreErrors

仅在同一错误出现在多处时才考虑。务必收窄 path,不要用宽松正则一把捂住全局。
parameters:
    ignoreErrors:
        -
            message: '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::whereLike\(\)#'
            path: tests/Fixtures/*

优先考虑的对策

  1. 补齐 PHPDoc
  2. 明确 Facade 或关联返回值类型
  3. mixed 替换为具体类型
  4. 只对能解释的误报使用 ignore

相关页面

Laravel 包开发

了解包含 Service Provider 与公开资源的包实现基础。

使用 Orchestra Testbench 测试 Laravel 包

了解与静态分析并用的包测试基础设施。

包的版本兼容性管理

查看 Laravel / PHP 兼容表与 GitHub Actions matrix 策略。
最后修改于 2026年7月13日