2020年も2か月が過ぎ、PHPの開発は次期メジャーバージョンの8.0のリリースに向けた開発が順調に進んでいます。順調に開発が進めば2020年末までにPHP 8.0のリリースが行われることが予想されていますが、現時点でリリースに向けたスケジュールは定義されていません。
PHP 8.0の最大の強化点はJIT(Just-In-Time)コンパイラの導入による高速化となります。JITについてはこのコラムで紹介してきましたので、今回からその他の変更点について、紹介していきます。
PHP 8.0の新機能
今回は、PHP 8におけるJIT以外の機能強化点として、言語に関連する以下の項目について紹介します。いずれの機能もPHP 7.4までに導入された機能をさらに強化したもので、PHPによるプログラミングをさらに便利にしてくれるものです。
- Union型の導入
- static型戻り値指定
- 弱いマッピング
- ::classのオブジェクトへの適用
Union型の導入
PHPでは、タイプヒンティングにより、関数(メソッド)の入力型および戻り値の型指定が可能です。従来、この型は1種類のみが指定可能でしたが、PHP 8.0ではUnion型として’|’を挟んで複数の型の候補を指定できるようになります。
リスト 1 Union型の例
<?php
class Test {
public int|float $x;
public function foo(Foo|Bar $in) : int|float {
return 1.23;
}
}
class Foo {};
class Bar {};
$obj = new Test();
$obj->foo(123); // Fatal error
$obj->foo(new Foo); // 1.23
$obj->x = "123";
var_dump($obj->x); // int(123)
$obj->x = "1.23";
var_dump($obj->x); // float(1.23)
リスト1にUnionのサンプルを示します。Testクラスのfooメソッドは以下のように宣言されており、引数としてFooクラスまたはBarクラスを受け取り、intまたはfloat型の戻り値を返します。
public function foo(Foo|Bar $in) : int|float {
nullを許容する(nullable)場合には、Foo|Bar|null のように null を引数に含めます。?Fooのような省略記法はUnionと併用して使用できないことに注意してください。
ここで、FooまたはBar以外の値を引数として入力すると、致命的なエラー(Fatal error)を発生します(13行目)。
プロパティ変数についても以下のようにUnionによる複数型指定が可能です。
public int|float $x;
文字列で数値を指定すると、Unionの選択肢の中にある型(この場合はintまたはfloat)について、PHPの従来の変換ルールにより整数はint型、浮動小数点数はfloat型に変換されます(15~18行目)。
static型戻り値指定
PHP 8.0では、関数・クラスメソッドの戻り値に特殊なクラス名staticを付加することで、遅延静的束縛(late static binding)が指定できるようになります。 ここで、staticが指定できるのは戻り値の型指定のみであり、入力引数やプロパティ変数の型指定では使用できないことに注意してください。リスト2に簡単な例を示します。まず、メソッドgen1,gen2を有するクラスAと、クラスAの派生クラスBを定義します。gen1メソッドはnew selfにより自身の新規インスタンスを返しますが、派生クラスBでこのメソッドを用いた場合でも、親クラスであるクラスAのインスタンスを返します(10行目)。一方、gen2メソッドではstaticクラスを返すため、派生クラスBでコールされるとクラスBのインスタンスを返します(11行目)。
ここで、PHP 8から導入された戻り値のstatic型指定を行ってみましょう。具体的には3、4行目を以下のように書き換えます。
public function gen1():static { return new self; }
public function gen2():static { return new static; }
この場合、戻り値がstaticでない10行目を実行すると以下のようなTypeErrorを発生します。
TypeError: Return value of A::gen1() must be of Type B, A returned
static型指定による型チェックが機能していることがわかります。
リスト 2 static型戻り値指定の例
<?php
class A {
public function gen1() { return new self; }
public function gen2() { return new static; }
}
class B extends A {}
$a = new A;
$b = new B;
var_dump($b->gen1()); // object(A)#3
var_dump($b->gen2()); // object(B)#3
弱いマッピング
PHP 7.4で弱いリファレンスが導入されましたが、破棄時のコールバックが提供されなかったため、その用途は限定的でした。PHP 8では弱いマッピング(Weak maps)が導入されます。弱いマッピングがオブジェクトへのリファレンスを保持します。このマッピングオブジェクトは、オブジェクトがガベージコレクションの対象の対象となることを妨げません。あるオブジェクトのキーがガベージコレクションの対象となった場合、当該マッピングは削除されます。
リスト3に弱いマッピングの例を示します。弱いマッピングのオブジェクト$mapを作成し、オブジェクト$objをキーとして、値を代入します。このケースでは、オブジェクト$objのハッシュ値を値として代入します(4行目)。変数$mapをダンプすると、以下のような出力となり、配列のキーにオブジェクト、値に代入した値が保持されていることがわかります(5行目)。
object(WeakMap)#1 (1) {
[0]=>array(2) {
["key"]=>object(stdClass)#2 (0) {}
["value"]=>string(32) "000000001c2bbea3000000007d9890f1"
}
}
変数$mapにオブジェクトをキーとしてアクセスすると、代入した値を取得することができます(6行目)。
次に、代入したオブジェクト$objを削除します。オブジェクト$objを削除後、変数$mapを再度ダンプすると、オブジェクト$objへの参照が削除されていることがわかります(8行目)。通常のオブジェクト代入の場合には、元のオブジェクトを削除しても、リファレンスが残るため、オブジェクトの実体は削除されません。しかし、弱いマッピングの場合、オブジェクトの実体はガベージコレクションの対象となり、逆にマッピングが削除されます。
リスト 3 弱いマッピングの例
<?php
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = spl_object_hash($obj);
var_dump($map); // object(WeakMap)#1 (1)
var_dump($map[$obj]); // $objのハッシュ値
unset($obj);
var_dump($map); // object(WeakMap)#1 (0)
::classのオブジェクトへの適用
クラスの名前を表す文字列をクラス名に::classをつけることで取得できます。一方、オブジェクト変数のクラス名を取得する際には、従来、get_class()関数を使用していました。PHP 8.0では、オブジェクト変数に::classをつけることでクラス名を取得できるようになります。リスト4にクラス名を参照する例を示します。get_class()関数を用いる場合と動作は変わりませんが、より直感的にコードがわかりやすくなる効果が期待できます。
リスト4 クラス名参照の例
<?php
class A {}
$a = new A;
echo A::class; // A
echo get_class($a); // A
echo $a::class; // A
今回は、PHP 8.0における主な機能強化点を紹介しました。PHP自体はすでにPHP 7までに成熟した環境であるため、言語仕様に大きな変更点はなく、さらに使いやすくするための進化をとげている印象です。次回は、その他の変更点について紹介します。