ツイート
シェア
LINEで送る
B! はてぶでブックマーク
Pocketでブックマーク
RSSフィード

PHP8.1, 交差型って何? なんだ、すべてを満たす型ってことか。

php
イラストダウンロードサイト【イラストAC】
の画像をもとに加工しています。

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
1つでもインタフェースが欠ける
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したのも通ります。

制限がゆるくなってるのが分かるでしょう。

クラスの型は交差型を使う意味がない

インタフェースの話ばかりしてクラスはどうした? と思った人もいたでしょう。

じつはクラスでは、交差型を使う意味がありません。

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を使ったエイリアスも定義されたとはいえないので使えません。

となると実質インタフェースだけに使うんじゃないかな?

それでも十分だけど。

前の投稿
PHP8.1, 第一級callable, コールバックの指定がより簡潔で分かりやすくなった。
PHP8.1, never型, PHPプログラムが終了することを示す。関数やメソッド専用の型
次の投稿
コメントを残す

*