PHP8.1にNever型が追加されました。読んで字の如し。この型を宣言した関数はPHPプログラムの処理が必ず終了することを指します。
終了は『関数で終わらせる』『例外をスローする』『無限ループ』の3つ。
ん? 無限ループもですか? ってかんじ。これ引っかかる人多いんじゃないかな?
Never型が宣言された関数は何も返しません。というか処理の中で必ずPHPプログラムを終了させる必要があります。
die()
exit()
trigger_error()
redirect()
etc...
ただし、if文で特定の条件のときだけ終了するようなことはできます。(理由は後述。)
またこの型は性質上、関数の戻り値専用です。関数のパラメータやクラスのプロパティには使えません。
例外スローや無限ループも終了の部類になる
never型の説明では、ちょっと気になる文言があります。
never は、 関数が戻ってこないことを示す戻り値の型です。 これは、関数の中で exit() がコールされるか、 例外がスローされるか、 無限ループに入るかのいずれかであることを意味します。
PHP公式ドキュメント
例外はキャッチしない限り処理終了なので分かりますが、無限ループも入ってるところがおもしろい。
neverは『絶対に...ない。』という意味ですが、ここでは『絶対に処理が戻らない』と覚えましょう。
だから無限ループも入ってます。
今まで、『プログラムを必ず終了させる』と言ってきましたが『必ず処理を戻らせない』が正解ですね?
ただし、無限ループする処理ってソケット通信の受信側とかちょっと特殊な処理をすることが多くあまり使う機会はないと思います。
とくにWebサーバーで使うときは、必ずブラウザへのレスポンスが必要なので、無限ループをすることはないんじゃないだろうか?
メソッドのオーバーライドで他の型から変更可能。
もうひとつ気になる文言があります。
never は、 型理論の用語で言うと、ボトム型にあたります。 つまり、全ての他の型の部分型であり、 継承する際に他の戻り値の型で置き換えることができます。
PHP公式ドキュメント
ボトム型
型理論において値を持たない型。
ゼロ型、空型ともいう。
NULLやPHPのneverなど。
この文章、ちょっとおかしい。
neverはすべての型の部分型と言いながら、継承するときほかの戻り値の型で置き換えることができると言っています。
最初ボクは『で』に着目してこういう風に考えました。
class Test {
public function finish() : never
{
exit();
}
}
class Test2 extends Test {
public function finish() : int
{
return 100;
}
}
PHP Fatal error: Declaration of Test2::finish(): int must be compatible with Test::finish(): never in /home/vagrant/php-test.php on line 11
エラーの内容は『Test2::finish(): int の宣言は Test::finish(): never と互換性がなければならない』。
これは実行時のエラーではなく構文エラーで致命的です。
そりゃそうですよね?
neverはintの一部であって、intがneverの一部じゃないので型のオーバーライド(上書き)ができるわけない。
それなら『で』じゃなくて『から』にしてほしい。日本語に細かすぎて申し訳ないけど。
正解はこちらです。
class Test {
public function finish() : int
{
return 100;
}
}
class Test2 extends Test {
public function finish() : never
{
exit();
}
}
$test2 = new Test2;
$test2->finish();
これを実行してもエラーは発生しません。
まぁ、ふつうは『neverはすべての型の部分型』のところに着目するので、『そんな間違いしねーよ』と言われればそれまでですが。
never型宣言のエラーパターン
ここからは、never型を使ったときに何がエラーになるのか見ていきましょう。
return を書いてはいけない
function test(int $param) : never
{
return;
}
PHP Fatal error: A never-returning function must not return in /home/vagrant/php-test.php on line 3
never型は必ず処理を戻らせてはいけないのでreturnは使えません。構文エラーなので定義しただけでエラーになります。
たんにreturnを抜いてみた
returnを書いちゃいけないということなので、今度は抜いてみましょう。
function test(int $param) : never
{
}
var_dump(test(1));
PHP Fatal error: Uncaught TypeError: test(): never-returning function must not implicitly return in /home/vagrant/php-test.php:3
Stack trace:
#0 /home/vagrant/php-test.php(5): test()
#1 {main}
thrown in /home/vagrant/php-test.php on line 3
今度は例外発生です。さっきとちがって関数を定義しただけでは何も起きません。関数をコールしてはじめてエラーになります。
エラー理由は『暗黙のreturnをしてはいけない』。
returnを書いてないときは暗黙のvoid型を返します。それをしちゃいけないということ。
冒頭で言ったように、プログラムを終了させる処理を必ず入れないと、この例外は発生し続けます。
条件付きでプログラム終了
たんに関数の最後にexit()を入れても面白くないので、ちょっとひねって条件付きでexit()をコールする関数にします。
function test(bool $param) : never
{
if ( $param ) {
echo 'program exit...' . PHP_EOL;
exit();
}
echo 'program continue...' . PHP_EOL;
}
もう、予想がついている人はいるんじゃないでしょうか?
test(true);
program exit...
test(false);
program continue...
PHP Fatal error: Uncaught TypeError: test(): never-returning function must not implicitly return in /home/vagrant/php-test.php:9
Stack trace:
#0 /home/vagrant/php-test.php(11): test()
#1 {main}
thrown in /home/vagrant/php-test.php on line 9
条件付きでは、プログラムが終了する条件のパラメータを渡したら正常に終了しました。一方、falseを指定してプログラムが終わらない条件になったら例外が発生しました。
最初は違和感がありましたが、これはこれで理にかなってると思います。
だって、never型の条件に関数内での例外スローがあるので、それに従ってるから。逆を言うと、これがあるから特定の条件、returnなしの場合は構文エラーにならない。
クラスベースなら気にせずneverにできる?
クラスベースのプログラミングでは、特定の条件で例外が発生することはよくあります。というか、そういうプログラミングをする。
それを考えると、クラスのメソッドならたいていnever型が宣言できる。メソッドのプログラムの書き方がnever型が宣言できる条件に合致しているから。
PHPもクラスベースのプログラミングをするよう暗に進めてるところがあり、クラスで書くなら気にせず宣言できるよ~、楽だよ~、ということなのだと思います。