HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

関数やメソッドの引数のタイプヒント指定の罠

久しぶりにネタとしてメモしておきたい事象にぶつかったのでメモ。

「罠」とは言っても、熟練のPHPerにとっては当たり前のことなんだろうけど‥

PHP(PHP 5環境)で以下のようなコードを書いていた。

<?php

function hoge_int(int $val) {
    var_dump(__FUNCTION__, $val);
}

hoge_int(10);

さて、これを実行してみると‥

PHP Catchable fatal error:  Argument 1 passed to hoge_int() must be an instance of int, integer given, called in C:\temp\hoge.php on line 7 and defined in C:\temp\hoge.php on line 3

一瞬、何のことだか意味が分かりません。
渡しているのはint(integer)値だし、タイプヒント指定と食い違っているようには見えない。
そこで、エラーメッセージを元に検索してみると、以下のサイトを見つけた。

types - Really PHP? "Argument 1 passed to my_function() must be an instance of string, string given" - Stack Overflow

まさかと思いながら、以下のコードを試してみる。

<?php

function hoge_int(int $val) {
    var_dump(__FUNCTION__, $val);
}

final class int {
    private $_val;
    public function __construct($val) {
        if (is_int($val)) {
            $this->_val = $val;
        } else {
            trigger_error('No, you fool!', E_USER_NOTICE);
        }
    }
    public function __toString() {
        return (string)$this->_val;
    }
}

hoge_int(new int(10));

すると、以下の出力を得ることができる。関数が正常に呼ばれた証拠。

string(8) "hoge_int"
object(int)#1 (1) {
  ["_val":"int":private]=>
  int(10)
}

さて、先のエラーメッセージをよーく読んでみると、こんな風に書いてある。

must be an instance of int, integer given

「instance of int」を渡すべきところで「integer」を渡している、と。

そこで、さらに以下のようなことをしてみる。

<?php

function hoge_int(int $val) {
    var_dump(__FUNCTION__, $val);
}

final class int {
    private $_val;
    public function __construct($val) {
        if (is_int($val)) {
            $this->_val = $val;
        } else {
            trigger_error('No, you fool!', E_USER_NOTICE);
        }
    }
    public function __toString() {
        return (string)$this->_val;
    }
}

final class integer {
    private $_val;
    public function __construct($val) {
        if (is_integer($val)) {
            $this->_val = $val;
        } else {
            trigger_error('No, you fool!', E_USER_NOTICE);
        }
    }
    public function __toString() {
        return (string)$this->_val;
    }
}

hoge_int(new integer(10));

すると、こんなエラーメッセージが。

PHP Catchable fatal error:  Argument 1 passed to hoge_int() must be an instance of int, instance of integer given, called in C:\temp\hoge.php on line 35 and defined in C:\temp\hoge.php on line 3

今度は、「instance of int」を渡すべきところで「instance of integer」を渡している、と書かれている。
おぉ、いわゆるオブジェクト型でないといけないということじゃないか‥


あんまり話を引っ張っても別に面白くないので、上記サイトからもリンクされているPHPのマニュアル「Type Hinting/タイプヒンティング」を見てみる。

よく読めばちゃんと書いてあるじゃないか‥

Type hints can not be used with scalar types such as int or string.
タイプヒントは int や string といったスカラー型には使えません。 

マニュアルによれば、タイプヒントとして指定可能なのは以下のものらしい。

  • クラス名
  • インタフェース名
  • 配列 (PHP 5.1以降)
  • callable (PHP 5.4以降)

また、タイプヒントが付いたパラメータにはNULLを渡すことはできないが、パラメータのデフォルト値として「= null」と指定しておけばnullを渡すことも可能らしい。

じゃあ、スカラー型の指定を強制したい場合はどうするかというと、冒頭に挙げたサイトにも書いてあるが、

<?php

function hoge_int($val) {
    if (!is_int($val)) {
        trigger_error('No, you fool!', E_USER_ERROR);
    }
    var_dump(__FUNCTION__, $val);
}

hoge_int(10);

というコートを書くしかないらしい。


なんか、Autoboxing/Unboxingが実装される前のJavaでプリミティブ型の値をラッパークラスを使ってオブジェクトにしてからListに放り込むのめんどくさい‥なんてことをしていたのを思い出した‥