Laravel PHP

【PHPStan】PHPDoc で苦しんでいるエンジニア必見

開発環境

  • Docker
  • Laravel9(PHP8.1)
  • PHPStan Level8

はじめに

PHPStanのLevel8を現場で導入しました。

今まで「型」にこだわったソースコードを書いてきていませんでしたが、

PHPStanを導入で型の大切さを思い知らされました。

最初はとても苦労しましたが、

まるで別の言語を使っているかのように、PHPのイメージがかなり変わりました。

この記事では、PHPStanを導入して苦労したことや、

コードを書く上で気をつけていることをお伝えします。

PHPStan

PHPStanとはPHPのコードを静的に解析し、

実行したときにエラーが起きるようなコードを検出してくれるツールです。

コードを書いて、動かしてみて、バグに気づくことがありますが、

PHPStanはこれらのバグを事前に見つけ出してくれます。

10段階 のLevel

PHPStanには10段階のレベルがあります。

デフォルトではLevel0

Level1、2…9と数字が増えるごとに、チェックが厳しくなっていきます。

エラーの原因をチェックしてくれることはもちろんですが、

エラーの原因になる曖昧な型や、ムダな変数なども見つけ出してくれます。

ソースコード自体にもムダがなく可読性が上がることも期待できます。

PHPStan 型チェック

曖昧な型はコードの量が増えていくとバグの原因となります。

PHPStanは、

変数の定義、メソッドの引数、戻り値など、

あらゆるものの型を明確にするよう要求してきます。

PHPDocによる型指定

<?php

class User
{
    private int $id;
    private string $name;
    private array $items;

    public function __construct(int $id, string $name, array $items)
    {
        $this->id = $id;
        $this->name = $name;
        $this->items = $items;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getItems()
    {
        return $this->items;
    }
}
?>

PHPStanの一番の特徴は、PHPDocによる型指定です。

Levelによって指定の厳しさは変わりますが、

↑のソースコードではエラー予備軍扱いとなってしまいます。

<?php

class User
{
    /**
     * @var int
     */
    private int $id;

    /**
     * @var string
     */
    private string $name;

    /**
     * @var string[]
     */
    private array $items;

    /**
     * @param int $id
     * @param string $name
     * @param string[] $items
     */
    public function __construct(int $id, string $name, array $items)
    {
        $this->id = $id;
        $this->name = $name;
        $this->items = $items;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return string[]
     */
    public function getItems()
    {
        return $this->items;
    }
}
?>

このようにPHPDocで型を明記することによって、

PHPStanのエラーをクリアすることができます。

@var

変数の型を指定します

注意すべきはarrayです。

/**
 * @var array
 */
public array $items;

配列を構成している要素がなにかを明確に定義していないので

チェックに引っかかってしまいます。

/**
 * @var string[] もしくは array<string>
 */
public array $items;

このように配列の要素の指定も必要です。

その他 PHPDoc 配列の型

//連想配列(文字列 => 文字列)のとき

$array = [
    'apple' => 'りんご',
    'orange' => 'みかん',
];

/**
 * @var array<string, string>
 */
public array $array;

/**
 * もっと厳密な書き方
 * @var array{apple:string, orange:string}※Key名を指定するときは、<>でなく{}
 */
public array $array;


//連想配列(文字列 => 数字 or 真偽値)のとき
$array = [
    'number' => 1,
    'boolean' => true,
];

/**
 * @var array<string, int|bool>
 */
public array $array;


//多重連想配列のとき
$array = [
      [
         'apple' => 'りんご',
         'orange' => 'みかん',
      ],
      [
         'apple' => 'りんご',
         'orange' => 'みかん',
      ],
     ];

/**
 * @var array<int, array<string, string>>
 */
public array $array;

@param

コンストラクタやメソッドなどの、

引数の型を指定します

指定方法は@varと同じです

@return

メソッドの戻り値の型を指定します

@throws

例外の型を指定します

/**
 * @param int $number(引数)※変数名も記述する
 * @return int(戻り値)
 * @throw Exception(例外)
 */
public function validateNumber(int $number)
{
   if($number > 10){
      return $number
   }
   throw new Exception('this number is too small');
}

mixd

PHPには、「何でもいれて大丈夫、万能型」のmixdというものが存在します。

PHPStanのチェックを回避するという目的だけであれば、

このようにすべてmixdにしてしまえば通ってしまいます。

<?php

class User
{
    /**
     * @var mixd
     */
    private mixd $id;

    /**
     * @param mixd $id
     */
    public function __construct(mixd $id)
    {
        $this->id = $id;
    }

    /**
     * @return mixd
     */
    public function getId()
    {
        return $this->id;
    }
}
?>

ただこれでは型を指定しないことと変わりません。

できるだけmixdは使わないことをおすすめします

さいごに

慣れるまでは、書くことよりもPHPStanのエラーをなくすことのほうが大変かもしれません。

ですが、ファイル数やコード数が増えると、

型指定をしているだけで多くのバグを防ぐことができます。

この記事が少しでも参考になれば嬉しいです。

-Laravel, PHP