> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 套件的靜態分析（PHPStan / Larastan）

> 說明使用 PHPStan 與 Larastan 提升 Laravel 套件的型別安全性與可維護性所需的設定、型別註解、以及 CI 自動執行。

Laravel 套件並非發布一次即結束。要跟隨 Laravel 本體、PHP、相依套件的更新，並以年為單位維護，除了測試外還需持續執行靜態分析。

<Info>
  本頁為 [Laravel 套件開發](/zh-TW/advanced/package-development)、[以 Orchestra Testbench 測試 Laravel 套件](/zh-TW/advanced/package-testing)、[套件的版本相容性管理](/zh-TW/advanced/package-versioning) 的輔助指南。將靜態分析加入實作、測試、版本策略中，可使套件更易長期維護。
</Info>

## 什麼是靜態分析

靜態分析是不執行程式碼即能偵測型別不一致或潛在 bug 的手法。Laravel 套件有大量像服務容器、Facade、Eloquent 等動態機制，僅靠動作確認容易疏漏的問題可於早期發現。

導入靜態分析尤其能帶來下列效果。

* **提升型別安全性** — 於審查前即可偵測錯誤的引數或回傳值
* **早期發現 bug** — 不存在的方法呼叫、nullable 的疏漏可於測試前掌握
* **改善 IDE 補完** — 整理 PHPDoc 與 Generics 可提升補完精度
* **穩定的長期維護** — Laravel 或 PHP 升級時容易篩出損壞處

## PHPStan 的設定

首先以 PHPStan 單獨準備分析基礎。PHPStan 2.x 對 `mixed` 或 nullable 的處理較從前嚴格，也是整理套件公開 API 的契機。

<Steps>
  <Step title="將 PHPStan 加入開發相依">
    ```bash theme={null}
    composer require --dev phpstan/phpstan:^2.0
    ```
  </Step>

  <Step title="先直接分析目標目錄">
    套件通常會將 `src` 與 `tests` 作為初始目標。

    ```bash theme={null}
    vendor/bin/phpstan analyse src tests
    ```
  </Step>

  <Step title="固定於 Composer script">
    為讓 CI 與 local 使用相同指令，於 `composer.json` 定義 script 較易操作。

    ```json theme={null}
    {
        "scripts": {
            "analyse": "phpstan analyse"
        }
    }
    ```
  </Step>
</Steps>

## Larastan 的設定

於 Laravel 套件中僅使用 PHPStan 無法充分理解容器解析、Facade、Eloquent 關聯等 Laravel 特有機制。因此併用 PHPStan 的 Laravel 擴充 Larastan。

<Steps>
  <Step title="加入 Larastan">
    目前的套件名稱為 `larastan/larastan`。舊文章中可能出現 `nunomaduro/larastan`。

    ```bash theme={null}
    composer require --dev larastan/larastan:^3.0
    ```
  </Step>

  <Step title="若為套件開發，也一併加入 Testbench">
    Larastan 會啟動 Laravel 應用程式容器來解析型別。單獨分析 Laravel 套件時，可能需要 `orchestra/testbench`。

    ```bash theme={null}
    composer require --dev orchestra/testbench
    ```
  </Step>

  <Step title="讀入 extension.neon">
    於 `phpstan.neon` include Larastan 的設定。

    ```neon theme={null}
    includes:
        - vendor/larastan/larastan/extension.neon
    ```
  </Step>
</Steps>

## phpstan.neon 的設定

`phpstan.neon` 是匯總分析等級、對象路徑、排除路徑、例外規則的中心設定。對 Laravel 套件而言，一開始就追求 100 分並不實際，將設定調整為可持續提升較為務實。

以下為 `phpstan.neon` 範例。

