gallu’s blog

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

大きなIDをどうやってPHPからMySQLに渡す?:発端とまとめ

あるタイミングで、プリペアドステートメント回りのお話と、IDのカラム(によって決まる最大値)のお話と、intサイズのお話が別々に来た時に……頭ん中で混ざって、「あれ?」と思った事があったので調べてみた、って感じになります。


まず話に出ていたのが「AUTO_INCREMENTのカラムの型」のお話。
もうちょっと突っ込むと「intだと足りなくなる瞬間が以下略」。2147483647、ようは21億ちょい、なので、動かし方によっては十分に手が届いちゃう範囲。
int unsignedにしても4294967295だし、そもそもAUTO_INCREMENTなんで負の値いらないからまぁ「bigint unsignedだよねぇ」ってお話があって、これが素材その1。


bitintは8バイトなので、unsignedだと18446744073709551615とかいう、クソ怪しい値までOK……なんだけど。
PHPって、64bit環境の場合、あの子「signed intまでしか整数扱えない(以降は浮動小数点で扱う)」なので………あれ? 9223372036854775807 まではいいんだが、9223372036854775808以降は?
これが素材2。


PDOのプリペアドステートメントは「第二引数の変数の型と第三引数の定数の両方がINT」以外はstring扱いだよねぇ、って話が、全然別の流れで普通に出てきて。
………あれ? 「PHP的にintで扱えない値はどうなるの?」って疑問がむくむくと湧いてきて。
これが素材3。


というわけで、素材1〜3までを合成………するまえに。
とりあえず、最低限のチェックから。

<?php

// PHP_INT_MAXの「次の数」の確認
$i = PHP_INT_MAX;
$i += 1;
echo "---\n";
var_dump($i);
printf("%f\n", $i);
echo "---\n";
var_dump((int)$i);
var_dump(intval($i));
echo "---\n";
printf("%d\n", (int)$i);
printf("%d\n", intval($i));
echo "---\n";


// 少し雑に「大きな数」の確認(INT_MAXの先頭に1追加した数値)
$i = 19223372036854775807;
echo "---\n";
var_dump($i);
printf("%f\n", $i);
echo "---\n";
var_dump((int)$i);
var_dump(intval($i));
echo "---\n";
printf("%d\n", (int)$i);
printf("%d\n", intval($i));
echo "---\n";

---
float(9.2233720368548E+18)
9223372036854775808.000000
---
int(-9223372036854775808)
int(-9223372036854775808)
---
-9223372036854775808
-9223372036854775808
---
---
float(1.9223372036855E+19)
19223372036854775808.000000
---
int(776627963145224192)
int(776627963145224192)
---
776627963145224192
776627963145224192
---

うわぁい(苦笑
intでキャストもintval関数も、「負の値に行ったり(これはまぁわかる)」、よくわからん数値になってきたり(多分単純にビットあふれが切り捨てられてる)。
まぁ、マニュアルにも書いてあるしなぁ


http://php.net/manual/ja/function.intval.php

最大値はシステムに依存します。32 ビットシステムでは、 最大の符号付き整数の範囲 -2147483648 〜 2147483647 となります。 このため、そのようなシステムでは intval('1000000000000') は 2147483647 を返します。 64 ビットシステムにおける最大の符号付き整数は 9223372036854775807 となります。


なので、大きなIDを
・受け取って
・INTでキャストして
プレースホルダにバインド
すると、多分間違いなくなんとなくまずもっておそらく「NGであろうなぁ」と思われるに至り、これは「実験せにゃなぁ」と思ったわけでございます。


で…細かい実験は長いんで、後で書きますが。
ものすごく端的に要約すると、現時点のおいちゃん見解としては、大体以下の通りかなぁ、と。
・(IDなんで演算とかしない前提で)文字列で受け取り、文字列で渡すようにする
・WHERE句のIDに「明示的にCASTするかどうか」は、お好みで。可能性として「CASTしておいたほうが効率が良い、かも、しれない」のと「何となくせめて明示したい」w
・IDはvalidateする。ctype_digit()関数がよいと思う
・「文字列から数値への、WHERE句での使用時の暗黙の(またはCASTによる)変換」の挙動が変わらないように、祈るw


いやまぁ「例えば、bigint unsignedを避ける(bigint signedにする)」+「PHPは64bit環境」って選択肢もないわけではないのですがw
まぁ少ないとは思うのですが、サービスの余命考えた時に「どっちかねぇ?」って感じになると思うのです。


いやまぁ実際、現時点のおいちゃん見解も「ど〜かねぇ?」とは思うのですが。暗黙の変換の、しかも「明記されているわけではない」挙動に頼る、ってのも(CAST使えば"暗黙"ではないですが一応)。
ただ、現状、それ以外に今一つ「よいアイデア」が浮かばないんですよねぇ……というわけで、現状における「苦肉の策」だと思っていただけると。と。
なので。「普通の整数を扱う」ときはちゃんとINTで扱って、ただ「INT_MAXを超える可能性があり」かつ「算術演算が発生しない」AUTO_INCREMENTなIDについて」のみ、例外的に、上述のような方法を取らざるを得ないのかなぁ、という。


もうちょっと妙手があればなぁ、と思うので、コメントなどありましたら、お気軽によろしくお願いいたします!!