Edited at

ジェネリクスがないPHPでも配列中身のタイプヒントを可能にする「Splat Operator」

More than 1 year has passed since last update.

PHP7現在ではジェネリクスっぽいタイプヒントが書けない。したがって、配列の中身の型が決まっている関数でも、タイプヒントにはarrayを受け取ることを明示するのが最大限だ。

ジェネリクスに対応している言語であれば、doSomething(array<DateTime> $dates): voidのように配列の中の型まで固められる。このおかげで、事前条件チェックが効いて期待しない型の混入にも気づきやすいが、PHPではそうもいかない。なかなか歯がゆいものがある。

<?php

/**
* @param DateTime[] $dates
*/

function doSomething(array $dates): void
{
}

$dates = [
new DateTime('today'),
new DateTime('tomorrow'),
new DateTime('yesterday'),
null, // 🔥DateTimeじゃないものが紛れている🔥
];

doSomething($dates);

このコードは実行してもエラーにならない。

phpdoc(関数の上のコメント)にDateTime[]と書いてあるが、IDEや静的解析ツールではこれを見て検査してくれるかもしれないが、PHP実行時には何の拘束力にもならない。

PHP7.1ではstringなどのタイプヒントも実装され、いよいよphpdocでのドキュメントなしでも、関数に何を渡したら良いかを伝えられるコードが書きやすくなった。一方で、配列を受け取る関数ではまだまだphpdocを書かないとユーザやIDEに関数の仕様を伝えにくいという別の問題もある。


配列の中身をタイプヒントする裏ワザ

極めて限定的な局面になるが、配列の中身の型を明示する方法がPHPにもある。Splat Operatorを使う方法だ。...がそれだ。これは可変個引数の関数を実装するためのものだが、タイプヒントとも組み合わせることができる。

先程のコードをSplat Operatorで実装しなおすと次のようなコードになる。

<?php

function doSomething(DateTime... $dates): void
{
}

$dates = [
new DateTime('today'),
new DateTime('tomorrow'),
new DateTime('yesterday'),
null, // 🔥DateTimeじゃないものが紛れている🔥
];

doSomething(...$dates);

コレを実行すると関数呼出し時にエラーになる。


PHP Fatal error: Uncaught TypeError: Argument 4 passed to doSomething() must be an instance of DateTime, null given


Splat Operatorはあくまで可変個引数関数を実装するための機構なので限界はある。まず、配列を複数受け取る関数ではこの裏ワザは使えない。f(array $a)f(A... $a)に書き換えできるが、f(array $a, array $b)f(A... $a, B... $b)とはできない。

また、Splat Operatorは最後の引数にしか使えない。f(string $a, array $b)f(string $a, A... $b)に書き換えできるが、引数が逆のf(array $a, string $b)f(A... $a, string $b)と書くことはできない。

限定的な場面でしか使えないが、arrayとだけタイプヒントに書くよりは固いコードが書けそうではある。