読者です 読者をやめる 読者になる 読者になる

mb_convert_kanaの中身を見てみる

「mb_convert_kanaで文字コード指定しないと誤変換する場合がある」d:id:maru_cc:20080212:1202813263 という現象があり、文字コード周りで久々にはまった。
そんな話を同僚に話したら、デフォルトでinternal_encodingの設定にしたがってくれればいいのに。という意見が。
確かにそうだなーと思った。


で、実際に中身がどうなっているのか見てみた。
php5.2.5をcscopeで見てみる。
まずは、mb_convert_kanaで検索。目印は「PHP_FUNCTION」
mbstring.c line:2887

/* {{{ proto string mb_convert_kana(string str [, string option] [, string encoding])
   Conversion between full-width character and half-width character (Japanese) */
PHP_FUNCTION(mb_convert_kana)
{

このあたり。
まず、宣言周りがある

    int opt, i;
    mbfl_string string, result, *ret;
    char *optstr = NULL;
    int optstr_len;
    char *encname = NULL;
    int encname_len;

    mbfl_string_init(&string);
    string.no_language = MBSTRG(current_language);
    string.no_encoding = MBSTRG(current_internal_encoding);


で、次にパラメータ取得らしき動作が。

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ss", (char **)&string.val, &string.len, &optstr, &optstr_len, &encname, &encname_len) == FAILURE) {
        return;
    }

zend_parse_parametersを調べてみたところ、zend_API.cにそれらしい関数がある。
パラメータ取得の動作だと思う。たぶん。


で、次。

    /* option */
    if (optstr != NULL) {
        char *p = optstr;
        int n = optstr_len;
        i = 0;
        opt = 0;
        while (i < n) {
            i++;
            switch (*p++) {
            case 'A':
                opt |= 0x1;
                break;
(略)
            case 'm':
                opt |= 0x200000;
                break;
            }
        }
    } else {
        opt = 0x900;
    }

延々とパラメータのcase文が。


次にそれらしい目的のところが。

    /* encoding */
    if (encname != NULL) {
        string.no_encoding = mbfl_name2no_encoding(encname);
        if (string.no_encoding == mbfl_no_encoding_invalid) {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown encoding \"%s\"", encname);
            RETURN_FALSE;
        }
    }

ただし、よく見るとencnameが指定された場合の挙動のようだ。


最後に、変換部分と戻り値あたり。

    ret = mbfl_ja_jp_hantozen(&string, &result, opt);
    if (ret != NULL) {
        RETVAL_STRINGL((char *)ret->val, ret->len, 0);      /* the string is already strdup()'ed */
    } else {
        RETVAL_FALSE;
    }
}
/* }}} */

mbfilter.cが変換の挙動みたいだ。
全角半角変換以外にカタカナひらがな変換とかもしているのにmbfl_ja_jp_hantozenなんて関数名は昔のなごりだろうか。
ちょっと面白い。


で、よくよく見ると…
最初の宣言部分で

string.no_encoding = MBSTRG(current_internal_encoding);

internal_encodingを参照しているようだ。


ここで、ん?と。
今回問題が発生した該当のサーバで、phpinfoを書いたスクリプトをコマンドラインで動かしてみたところ、

mbstring.internal_encoding => EUC-JP => EUC-JP
mbstring.language => Japanese => Japanese

EUC-JPだ。。。


どうやら、UTF-8の設定になっていると思い込んでいたのは、httpd.confレベルでphp_valueを使用し設定していたようだ。
つまり、internal_encodingがEUC-JPのまま、UTF-8の文字列を変換にかけてしまっているようだった。


そーいえば、こんな問題が前にもあったなぁ。。。


その時の対応は、cli実行時用のUTF-8を指定したphp.iniを別途用意し、実行時にコマンドラインオプションで指定する方法で回避できる。

/path/to/php -c /path/to/cli_php.ini /path/to/script.php

--php-ini という長いオプションもある。
http://jp.php.net/manual/ja/features.commandline.php


前に、同じ問題ではまったことがあるのに、すっかり忘れてた。


追記(2008-02-15)
phpのコマンドラインオプション」でいろいろ調べてみました
d:id:maru_cc:20080214:1203007176