```neon theme={null}
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 可分階段導入。從目前程式碼基底能完整通過的等級開始，隨著修正推進再往上調整較為安全。

| Level | 適合的階段                                |
| ----- | ------------------------------------ |
| 0〜3   | 想先減少未知類別、函式、方法呼叫的階段                  |
| 4〜6   | 想整理公開 API 的引數、回傳值、屬性型別的階段            |
| 7〜9   | 想嚴格處理 nullable、union、`mixed` 的長期維護階段 |

<Tip>
  PHPStan 2.x 亦有 level 10，但 Laravel 套件建議先於 5〜7 穩定後再前往 8〜9 較為現實。若為新專案，從一開始就採較高等級可減少回頭修改。
</Tip>

### `paths`

於 `paths` 明確指定要分析的目錄。套件中除 `src` 外，也包含容易型別崩壞的 `tests` 會較有效。

### `excludePaths`

僅排除生成檔案、快取、驗證用 Workbench 等靜態分析價值低的位置。若排除過廣，會連原本要檢出的錯誤也看不到。

### `ignoreErrors`

`ignoreErrors` 是最後的手段。請以正規表達式限縮訊息，並併用 `path` 限定影響範圍。將來 Laravel 或 Larastan 改善時較易還原。

## 常用的型別 annotation

PHPStan 與 Larastan 的精度會因 PHPDoc 的寫法大幅變動。Laravel 套件中，尤其整理 `@param`、`@return`、`@var`、Generics（`@template`）具有高價值。

```php theme={null}
<?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` — 告知 `find()` 回傳具體的模型型別
* `@var Collection<int, TModel>` — 明確指定 collection 的 key / value 型別
* `@template TModel of Model` — 表達可重複利用的 generic repository

## Laravel 專屬的注意事項

Laravel 的「便利 magic」若不作處理則無法傳遞給靜態分析。需於套件端補上型別資訊，靠向分析器可理解的形式。

### Facade

在自訂 Facade 上，以 PHPDoc 補述使用者呼叫的方法，可同時對 IDE 與靜態分析生效。

```php theme={null}
<?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，新增可辨識回傳值與引數的 wrapper 方法較安全。

<Warning>
  若僅為靜態分析而持續增加 `ignoreErrors`，會遮蓋 Facade 或 Macro 真正的損壞方式。請優先嘗試以明確方法、型別化的 value object、追加 PHPDoc 解決。
</Warning>

### Eloquent 模型的型別指定

Eloquent 關聯或動態屬性，結合 `@property` 與 relation 的 Generics 較有效。

```php theme={null}
<?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 自動執行

靜態分析不僅於 local，也務必於 CI 執行。特別是支援多個 Laravel 版本的套件，若以與[套件的版本相容性管理](/zh-TW/advanced/package-versioning)的 test matrix 相同的思路運轉，可同時監視相容性與型別安全性。

以下為 `.github/workflows/static-analysis.yml` 的範例。

```yaml theme={null}
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
```

此範例使用與測試 matrix 相同的 Laravel / Testbench 對應表。於測試與靜態分析同時監視相同組合，較不易遺漏「執行可通但型別損壞」的狀態。

## 常見的誤判與應對

看到靜態分析的警告時，請先懷疑「是否為型別資訊不足」。看似誤判者，實際上常常只是 PHPDoc 不足。

### `@phpstan-ignore-next-line`

可作為暫時的迴避手段，但請僅用於自己能說明理由的行。

```php theme={null}
// @phpstan-ignore-next-line 因 Laravel 的 macro 在執行時註冊
$builder->whereLike('name', $keyword);
```

### `ignoreErrors`

僅於相同錯誤出現於多處時考慮。務必限縮 path，勿以粗略的正規表達式一概蓋掉整體。

```neon theme={null}
parameters:
    ignoreErrors:
        -
            message: '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::whereLike\(\)#'
            path: tests/Fixtures/*
```

### 應優先的處理

1. 補上 PHPDoc
2. 明確指定 Facade 或 relation 的回傳值型別
3. 將 `mixed` 替換為具體型別
4. 剩下能說明的誤判才 ignore

## 相關頁面

<Columns cols={3}>
  <Card title="Laravel 套件開發" icon="box" href="/zh-TW/advanced/package-development">
    確認包含服務提供者與公開資源在內的套件實作基礎。
  </Card>

  <Card title="以 Orchestra Testbench 測試 Laravel 套件" icon="flask-conical" href="/zh-TW/advanced/package-testing">
    說明希望與靜態分析共同運行的套件測試基礎。
  </Card>

  <Card title="套件的版本相容性管理" icon="git-branch" href="/zh-TW/advanced/package-versioning">
    確認 Laravel / PHP 的對應表與 GitHub Actions 的 matrix 策略。
  </Card>
</Columns>


## Related topics

- [Collection Deep Dive](/zh-TW/advanced/collection-deep-dive.md)
- [Laravel 新程式碼分析生態系 — surveyor / ranger / roster](/zh-TW/blog/laravel-ecosystem-analysis.md)
- [Laravel Package Skeleton — 官方套件用起始模板](/zh-TW/blog/package-skeleton-introduction.md)
- [部落格](/zh-TW/blog/index.md)
- [Laravel Console Starter](/zh-TW/packages/laravel-console-starter/index.md)
