PHPの配列要素の型がチェックできない問題
この、PHPの配列要素の型をチェックできない問題ですが、みなさん一度は思われたことがあるのではないでしょうか?関数やメソッドの引数や返り値、オブジェクトのプロパティにについては型はつけやすくなりましたが、配列の内部構造はまだまだです
例えば関数の戻り値にarray
を指定している場合、配列以外の型を持った値を返そうとするとエラーになりますが、配列の要素に関しては何が入ってこようとエラーは出ません。そのため、思わぬバグに繋がってしまうことがあるのです
そこで本記事ではその解決策となるテクニックをご紹介していきます
結論:Array Shapes/旧PSR-5形式のジェネリクス記法で記載し、PHPStanを使用して解析する
早速結論ですが、PHPDocのArray Shapes記法とPHPStanを組み合わせて配列要素の型をチェックします
ざっくりと言うと、Array Shapes/旧PSR-5形式のジェネリクス記法でDocコメントを記載し実装を進め、その後に静的解析(PHPstan)を実行することで、型チェックをするイメージです
ケーススタディ
ややこしい話をするより実際にコードをいくつか見ていきます
旧PSR-5形式のジェネリクス記法
まずはジェネリクス記法から。下記コードのようにarray<int, User>
とすることで、キーと値の型を指定します
/**
* @return array<int, User>
*/
function getUsers(): array
{
return $this->users;
}
また、キーの指定が必要ない場合は
/**
* @return array<User>
*/
function getUsers(): array
{
return $this->users;
}
のように記載します。
Array Shapes記法
続いてArray Shapes記法についてです。記法が複数出てきて混乱するかもしれないですが、どちらを使っても、併用しても問題ありあせん(基本的には併用することになるかと思います)
また、仕様については下記サイトに記載してあります
早速例題ですが、連想配列は以下のように型をつけることができます。id
とname
、isJapanese
がキー名になっていて、その隣に型を記載します
/**
* @return array{id: UserId, name: string, isJapanese: bool}
*/
public function getArray(): array
{
return [
'id' => new UserId(1),
'name' => '山田花子',
'isJapanese' => true
];
}
nullを許容する際は、address: ?string
のように、値の先頭に?
をつけます
/**
* @return array{id: UserId, name: string, isJapanese: bool, address: ?string}
*/
public function getArray(): array
{
return [
'id' => new UserId(1),
'name' => '山田花子',
'isJapanese' => true,
'address' => null
];
}
上記の例ではaddress
の値はnullが入りえますが、addressを省略することはできません。キーが省略される可能性がある場合は、address?: string
のように、キー名の最後に?
をつけます
/**
* @return array{id: UserId, name: string, isJapanese: bool, address?: string}
*/
public function getArray(): array
{
return [
'id' => new UserId(1),
'name' => '山田花子',
'isJapanese' => true
];
}
nullを許容とキーを省略を併用したい場合は、address?: ?string
と記載してください
PHPStanを実行する
ここまでで準備は整いました。あとはPHPStanを実行することで、タインプヒントのエラーを発見することができます
また、PHPStanのlevelについてですが、
6.report missing typehints.
下記サイトにも記載してある通り、Level6からタイプヒントのエラーをレポートするようになるので、PHPStanのlevelは6に設定するようにしましょう
おわりに
以上で、配列要素の型チェックができるようになります
プラスアルファとしては、PHPStanをCI(継続的インテグレーション)に組み込み、自動的にこのチェックが行われるようにしましょう。機会があればまた記事にしたいと思います
何かご指摘やご意見があればコメントお願いします!