開発環境
- 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のエラーをクリアすることができます。
変数の型を指定します
注意すべきは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のエラーをなくすことのほうが大変かもしれません。
ですが、ファイル数やコード数が増えると、
型指定をしているだけで多くのバグを防ぐことができます。
この記事が少しでも参考になれば嬉しいです。