PHPのバージョンでZend_Jsonの動作が違っていた件2008年04月06日 04時05分27秒

Zend_Json::decode()って便利!と思っていたが

Zend_Json::decode()が割と使える。UNICODEエスケープされた文字(\uXXXX形式ね)をデコードできるからだ。

<?php
require_once 'Zend/Json.php';

$s = '"\u65e5\u672c\u8a9e"';
echo Zend_Json::decode( $s );

とかってすると、「日本語」と出力が得られる(内部エンコードがutf-8じゃないとあかんみたいだが)。

だもんで、クライアント側でJSでescape()したマルチバイト文字を、preg_replace_callbackを絡めて'%uXXXX'を'\uXXXX'に変換した上でダブルクォートで囲ってZend_Json::decode()に渡すようにして復元したりしていた。

が、Zend_Jsonの動きをたいして気にしていなかったため、ちみっとハマった。

PHPのバージョンの違いで、なんかヘン。

ある環境では上記のようなデコード処理がまったく問題なく動いていたのだが、他の環境で動かしたとたんに'\uなんて不正なエスケープだ!'とエラーがでるようになった。Zend Frameworkのバージョンはどちらも「1.0.0」を使っているのに。

文字コードの関連も、実行環境に依存しないように必ずdefault_charsetとmbstring.internal_encoding、mbstring.http_outputをコード中で指定してutf-8にあわせてあるし、違いといえばPHPのバージョン。

ためしに、

<?php
require_once 'Zend/Json.php';

$s = '日本語';
echo Zend_Json::encode( $s );

なんてのをやってみたところ、正常に動作する環境は
"\u65e5\u672c\u8a9e"
とUNICODEエスケープで出力されたが、うまく動かない環境のほうでは
"日本語"
と、まんまで出力されている。はて。

ソースを覗いてみたら

エンコード部分で動作に違いがでたので、Zend/Json/Encoder.php(Zend_Json_Encoder)のソースを見てみた。該当するのは _encodeString プロテクトメソッドか。

/**
 * JSON encode a string value by escaping characters as necessary
 *
 * @param $value string
 * @return string
 */
protected function _encodeString(&$string)
{
    // Escape these characters with a backslash:
    // " \ / \n \r \t \b \f
    $search  = array('\\', "\n", "\t", "\r", "\b", "\f", '"');
    $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"');
    $string  = str_replace($search, $replace, $string);

    // Escape certain ASCII characters:
    // 0x08 => \b
    // 0x0c => \f
    $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);

    return '"' . $string . '"';
}

(Zend/Json/Encoder.php より抜粋)
はて、マルチバイト文字の扱いとか特にやってる風ではないな。どないなっとんねん。

じゃあ、実際にコードから叩いているZend_Jsonのほうを見てみるか。

/**
 * Encode the mixed $valueToEncode into the JSON format
 *
 * Encodes using ext/json's json_encode() if available.
 *
 * NOTE: Object should not contain cycles; the JSON format
 * does not allow object reference.
 *
 * NOTE: Only public variables will be encoded
 *
 * @param mixed $valueToEncode
 * @param boolean $cycleCheck Optional; whether or not to check for object recursion; off by default
 * @return string JSON encoded object
 */
public static function encode($valueToEncode, $cycleCheck = false)
{
    if (function_exists('json_encode') && self::$useBuiltinEncoderDecoder !== true) {
        return json_encode($valueToEncode);
    }

    require_once 'Zend/Json/Encoder.php';
    return Zend_Json_Encoder::encode($valueToEncode, $cycleCheck);
}

(Zend/Json.php より抜粋)
ほあ!?json_encode()??

はぁ、PHP5.2.0からだったのねん

同様にZend_Json::decode()部分もjson_decode()が存在していたらそっちを利用するようになっていた。調べてみるとこの2つの関数は、JSON関数として、PHP5.2.0からは標準でインストールされるようになったPECL拡張モジュールで提供されている関数だったと。

先ほどの2つの環境、うまく動かないほうは5.1.6、正常なほうは5.2.5だもんで、なるほどこの通りになるのか。

先ほどのjson拡張モジュール自体はPHP4.3.0以降に適合するので、それをインストールすれば同様の動作になるけど、Zend_Json関連を使うときは一応PHPのバージョンを気にしておいたほうがよいかも。

オマケ

前半部分でescape()した文字のデコード目的で使用、ってな話を書いたけど、「escape()って必ずUNICODEエスケープなのか?」ってのに自信がなくなったので調べてみたら、こんな一覧表が見つかった。

なるほど現在普通に使われるようなブラウザならたいていUNICODEエスケープとみて間違いないかな(MacユーザでiCab使ってる人いたらごめんなさい)。

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※なお、送られたコメントはブログの管理者が確認するまで公開されません。

名前:
メールアドレス:
URL:
コメント:

トラックバック

このエントリのトラックバックURL: http://dara-j.asablo.jp/blog/2008/04/06/2987286/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。