初めに
PHPStanを導入すると、nullチェックをしたにもかかわらず、null許容型と判断され、Cannot call method
が出てしまうこともあるかもしれません。
ここでは、その際の解決方法について説明します。
問題:nullチェック後もnull許容型であることを指摘されてしまう
以下のようなコードを解析してみます。
-
$fuga
はFuga|null
型のためnullチェックを実施 - Exceptionの呼び出しのために共通的なメソッドを使用
class Hoge
{
public function hogeMethod():void
{
// 返却値がnull許容型のメソッドを呼び出す
/** @var Fuga|null $fuga */
$fuga = $this->getFuga();
// null の場合は共通的なエラーを返すメソッドを呼び出す
if (is_null($fuga)) {
$this->throwCommonException();
}
// null じゃなくなったので、安全に呼び出せるはず
$piyo = $fuga->getPiyo();
}
/**
* 共通エラーを返すメソッド
*/
private function throwCommonException(): void
{
throw new Exception(
"common error",
500
);
}
}
解析したところ、nullチェックしたにも関わらずCannot call method
の指摘が出てしまいました。
$ ./bin/phpstan analyse
:14 Cannot call method getPiyo() on Fuga|null.
原因:nullチェック時に呼び出す共通エラーメソッドの戻り値がvoidになっている
この原因はthrowCommonException()
の戻り値がvoidになっていることです。
このメソッドではthrow
しかしていませんが」、phpstanは中身を気にしません。voidであればこの後も処理が継続されると認識してしまいます。
/**
* 共通エラーを返すメソッド
*/
private function throwCommonException(): void
{
throw new Exception(
"common error",
500
);
}
解決策1:nullチェック時にreturnする
一つ目の解決策は「nullチェックの中でreturnしてあげること」です。
とはいえ、PHPStanのために意味のないreturnを書くのは少し気になりますね。
public function hogeMethod():void
{
// 返却値がnull許容型のメソッドを呼び出す
/** @var Fuga|null $fuga */
$fuga = $this->getFuga();
// null の場合は共通的なエラーを返すメソッドを呼び出す
if (is_null($fuga)) {
$this->throwCommonException();
// 明示的にリターンする!
return;
}
// null じゃなくなったので、安全に呼び出せるはず
$piyo = $fuga->getPiyo();
}
解決策2:neverを用いる
二つ目の解決策はPHPのnever型を用いることです。
returnを用いる場合と違い、余計な処理を書かなくて済むのは嬉しいですね。
/**
* 共通エラーを返すメソッド
*/
private function throwCommonException(): never
{
throw new Exception(
"common error",
500
);
}
PHP公式ドキュメントには、never型について以下のように書いてあります。
never は、 関数が戻ってこないことを示す戻り値の型です。 これは、関数の中で exit() がコールされるか、 例外がスローされるか、 無限ループに入るかのいずれかであることを意味します。
これによって、処理が終わることをPHPStanに明示的に示すことができました。
終わりに
neverはPHP8.1から登場した比較的新しい型です。
PHPStanを既存のシステムに導入すると、このような問題に直面するかもしれません。
その際はnever型に置き換えることも検討してみてください。
ここまでご覧いただきありがとうございました!