【PHP7.4】PHP7.4では変数に型指定できる & 菱形継承っぽいものができる


2021/06/07追記

PHP8.1から交差型が使えるようになります。

ここから本文

PHPのすごい開発者Nikita Popovが書いたTyped Properties and more: What's coming in PHP 7.4?というスライドを見ていたらなにやら面白かったので、適当に紹介してみる。

変数の型指定

PHP7.4ではプロパティに型指定できるようになりましたが、実はプロパティではない只の変数にも型指定することができます。

class Dummy{
    public int $id = 42;
}
$dummy = new Dummy();

$id = & $dummy->id;

$id = 10; // OK
$id = 'not an id'; // Uncaught TypeError

ええ…

なんかもう普通にint $id = 42とか書けるようにしたほうがいい気もしないでもないですが、構文解析とかの都合で難しいのでしょう。

なんにせよ、これで変数の型指定もできるようになってしまいました。
このまま書くと非常にもったり感がありますが、きっと簡単に使えるライブラリが出てくることでしょう。

菱形継承

似たような方法でプロパティの菱形継承っぽいこと(交差型)も可能です。

class A{}

class B extends A{
    public ?Traversable $a;
}
class C extends A{
    public ?Countable $a;
}

$b = new B();
$c = new C();
$a =& $b->a;
$a =& $c->a;

$a = new RecursiveArrayIterator (); // OK
$a = new MultipleIterator(); // Uncaught TypeError: Cannot assign MultipleIterator

RecursiveArrayIteratorはTraversableとCountableをimplementsしているので代入可能ですが、MultipleIteratorはCountableをimplementsしていないので代入不可能です。
ということで菱形継承が可能になりました。

どんなときに役立つのか、よくわかりませんが。

親クラスには書けない

上記は親クラスに何も書かれていなかったので、本来の菱形継承ではありません。

親クラスでプロパティの型を指定した場合、子クラスでその制限を変更することはできません。
すなわち、以下のコードは残念ながら動きません。

動かない
class A{
    public Traversable $a;
}
class B extends A{
    public OuterIterator $a; // Fatal error: Type of B::$a must be Traversable
}

メソッドの型宣言は子クラスで狭めることができますが、プロパティはPHP7.4α1の時点では狭めることができません。
メソッドと動作を合わせるのであればこの書き方も許容されるべきですが、はたして修正されるでしょうか?
だめなままなのかな?

これが許可されるのであれば、以下のように自然な?形の菱形継承も可能になります。

動かない
class A{
    public ?Iterator $a;
}

class B extends A{
    public ?OuterIterator $a;
}
class C extends A{
    public ?RecursiveIterator $a;
}

$b = new B();
$c = new C();
$a =& $b->a;
$a =& $c->a;

$a = new RecursiveArrayIterator(); // OK
$a = new MultipleIterator(); // Uncaught TypeError: Cannot assign MultipleIterator

書けるようになったからといってどうするのかわかりませんが。

ジェネリクス

スライドを見ていたら、意外な構文が出てきました。

似非ジェネリクスではなく、本物のジェネリクスの導入を考えているみたいです。

まだ動かない
interface Event{}

class SpecificEvent implements Event{}

interface EventHandler<E: Event>{
  public function handle(E $e);
}

class SpecificEventHandler implements EventHandler<SpecificEvent>{
  public function handle(SpecificEvent $e){}
}

RFCもありませんし(Generic Types and Functionsは構文が異なる)、まだ思いつき段階といったところでしょうが、このひとNikitaですからね。
PHP8でジェネリクスが導入されていた、なんてことになったとしても驚きはないですね。

感想

変数の型指定は有用ですね。
定義方法さえ簡単になれば、あとパフォーマンスが極端に落ちるなどの問題がなければ、普通に使ってもよさそうです。
ただPHPの場合、stringを返す組み込み関数が普通にfalseを返してきたりするので、全てを置き換えるのはちょっと辛そうです。

菱形継承はそもそも役に立つ場面を知らないのでなんとも言えません。

ジェネリクスはどうなんですかね、PHPでそこまで必要なんですかね。
確かに配列要素の型を限定したいことは頻繁にありますが、しかし、あらゆるオブジェクトに型指定が欲しいとまではあまり思ったことがないですね。
今でもArrayAccessやらを使えば一応できますしね。