配列や独自クラスをDBに入れておいて、それを後から使うようなこと、したくなりますよね。
そんな時に便利なのがserialize
です。(serializeとは?)
JSON
と言う手もありますが、型の情報が失われてしまうので、serializeを使うことも多くあると思います。
ですが、そのserializeした情報、DBに入れる時どうしてますか?
もしかして、text
型のカラムに直接突っ込んだりしていませんか?(私はtext
型のカラムに突っ込んでいました)
戻り値がstring
だからと言って、text
型のカラムに入れるのはNGです。
以前趣味で作っていたwebアプリではまったので、その際のポイントをかいつまんでまとめてみました。
何がいけないのか
実はtext
型のカラムにserializeされた文字列を入れると、unserializeする際に上手くいかないことがあります。
insert自体に問題はなく、そこでエラーが出たりexceptionがthrowされることもありません。
でも、その時点でDBに格納されるデータは壊れており、unserializeすることができないため復旧も不可能となります。
何故こんなことが起きるのか、ですが、簡単に言うとinsert時にデータが欠損してしまい、unserialize時に正しく元に戻せなくなってしまうことが原因でした。
insert時にデータが欠損してもinsert自体には問題がないため、DBには中途半端な状態でinsertされてしまい、気付くのはunserializeのタイミング、と言うなんとも悲しい状態の出来上がりです。
解決策
serializeメソッドではstring
が返ってくるのに、DBにはtext
で入れてはいけません。ではどうしたらいいのか。
実は答えは簡単で、PHPのマニュアルに記載があります。
これはバイナリ文字列であり、null バイトを含む可能性もあることに注意しましょう。 保存したり利用したりするときも、null バイトが含まれることを想定しておかないといけません。 たとえば、serialize() の出力をデータベースに格納するときには、 通常は CHAR 型や TEXT 型ではなく BLOB 型を使わないといけません。
上記の通り、serializeメソッドの戻り値はバイナリ文字列
であるため、BLOB型のカラムを用意してそこに入れる必要があります。(参考:BLOBとは?)
serializeメソッドの戻り値はstring
なんですが、中身はバイナリ文字列
なので、普通に文字列として扱うとデータが欠損することがあるようでした。
考えてみれば単純なことなんですが、戻り値がstring
であることに惑わされ、解決までに結構時間を費やしてしまいました。
この時はこれで解決できましたが、正直なところデータの検証をしたわけではないので、正確には別の原因があった可能性もあります。
もし別の原因をご存知の方がいらっしゃれば、是非お教えいただければと思います。
補足
実はMySQLのBLOB型にはいくつか種類があり、バイナリデータによっては格納しきれない場合があります。
参考:MySQLのBLOBについて
BLOB はさまざまな容量のデータを保持できる大きなバイナリオブジェクトです。BLOB 型は、TINYBLOB、BLOB、MEDIUMBLOB、および LONGBLOB の 4 つがあります。これらの違いは、保持できる値の最大長だけです。
保持できる値の最大長が違うだけで4種類あり、格納したいデータのサイズを見積もった上でテーブルを作る必要があります。
格納したいデータによってはカラムのサイズを超えてしまい、BLOB型でも欠損が起こる場合があるので、テーブルを作る際はデータサイズの確認を怠らないよう気をつけたいと思います。