php5 + JSON-C では JSON_BIGINT_AS_STRING が効かない
phpには、JSONを扱うためのjson拡張が同梱されている。(ext-json(JSON_parser))
しかしこのソース中の以下の一文が問題で、各種Linuxディストリで配布できないことになっている。
The Software shall be used for Good, not Evil.
Bug #63520 JSON extension includes a problematic license statement
よって、パッケージでインストールする場合、json拡張はphp同梱のではなく、別のものが使われている。
たとえば Remi の場合は たぶん pecl-jsonc
PHPコードからのインタフェイスはそのままに、実装の下位ライブラリだけ別物というイメージ。
しかし実装が違うため、挙動が微妙に違う。
開発環境がソースからビルドのPHP、STG/本番環境が Remi のPHP というプロジェクトで問題が起きたのでメモ。
JSON-C では JSON_BIGINT_AS_STRING が効かない
私が使っていて問題になったのは、 json_decode() で JSON_BIGINT_AS_STRING が効かないというもの。
JSON含まれる大きな数値が、PHP_INT_MAX を超えている場合に文字列として扱うオプション。
JSONの相互運用性を考慮して、基本的には常に付けるべきものだろう。
これが JSON-C だと、この定数は存在するものの指定しても効果がなく、
Noticeを吐きつつ PHP_INT_MAX になる模様。(それ以上の細かな挙動は調べていない)
php-5.5.27(ソースからビルド) + 同梱の ext-json(JSON_parser) の例
PHP_INT_MAX を超えた場合は string となっている。期待通り。
/usr/local/php-5.5.27/bin/php -v PHP 5.5.27 (cli) (built: Aug 24 2015 13:58:57) Copyright (c) 1997-2015 The PHP Group Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies with Xdebug v2.2.5, Copyright (c) 2002-2014, by Derick Rethans $ /usr/local/php-5.5.27/bin/php -i | grep -i json json json support => enabled json version => 1.2.1 ### PHP_INT_MAX -1 $ /usr/local/php-5.5.27/bin/php -r 'var_dump(json_decode("{\"num\":9223372036854775806}", true, 512, 2));' array(1) { 'num' => int(9223372036854775806) } ### PHP_INT_MAX $ /usr/local/php-5.5.27/bin/php -r 'var_dump(json_decode("{\"num\":9223372036854775807}", true, 512, 2));' array(1) { 'num' => int(9223372036854775807) } ### PHP_INT_MAX +1 $ /usr/local/php-5.5.27/bin/php -r 'var_dump(json_decode("{\"num\":9223372036854775808}", true, 512, 2));' array(1) { 'num' => string(19) "9223372036854775808" }
php-5.5.27(remi) + php-pecl-jsonc-devel-1.3.5-1.el6.remi.5.5.x86_64 の例
PHP_INT_MAX を超えた場合は PHP_INT_MAX に 切り詰め されている。
Notice は >= PHP_INT_MAX
から発生。
プロジェクト全体で E_NOTICE を無視するようなコーディングをしていると、
値を間違って解釈してしまったことに気づきすらしない。
各ディストリが勝手にやってることとはいえ、phpのマニュアル通りに動作しなくなるのはどうなのか。
$ php -v PHP 5.5.27 (cli) (built: Jul 13 2015 07:24:17) Copyright (c) 1997-2015 The PHP Group Zend Engine v2.5.0, Copyright (c) 1998-2015 Zend Technologies with Xdebug v2.2.5, Copyright (c) 2002-2014, by Derick Rethans $ php -i | grep -i json /etc/php.d/json.ini, json json support => enabled json version => 1.3.5 JSON-C version (bundled) => 0.11 ### PHP_INT_MAX -1 $ php -r 'var_dump(json_decode("{\"num\":9223372036854775806}", true, 512, 2));' array(1) { 'num' => int(9223372036854775806) } ### PHP_INT_MAX $ php -r 'var_dump(json_decode("{\"num\":9223372036854775807}", true, 512, 2));' PHP Notice: json_decode(): integer overflow detected in Command line code on line 1 PHP Stack trace: PHP 1. {main}() Command line code:0 PHP 2. json_decode() Command line code:1 array(1) { 'num' => int(9223372036854775807) } ### PHP_INT_MAX +1 $ php -r 'var_dump(json_decode("{\"num\":9223372036854775808}", true, 512, 2));' PHP Notice: json_decode(): integer overflow detected in Command line code on line 1 PHP Stack trace: PHP 1. {main}() Command line code:0 PHP 2. json_decode() Command line code:1 array(1) { 'num' => int(9223372036854775807) }
まとめ
php-5.5.27 ext-json | php-5.5.27 pecl-jsonc | php-7.1.4 ext-json | |
---|---|---|---|
PHP_INT_MAX - 1 | ○数値 | ○数値 | ○数値 |
PHP_INT_MAX | ○数値 | △数値, Warning | ○数値 |
PHP_INT_MAX + 1 | ○文字列 | ×数値, Warning, PHP_INT_MAXに切り詰め | ○文字列 |
JSONの数値型とプログラム言語のキャストはPHPに限った話ではないので、
大きな値になりうる場合は文字列型でやり取りした方がいい。
今回の例には含まれていないが浮動小数も同様。
JSON-C が使われているかどうかの判断自体は、JSON_C_BUNDLED
の存在有無でできそう。
でもプログラム中で分岐させるのではなく、文字列でやり取りした方がいい。
<?php if(defined('JSON_C_BUNDLED')) { // JSON-C } else { // おそらくphp同梱の JSON_parser }
php7 ではまた別の実装になっている模様。
PHPのJSONライセンス問題が一応決着
試してないが、php同梱なのでおそらく JSON_BIGINT_AS_STRING は 効くだろう 効いた。