TypeErrorと型エラーの向き合い方、strict_typesと暗黙変換
TypeErrorとは、宣言した型と合わない値が渡されたときに投げられるエラーです。declare(strict_types=1)を書くと暗黙の型変換を止めて厳密に検出できます。
デフォルトは黙って型を変換してくれる
PHPは既定だと親切心を発揮して、引数の型が合わないときにこっそり変換を試みます。int を要求する関数に ‘3’ を渡すと 3 に変換される、みたいな挙動ですね。動いてしまうがゆえに、想定外の値が混ざっても気づかない、という危うさがあります。
function half(int $n): int
{
return intdiv($n, 2);
}
echo half('10'); // 5 になる。文字列が黙って変換される
echo half('10abc'); // これは TypeError(数値として解釈できない)
’10’ が通ってしまうと、本来数値であるべき箇所に文字列が流れ込んでいても検出できません。境界で型を守れていない状態ですね。
declare(strict_types=1)で暗黙変換を止める
ファイル先頭に declare(strict_types=1) を書くと、その挙動が変わって、宣言した型と厳密に一致しない引数は TypeError になります。int の float への拡大だけは例外的に許されますが、それ以外の勝手な変換はしなくなるので、バグが早い段階で表面化します。私は基本的に全ファイルで有効にしています。
declare(strict_types=1);
function half(int $n): int
{
return intdiv($n, 2);
}
echo half('10'); // TypeError: 文字列は受け付けない
この宣言は「呼び出し側のファイル」の設定で判定される点が地味に重要で、関数を定義したファイルではなく、呼んでいるファイルの declare が効きます。
入力は境界で明示的に変換する
strict にすると外部入力、たとえばフォームやクエリ文字列は全部 string で来るので、そのまま int の引数には渡せません。ここで暗黙変換に頼るのではなく、境界で明示的にキャストや検証をするのが筋がいいと思います。意図しない値はここで弾けます。
$raw = $_GET['page'] ?? '1';
if (!ctype_digit($raw)) {
throw new InvalidArgumentException('pageは整数で指定してください');
}
$page = (int) $raw; // 検証を通してから変換
half($page);
まとめ
暗黙変換は一見便利ですが、型の食い違いを隠してしまうので、私は strict_types を有効にして境界で明示変換する方針が好みです。TypeError は敵ではなく、間違った値が奥まで進む前に止めてくれる番人だと思うと、付き合い方が変わってくる気がします。
よくある質問
Q. strict_typesは付けたほうがいいですか?
A. 私は基本的に全ファイルで有効にしています。暗黙変換を止めて、型の食い違いを早い段階で表面化させられます。
Q. strict_typesはどこの設定が効きますか?
A. 関数を定義したファイルではなく、呼び出し側のファイルのdeclareが効く点に注意が必要です。
Q. strictにすると外部入力はどう扱いますか?
A. フォームやクエリは全部stringで来るので、境界でctype_digitなどの検証を通してから明示的にキャストします。