PHPのmbstring関数、arrayを突っ込むとfalseが返ってくるのはPHP 5.6まで
TOP > てきとうにこらむ > ゲーム作りとプログラミング日記 > PHPのmbstring関数、arrayを突っ込むとfalseが返ってくるのはPHP 5.6まで
最近あった思い込み
PHPでは、標準搭載された関数(ビルトイン関数)にはマニュアルに載ってない挙動をすることがある。何を言ってるのかというと
- 引数がstringを指定されてるのにarrayやobjectを突っ込む
- 引数の数が違う
みたいな話。そういうとき、ビルトイン関数は多分NULLが返ってくるということになっている。http://php.net/manual/ja/functions.internal.phpを引用すると
注意: 関数へのパラメータとして関数が想定しているのとは異なるものを渡した場合、 例えば文字列を想定しているところに配列を渡した場合などの場合は関数の返り値は未定義となります。たいていの場合は NULL を返すでしょう。しかしこれはあくまでも規約にすぎず、これに依存することはできません。
ということなので、タイトルの通りにarrayを突っ込むと「多分NULLだが関数によって違う」のである。その例の一つとしてmbstringモジュールの関数がそうで、mb_strlenなどではfalseになっていたのである。
mbstringは長らくfalseだった
大体のmbstringの関数は、stringが要求されているときにarrayを突っ込むとfalseが返ってくる。
PHP 5.6で試してみよう。
$ php -r 'var_dump(mb_strlen(array()));'
PHP Warning: mb_strlen() expects parameter 1 to be string, array given in Command line code on line 1
bool(false)
こんなふうになっていたので、falseが返ってくるのか、程度にしか思ってなかった。
では、PHP 7.0ではどうなのか。
$ php -r 'var_dump(mb_strlen(array()));'
PHP Warning: mb_strlen() expects parameter 1 to be string, array given in Command line code on line 1
NULL
あれっ、NULLじゃん。全く知らなかった…
ちなみに、他のmbstringの関数もNULLに変わってた。
ソースを読んでみる
php-srcの「引数を解析して、失敗(不正な引数)だったら」というのはだいたい下のコードに当たる。
PHP 5.6.38のコード https://github.com/php/php-src/blob/PHP-5.6.38/ext/mbstring/mbstring.c#L2212
PHP_FUNCTION(mb_strlen)
{
int n;
mbfl_string string;
char *enc_name = NULL;
int enc_name_len;
mbfl_string_init(&string);
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", (char **)&string.val, &string.len, &enc_name, &enc_name_len) == FAILURE) {
RETURN_FALSE;
}
PHP 7.0.0のコード。https://github.com/php/php-src/blob/PHP-7.0.0/ext/mbstring/mbstring.c#L2220
PHP_FUNCTION(mb_strlen)
{
int n;
mbfl_string string;
char *enc_name = NULL;
size_t enc_name_len;
mbfl_string_init(&string);
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", (char **)&string.val, &string.len, &enc_name, &enc_name_len) == FAILURE) {
return;
}
両者のうち、zend_parse_parametersのifブロックに注目してほしいのだけど、PHP 5.6.38ではRETURN_FALSEマクロで片付けていたのに、PHP 7.0.0ではreturnとなっている。
RETURN_FALSEはPHPのfalseを返している(return_value構造体にFALSEをセットしている)。そのままreturnする場合には、NULLになる(return_value構造体に何もしないため)。
いつから変わった
該当のコミットは c998bfaf8600bc38dbcd86b84a6469ed06468012 である。コードとしては約4年前、バージョンとしては7.0.0から入った。
PHP 5.6.x から PHP 7.0.x への移行にもこの内容はなかったように見受けられる。
明らかにおかしい引数を渡す場合、mbstringではエラー処理などでfalseを想定しているコードを書いている場合、思わぬ落とし穴にハマることがある。(そんなコードを書く人がいるのかはちょっとわからないけど)
この件でNULLではないmbstring関数
試したバージョンはPHP 7.3.0RC6。
リストを書くのが面倒だったので以下のプログラムを記述した。
結果、以下の関数がNULL以外を返すようだ。E_WARNINGは無視していることに注意
function | 結果 |
---|---|
mb_check_encoding | true |
mb_convert_case | false |
mb_detect_order | false |
mb_ereg_match | false |
mb_ereg_replace_callback | false |
mb_ereg_replace | false |
mb_ereg_search_getpos | 0 |
mb_ereg_search_getregs | false |
mb_ereg | false |
mb_eregi_replace | false |
mb_eregi | false |
mb_regex_set_options | false |
mb_split | false |
mb_substitute_character | false |
※mb_check_encodingがなぜNULLでもfalseでもなくtrueを返しているのかというと、PHP 7.2からmb_check_encodingは配列を受け入れられるようになったためである。つまり、空の配列というものにエンコーディングのチェックをかけても、エンコーディング上問題がないのでtrueになる。結果として、5.6ではfalse、7.0、7.1はNULL、7.2以降はtrueになることになる。
実は知らなかった
ぼく自身も、知らなかったので非常に恥ずかしいと思いながらこのエントリを書いてる。本当なら知ってなきゃいけないのにって思いながら。とはいえ、5.6から7系に移るときに何かしらの情報がないと大変だろうし(こういうのってCIとかで捕まえられそう?)忘備録として残しておきます。