PHPStanのレベル別エラー発生のサンプルコードと解決方法

## 目次

- [はじめに](#はじめに)
- [前提](#前提)
- [レベル0](#レベル0)
- [レベル1](#レベル1)
- [レベル2](#レベル2)
- [レベル3](#レベル3)
- [レベル4](#レベル4)
- [レベル5](#レベル5)
- [レベル6](#レベル6)
- [レベル7](#レベル7)
- [レベル8](#レベル8)
- [レベル9](#レベル9)
- [おわりに](#おわりに)

---

## はじめに

特に意識せず、コーディング標準ツールの PHPStan を利用していましたが、レベル毎にどの問題を補足してくれるのか気になったので、まとめてみました。

---

## 前提

バージョン

- PHP
  - 8.1.6
- PHPStan
  - 1.6.9

こちらのリポジトリで確認しました

https://github.com/q23isline/study_php/tree/feature/add-sample-phpstan-error

---

## レベル0

---

### 基本的なチェック：シンタックスエラー

- PHPStan エラー発生コード

    ```php
    class Profile()
    {}
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Parse error: syntax error, unexpected token "(", expecting "{"
    ```

- PHPStan エラー解決方法

    ```php
    // クラス定義に () は不要
    class Profile
    {}
    ```

---

### 不明なクラス

- PHPStan エラー発生コード

    ```php
    new ProfileRepository();
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Fatal error: Uncaught Error: Class "ProfileRepository" not found
    ```

- PHPStan エラー解決方法

    ```php
    // ProfileRepository クラスを定義する
    class ProfileRepository
    {}
    new ProfileRepository();
    ```

---

### 不明な関数

- PHPStan エラー発生コード

    ```php
    isArray([]);
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Fatal error: Uncaught Error: Call to undefined function isArray()
    ```

- PHPStan エラー解決方法

    ```php
    // 存在する関数を呼び出す
    is_array([]);
    ```

---

### 呼び出された不明なメソッド

- PHPStan エラー発生コード

    ```php
    class AccountRepository
    {}
    AccountRepository::find();
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Fatal error: Uncaught Error: Call to undefined method AccountRepository::find()
    ```

- PHPStan エラー解決方法

    ```php
    // find メソッドを定義する
    class AccountRepository
    {
        public static function find()
        {}
    }
    AccountRepository::find();
    ```

---

### 関数に渡された引数の数が間違っている

- PHPStan エラー発生コード

    ```php
    is_array();
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Fatal error: Uncaught ArgumentCountError: is_array() expects exactly 1 argument, 0 given
    ```

- PHPStan エラー解決方法

    ```php
    // is_array 関数は第一引数に値が必須
    is_array([]);
    ```

---

### 常に未定義の変数

- PHPStan エラー発生コード

    ```php
    class Account
    {
        public function find()
        {
            return $account;
        }
    }
    echo (new Account)->find();
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Warning: Undefined variable $account
    ```

- PHPStan エラー解決方法

    ```php
    // account 変数を初期化する
    class Account
    {
        public function find()
        {
            return $account = 1;
        }
    }
    echo (new Account)->find();
    ```

---

## レベル1

---

### 未定義の変数

- PHPStan エラー発生コード

    ```php
    echo $profile;
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Warning: Undefined variable $profile
    ```

- PHPStan エラー解決方法

    ```php
    // profile 変数を初期化する
    echo $profile = 1;
    ```

---

## レベル2

---

### 不明なメソッドをすべてチェックする

- PHPStan エラー発生コード

    ```php
    class BookRepository
    {}
    (new BookRepository())->find();
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Fatal error: Uncaught Error: Call to undefined method BookRepository::find()
    ```

- PHPStan エラー解決方法

    ```php
    // find メソッドを定義する
    class BookRepository
    {
        public function find()
        {}
    }
    (new BookRepository())->find();
    ```

---

### PHPDocs を検証する

- PHPStan エラー発生コード

    ```php
    class AuthorRepository
    {
        /**
        * @param int $id ID
        * @return void
        */
        public function find()
        {}
    }
    (new AuthorRepository())->find();
    ```

- PHPStan エラー発生コード実行時のログ

    エラーにはならない

- PHPStan エラー解決方法

    ```php
    // find メソッドと一致する PHPDocs を記載する
    class AuthorRepository
    {
        /**
        * @return void
        */
        public function find()
        {}
    }
    (new AuthorRepository())->find();
    ```

---

## レベル3

---

### 返り値の型

- PHPStan エラー発生コード

    ```php
    function findBook(): array
    {
        return null;
    }
    findBook();
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Fatal error: Uncaught TypeError: findBook(): Return value must be of type array, null returned
    ```

- PHPStan エラー解決方法

    ```php
    // 返り値の型を実態に合うように定義する
    function findBook(): ?array
    {
        return null;
    }
    findBook();
    ```

---

### プロパティに割り当てられた型

- PHPStan エラー発生コード

    ```php
    class Book
    {
        public int $id;
    }
    $book = new Book();
    $book->id = '1';
    ```

- PHPStan エラー発生コード実行時のログ

    `declare(strict_types=1);`を定義していればエラーになる

    ```text
    Fatal error: Uncaught TypeError: Cannot assign string to property Book::$id of type int
    ```

- PHPStan エラー解決方法

    ```php
    // 返り値の型を実態に合うように定義する
    class Book
    {
        public int $id;
    }
    $book = new Book();
    $book->id = 1;
    ```

---

## レベル4

---

---

### 基本的なデッドコードチェック：常に false

- PHPStan エラー発生コード

    ```php
    $authorId = 1;
    if (!$authorId) {
        throw new Exception();
    }
    echo $authorId;
    ```

- PHPStan エラー発生コード実行時のログ

    エラーにはならない

- PHPStan エラー解決方法

    ```php
    // デッドコードは削除する
    $authorId = 1;
    echo $authorId;
    ```

---

### 基本的なデッドコードチェック：リターン後の到達不能コード

- PHPStan エラー発生コード

    ```php
    function setDefaultAuthor()
    {
        $authors = [];
        return $authors;
        $authors[] = 'Tanaka';
    }
    print_r(setDefaultAuthor());
    ```

- PHPStan エラー発生コード実行時のログ

    エラーにはならない

- PHPStan エラー解決方法

    ```php
    // デッドコードは削除する
    function setDefaultAuthor()
    {
        $authors = [];
        return $authors;
    }
    print_r(setDefaultAuthor());
    ```

---

## レベル5

---

### メソッドと関数に渡される引数の型をチェックする

- PHPStan エラー発生コード

    ```php
    class CategoryRepository
    {
        public function findById(int $id): int
        {
            return $id;
        }
    }
    $repository = new CategoryRepository();
    echo $repository->findById('1');
    ```

- PHPStan エラー発生コード実行時のログ

    `declare(strict_types=1);`を定義していればエラーになる

    ```text
    Fatal error: Uncaught TypeError: CategoryRepository::findById(): Argument #1 ($id) must be of type int, string given, called
    ```

- PHPStan エラー解決方法

    ```php
    // 引数の型と一致する型でメソッド呼び出しする
    class CategoryRepository
    {
        public function findById(int $id): int
        {
            return $id;
        }
    }
    $repository = new CategoryRepository();
    echo $repository->findById(1);
    ```

---

## レベル6

---

### 欠落しているタイプヒントを報告する

- PHPStan エラー発生コード

    ```php
    function deleteBook($id): int
    {
        return $id;
    }
    echo deleteBook(1);
    ```

- PHPStan エラー発生コード実行時のログ

    エラーにはならない

- PHPStan エラー解決方法

    ```php
    // 引数のタイプヒントを定義する
    function deleteBook(int $id): int
    {
        return $id;
    }
    echo deleteBook(1);
    ```

---

## レベル7

---

### 部分的に間違った union 型を報告する

- PHPStan エラー発生コード

    ```php
    function deleteAuthor(string|int $id): int
    {
        return $id;
    }
    deleteAuthor(1);
    ```

- PHPStan エラー発生コード実行時のログ

    エラーにはならない

- PHPStan エラー解決方法

    ```php
    // 返り値の型になりうる型を定義する
    function deleteAuthor(string|int $id): string|int
    {
        return $id;
    }
    deleteAuthor(1);
    ```

---

## レベル8

---

### null 許容型のメソッドの呼び出しとプロパティへのアクセス

- PHPStan エラー発生コード

    ```php
    class Category
    {
        public string $name = '';
        function setName(?string $name): void
        {
            $this->name = $name;
        }
    }
    $category = new Category();
    $category->setName(null);
    echo $category->name;
    ```

- PHPStan エラー発生コード実行時のログ

    ```text
    Fatal error: Uncaught TypeError: Cannot assign null to property Category::$name of type string
    ```

- PHPStan エラー解決方法

    ```php
    // プロパティを nullable にするか、setName メソッドの引数を null 不許可にする
    class Category
    {
        public ?string $name = '';
        function setName(?string $name): void
        {
            $this->name = $name;
        }
    }
    $category = new Category();
    $category->setName(null);
    echo $category->name;
    ```

---

## レベル9

---

### 型について厳密にする（mixed 不許可）

- PHPStan エラー発生コード

    ```php
    function findDefaultCategory(mixed $id): string
    {
        findCategoryById($id);

        return 'Default';
    }
    function findCategoryById(int $id): mixed
    {
        return [];
    }
    print_r(findDefaultCategory(1));
    ```

- PHPStan エラー発生コード実行時のログ

    エラーにはならない

- PHPStan エラー解決方法

    ```php
    // mixed を使わない
    function findDefaultCategory(int $id): string
    {
        findCategoryById($id);

        return 'Default';
    }
    function findCategoryById(int $id): mixed
    {
        return [];
    }
    print_r(findDefaultCategory(1));
    ```

---

## おわりに

サンプルコードを製造するのに何気に時間がかかりました。  
この記事が他のエンジニアの助けになれば幸いです。

https://phpstan.org/