PHP8.1で、交差型という新しい型が登場しました。
最初、『交差? なんぞや?』と思いましたが、なんてことない、PHP8.0で追加されたunion型の『かつ(&)』バージョンです。
これも分かりづらい?
大丈夫です。コードを見れば一発で分かります。
交差型は、すべてを満たすので '&' でつなぐ
if文などの条件には、複数条件をつなぐ演算子『||(または)』『&&(かつ)』がありますよね?
それのデータ型バージョンがunion型と交差型です。
型の場合は、パイプ(|)とアンパサンド(&)がそれぞれひとつだけ。
意味も条件のときと同じ。『|(または)』『&(かつ)』
そりゃ、同じですよね? ちがったらブーイングの嵐です。
union型はPHP8.0で追加されました。
そしてPHP8.1で『&』バージョンの交差型が追加されました。
交差型の説明を言葉でするとたったこれだけ。
これ、日本語名が分かりにくい。交差って言われるとイメージが湧かない。
交差型でインタフェースの型を厳密にチェックする
ここからは実際にコードを動かしてみましょう。交差型の一番の使いどころはインタフェースです。
インタフェースは継承もでき、クラスにいくつもimplementsできます。しかし、クラスの型として使用するときはひとつしか指定できませんでした。
これが交差では厳密でピンポイントに型を指定できます。
インタフェースの親子関係はこうなってるとします。
ParentTest │ ┌──────────┐ Child1 Child2 │ │ ┌────┐ ┌────┐ Child_1_1 Child_1_2 Child_2_1 Child_2_2 |
赤線の型だけを受け入れるコードはこちら。
<?php
interface ParentTest{};
interface Child1 extends ParentTest{};
interface Child2 extends ParentTest{};
interface Child_1_1 extends Child1{};
interface Child_1_2 extends Child1{};
interface Child_2_1 extends Child2{};
interface Child_2_2 extends Child2{};
function test(Child_1_1&Child_1_2&Child_2_1 $types)
{
return;
}
test()を使ってコードを実行してみましょう。もちろん、実行するPHPのバージョンは8.1です。
class Test implements Child_1_1, Child_1_2, Child_2_1{};
test(new Test());
echo 'program end' . PHP_EOL;
program end
まずは、型が唯一合うものから。
交差型は&なので、functionのパラメータで指定した3つのインタフェースをすべてimplementsしたクラスしか受け付けません。
それ以外はすべて TypeError例外が発生します。
3つのうちいくつか欠けたもの、同じ親をもつインタフェース、全共通の親インタフェースなどいろんなパターンも実行してみましょう。
もちろん結果はすべて、TypeError例外です。
class Test implements ParentTest{};
test(new Test());
PHP Fatal error: Uncaught TypeError: test(): Argument #1 ($types) must be of type Child_1_1&Child_1_2&Child_2_1, Test given, called in /home/vagrant/php-test.php on line 17 and defined in /home/vagrant/php-test.php:11
Stack trace:
#0 /home/vagrant/php-test.php(17): test()
#1 {main}
thrown in /home/vagrant/php-test.php on line 11
class Test implements Child1, Child2{};
test(new Test());
PHP Fatal error: Uncaught TypeError: test(): Argument #1 ($types) must be of type Child_1_1&Child_1_2&Child_2_1, Test given, called in /home/vagrant/php-test.php on line 17 and defined in /home/vagrant/php-test.php:11
Stack trace:
#0 /home/vagrant/php-test.php(17): test()
#1 {main}
thrown in /home/vagrant/php-test.php on line 11
class Test implements Child_1_1, Child_2_1{};
test(new Test());
PHP Fatal error: Uncaught TypeError: test(): Argument #1 ($types) must be of type Child_1_1&Child_1_2&Child_2_1, Test given, called in /home/vagrant/php-test.php on line 17 and defined in /home/vagrant/php-test.php:11
Stack trace:
#0 /home/vagrant/php-test.php(17): test()
#1 {main}
thrown in /home/vagrant/php-test.php on line 11
もうこのへんでいいでしょう。&(かつ)なんだから最初のコード以外通るわけがないので。
ちなみに、同じ条件のパラメータ制限をPHP7以前でやろうとすればこうなります。
/**
* PHP7以前
*/
function test(object $types)
{
if (!$types instanceof Child_1_1 && !$types instanceof Child_1_2 && !$types instanceof Child_2_1) {
throw new TypeError();
}
return;
}
このコードの場合、すべての共通の親がいるので(ParentTest)、objectはParentTestに変えてもいいですが、共通の親を持たないことも考えてobject型にしました。
どれだけコードがスリムになるのか分かるでしょう。
最後に型定義で親を指定して、その子を渡してみましょう。
function test(Child1&Child_2_1 $types)
{
return;
}
class Test1 implements Child_1_1, Child_1_2, Child_2_1{};
class Test2 implements Child_1_1, Child_2_1{}; <--- 余計
class Test3 implements Child_1_2, Child_2_1{}; <--- 余計
class Test4 implements Child1, Child_2_1{}; <--- 余計
test(new Test1());
test(new Test2());
test(new Test3());
test(new Test4());
echo 'program end' . PHP_EOL;
program end
一見、同じ条件のパラメータが通過するのでOKに見えますが、Child1の子が欠けたときも通るし、親のChild1をimplementsしたのも通ります。
制限がゆるくなってるのが分かるでしょう。
PHP公式ドキュメント
クラスの型は交差型を使う意味がない
インタフェースの話ばかりしてクラスはどうした? と思った人もいたでしょう。
じつはクラスでは、交差型を使う意味がありません。
ParentTest │ ┌──────────────┐ Child1 Child2 │ │ ┌─────┬─────┐ ┌─────┐ GChild_1_1 GChild_1_2 GChild_1_3 GChild_2_1 GChild_2_2 |
クラスはピラミッド構造の階層になっていて、クラスが継承できる親クラスは1つです。
クラスの型を定義するのは自身の型か親の型だけ。上記の赤線部分に制限したければ、union型を使います。
function test(Child_1_1|Child_1_2|Child_2_1 $types)
{
return;
}
気をつけないといけないのは、if文の条件とちがって交差型とunion型の混同はできません。
もちろん、()を付けて優先度をコントロールもできない。
これが交差型の現状です。
複数のクラスを交差型で定義すると、それを通過するクラスは存在しえないので、つねにTypeError例外が出るバグになってしまいます。
親と子の交差型は通りますが、その定義には意味がありません。
(それなら子のクラスの定義だけでいい。)
クラスとインタフェースが混ざった交差型は使えるので、そこでコントロールしてもいいでしょう。
PHP公式ドキュメント
int型とbool型を交差型で指定するとどうなるのか?
ふと、intとboolで交差型したらどうなるんだろうと思いましたが、交差型で使えるのは定義されたクラスとインタフェースだけです。
useを使ったエイリアスも定義されたとはいえないので使えません。
となると実質インタフェースだけに使うんじゃないかな?
それでも十分だけど。
PHP公式ドキュメント