この記事を三行にまとめると
僕もよく使ってますこのエラーにはほとほと困り果てた
声:レニーハート
photoshopのスポット修復ブラシツールっての使うと、良い感じに文字を消すことができるんですねー。そんな機能、初めて知りました。
上の画像の「serialize」と「json_encode」って文字の部分、本当は「エイリアン」と「プレデター」って書いてあったんですけど、何とかこの文字だけキレイに消すことできないかなーと思っていたら、そのスポット修復ブラシツールってやつを使うと良いよっていう情報を入手しまして……試してみた結果がこれです。あまりキレイには消せませんでしたけど、まあいいでしょう。
しかし、この画像を加工してブログに貼っつけんの、良いですね。勝手に使って怒られなければの話ですけど。怒られたら謝ろう。今から準備しとくわ。
僕はphotoshopって全っ然使えないんですよ。でも使いこなせるようになりたいとは、前から思ってたんですよね。ただ、その勉強の場がなかなかなかったので……今後はここを有効に使っていこう。少しずつやり方覚えて行くっす。
あー、すみません。いきなり関係ない話になってしまいましたね。
反省はしていない。
といっても、難しいことは何もない。PHPにはserializeっていう関数があるので、それを使えば階層の深い配列だろうと、一発で保存可能な形に変換してくれる。変換されたデータを配列に戻すには、unserializeってのを使う。
実際、変換してみるとこんな風になる。
僕もよく使ってます。データベースの設計をしていて、困ったらserializeしとけば良いやってくらい使ってる。マヨネーズ感覚で使ってる。
(この記事を書いている人は極度のマヨラーのため、困ったらとりあえずマヨネーズぶっかけとけば何でも美味しく食べられると思っています)
マヨネーズはすごいよ何でも合うよ
コレステロールとコレステロールの夢のコラボレーション
Notice: unserialize() [function.unserialize]: Error at offset・・・
DBから取得したデータのunserializeに失敗して、結果がfalseになってしまうという、そんなエラーのようです。
ぶっちゃけ、このエラーにはほとほと困り果てた。
何が困っちゃうって、このエラー、どうやって解消したら良いのかよく分からねーんですよ。
エスケープしなきゃいけない文字(ダブルコーテーションとか)をエスケープしてないせいとか、その辺りに原因があるんじゃないかなーと最初は思いました。でも、どうやらそういう感じでもないらしい。
何でかってーと、エラーが出たデータと全く同じ文字(←ここ重要)を入れた配列を再度serializeしてDBに登録して、それを取得してunserializeすると、エラーが出なくなるんですよ。
なしてさ?
自分なりにいろいろ試してはみたつもりなんですけど、同じ配列データでもエラーが出るときと出ないときがあるんで、簡単にエラーの再現が行なえないこともあって、結局どうしたら良いのかよく分かりませんでした。お手上げ侍です。
誰か良い情報持ってたら教えてほしいっす。
それがjson_encodeです。
これは、配列などのデータをJSON形式に変換してくれる関数です。
JSONっていうのは、あー……javascriptにおけるオブジェクトの表記法を基にしたデータ形式ってことらしいです。javascriptでobject型のデータを書く場合と同じ感じの文字列っていう認識で良いんですかね。
配列をjson_encodeしてみると、こうなる。
「u3042」とか「u3044」とかは、マルチバイト文字をJSON形式でエスケープするとこうなるっぽいです。基本、json_encodeを使うと、自動でエスケープしてくれます。PHP5.4以降であれば、エスケープを行なわないようにすることもできるみたい。
この結果($str)も文字列なので、DBに保存することができます。配列に戻すにはjson_decodeを使えば良い。流れはserializeと一緒です。
このjson_encodeを使うことで、unserialize時の解決できなかったエラーは、とりあえず回避することができる。
僕も今回、仕事で開発しているウェブサービスで、serializeを使っている部分を一旦全部json_encodeを使うように修正して、難を逃れておきました。
unserializeが効かないせいで、シリアライズされてるデータを自分でせっせと配列に直さなきゃいけないっていうアナログ作業がだいぶめんどっちかったですけど、ひとまず、めでたしめでたし?
と、いうことで、こっからが本番だ。ここまで、対決ムードではなかったからね。言ってみりゃ、選手紹介みたいなもんよ。
赤コーナー……シルリァァァァァルァイィィィィィズゥゥゥゥゥ!!
青コーナー……ジェイソンンンンンヌエンコォォォォォドゥォォォォォ!!
(声:レニーハート)
実際に簡単なプログラムを書いて、バイト数や処理にかかる時間を見てみましょう。
こんな感じですかね。今回はDBに登録されているデータを取得して配列に戻すまでの時間を計測するってことで、serializeやjson_encodeの処理時間は含めないようにしてます。unserializeとjson_decodeの処理時間のみね。
僕がやってみた結果は、以下のようになりました。
バイト数(データ量)はjson_encodeの方が少ないですね。でも処理時間はjson_encodeの方が3倍くらいかかってます。
まあ、serializeの場合は配列の要素数とかキーや値の型、バイト数も文字列の中に含まれてますから、その分データ量が多くなってしまうんでしょうね。serializeした文字列の中に、「a:3」とか「s:15」とかあったじゃん? あれがそう。「a:3」は配列の要素数が3個って意味で「s:15」はデータの型がstring型で、データ量が15バイトってことを意味しているらしい。一方のjson_encodeされた文字列には、そういう情報ないからね。
ってことで、処理時間を重視するならserialize、データ量を重視するならjson_encodeを使えば良い……とは、一概には言えないんだねぇ、これが。
今は、文字が全部アルファベット、つまり1バイト文字のみの場合の話。マルチバイト文字が入って来ると、また違って来ます。
json_encodeでマルチバイト文字をエスケープしたときって、「u3042」みたいに、一文字が5バイトになるのよ。通常、UTF-8のマルチバイト文字は3バイトだから、マルチバイト文字を多く含むデータを変換した場合は、json_encodeの方がデータ量が多くなる。
例えば……入力するデータを以下のように変えてみよう。
これで再度serializeとjson_encodeをやって、処理時間やデータ量を見てみる。
json_encodeの方がデータ量が多くなったね。処理時間も、serializeの方は変わらなかったのに、json_encodeの方はさらに3倍くらいになってる。エスケープされた文字を元に戻してるからかな?
まあ、json_encodeのときに、マルチバイト文字をエスケープしないようにすれば、やっぱりjson_encodeの方がデータ量は少なくなるけれども……こういうのって、エスケープしといた方が安全そうじゃん?
ってか、僕が仕事で使ってるサーバー、PHPのバージョンが5.3だしな……さっきも言ったけど、5.4じゃないとjson_encode時のエスケープキャンセルは行なえない。
さあ、そんなわけで、対決が終了しました。
今回の結果を見た感じでは、軍配はserializeの方に上がりそうなんだけど……僕みたいに、serializeだと配列に戻せないエラーが出るからjson_encodeを使うっていう場合もあるだろうし、どっちを使うべきだってのは、勝敗とは別問題ってことですね。これでjson_encodeの完全勝利なら、「serializeよりもjson_encodeを使え!」って、僕も声を大にして叫ぶとこなんですが。
serializeだとエラーが出るようになったのも、専用サーバーに鞍替えしてからだから、僕がやったサーバーの設定にどっかミスがあるせいだと思うのよね。だからserialize自体に脆弱性があるとかではないはず。でも確かめてないから自信はない。めんご。
ちなみに、エイリアンとプレデターは、映画を見たことないんだけど、どっちが勝つん?
上の画像の「serialize」と「json_encode」って文字の部分、本当は「エイリアン」と「プレデター」って書いてあったんですけど、何とかこの文字だけキレイに消すことできないかなーと思っていたら、そのスポット修復ブラシツールってやつを使うと良いよっていう情報を入手しまして……試してみた結果がこれです。あまりキレイには消せませんでしたけど、まあいいでしょう。
しかし、この画像を加工してブログに貼っつけんの、良いですね。勝手に使って怒られなければの話ですけど。怒られたら謝ろう。今から準備しとくわ。
僕はphotoshopって全っ然使えないんですよ。でも使いこなせるようになりたいとは、前から思ってたんですよね。ただ、その勉強の場がなかなかなかったので……今後はここを有効に使っていこう。少しずつやり方覚えて行くっす。
あー、すみません。いきなり関係ない話になってしまいましたね。
反省はしていない。
こっからが本題
PHPでコードを書いていると、配列をしょっちゅう使うことになると思うんですが、でも配列のデータをそのままの形でDBに登録することはできない。何かしらの方法でデータを加工して登録することになります。といっても、難しいことは何もない。PHPにはserializeっていう関数があるので、それを使えば階層の深い配列だろうと、一発で保存可能な形に変換してくれる。変換されたデータを配列に戻すには、unserializeってのを使う。
実際、変換してみるとこんな風になる。
$data = array('あいうえお', 'かきくけこ', array('さしすせそ', 'たちつてと'));
$str = serialize($data);
//$strの中身
a:3:{i:0;s:15:"あいうえお";i:1;s:15:"かきくけこ";i:2;a:2:{i:0;s:15:"さしすせそ";i:1;s:15:"たちつてと";}}
僕もよく使ってます。データベースの設計をしていて、困ったらserializeしとけば良いやってくらい使ってる。マヨネーズ感覚で使ってる。
(この記事を書いている人は極度のマヨラーのため、困ったらとりあえずマヨネーズぶっかけとけば何でも美味しく食べられると思っています)
マヨネーズはすごいよ何でも合うよ
コレステロールとコレステロールの夢のコラボレーション
と・こ・ろ・が!
今までは特に何の問題もなく使えていたんですけど、最近、たま〜にこんなエラーが出るようになりました。Notice: unserialize() [function.unserialize]: Error at offset・・・
DBから取得したデータのunserializeに失敗して、結果がfalseになってしまうという、そんなエラーのようです。
ぶっちゃけ、このエラーにはほとほと困り果てた。
何が困っちゃうって、このエラー、どうやって解消したら良いのかよく分からねーんですよ。
エスケープしなきゃいけない文字(ダブルコーテーションとか)をエスケープしてないせいとか、その辺りに原因があるんじゃないかなーと最初は思いました。でも、どうやらそういう感じでもないらしい。
何でかってーと、エラーが出たデータと全く同じ文字(←ここ重要)を入れた配列を再度serializeしてDBに登録して、それを取得してunserializeすると、エラーが出なくなるんですよ。
なしてさ?
自分なりにいろいろ試してはみたつもりなんですけど、同じ配列データでもエラーが出るときと出ないときがあるんで、簡単にエラーの再現が行なえないこともあって、結局どうしたら良いのかよく分かりませんでした。お手上げ侍です。
誰か良い情報持ってたら教えてほしいっす。
json_encodeの出番
いつどんな状態で上記のエラーが出るか分からない以上、このままserializeを使い続けても大丈夫っていう安心感もないわけで、ひとまず、別の方法で配列を保存できる形に変換することにしました。それがjson_encodeです。
これは、配列などのデータをJSON形式に変換してくれる関数です。
JSONっていうのは、あー……javascriptにおけるオブジェクトの表記法を基にしたデータ形式ってことらしいです。javascriptでobject型のデータを書く場合と同じ感じの文字列っていう認識で良いんですかね。
配列をjson_encodeしてみると、こうなる。
$data = array('あいうえお', 'かきくけこ', array('さしすせそ', 'たちつてと'));
$str = json_encode($data);
//$strの中身
["\u3042\u3044\u3046\u3048\u304a","\u304b\u304d\u304f\u3051\u3053",["\u3055\u3057\u3059\u305b\u305d","\u305f\u3061\u3064\u3066\u3068"]]
「u3042」とか「u3044」とかは、マルチバイト文字をJSON形式でエスケープするとこうなるっぽいです。基本、json_encodeを使うと、自動でエスケープしてくれます。PHP5.4以降であれば、エスケープを行なわないようにすることもできるみたい。
この結果($str)も文字列なので、DBに保存することができます。配列に戻すにはjson_decodeを使えば良い。流れはserializeと一緒です。
このjson_encodeを使うことで、unserialize時の解決できなかったエラーは、とりあえず回避することができる。
僕も今回、仕事で開発しているウェブサービスで、serializeを使っている部分を一旦全部json_encodeを使うように修正して、難を逃れておきました。
unserializeが効かないせいで、シリアライズされてるデータを自分でせっせと配列に直さなきゃいけないっていうアナログ作業がだいぶめんどっちかったですけど、ひとまず、めでたしめでたし?
いざ、尋常に
エラーが出ないことに比べれば些細な問題のような気もしますが……serializeを使った場合とjson_encodeを使った場合では、変換後の文字列のバイト数とか、それを配列に戻すときの処理時間に少々差がある。なので、状況に応じて、どっちを使うのか検討する必要は、あるかもしれない。と、いうことで、こっからが本番だ。ここまで、対決ムードではなかったからね。言ってみりゃ、選手紹介みたいなもんよ。
赤コーナー……シルリァァァァァルァイィィィィィズゥゥゥゥゥ!!
青コーナー……ジェイソンンンンンヌエンコォォォォォドゥォォォォォ!!
(声:レニーハート)
実際に簡単なプログラムを書いて、バイト数や処理にかかる時間を見てみましょう。
//serializeの場合
for($i = 0; $i < 10000; $i++) {
$data[$i] = 'abcedfghijklmnopqrstuvwxyz';
}
$str = serialize($data);
$len = strlen($str);
$start = microtime(true);
$str = unserialize($str);
$end = microtime(true);
$time = $end - $start;
echo $len;
echo $time;
//json_encodeの場合
for($i = 0; $i < 10000; $i++) {
$data[$i] = 'abcedfghijklmnopqrstuvwxyz';
}
$str = json_encode($data);
$len = strlen($str);
$start = microtime(true);
$str = json_decode($str);
$end = microtime(true);
$time = $end - $start;
echo $len;
echo $time;
こんな感じですかね。今回はDBに登録されているデータを取得して配列に戻すまでの時間を計測するってことで、serializeやjson_encodeの処理時間は含めないようにしてます。unserializeとjson_decodeの処理時間のみね。
僕がやってみた結果は、以下のようになりました。
//serialize
バイト数・・・408900
処理時間・・・2ms
//json_encode
バイト数・・・290001
処理時間・・・6ms
バイト数(データ量)はjson_encodeの方が少ないですね。でも処理時間はjson_encodeの方が3倍くらいかかってます。
まあ、serializeの場合は配列の要素数とかキーや値の型、バイト数も文字列の中に含まれてますから、その分データ量が多くなってしまうんでしょうね。serializeした文字列の中に、「a:3」とか「s:15」とかあったじゃん? あれがそう。「a:3」は配列の要素数が3個って意味で「s:15」はデータの型がstring型で、データ量が15バイトってことを意味しているらしい。一方のjson_encodeされた文字列には、そういう情報ないからね。
ってことで、処理時間を重視するならserialize、データ量を重視するならjson_encodeを使えば良い……とは、一概には言えないんだねぇ、これが。
今は、文字が全部アルファベット、つまり1バイト文字のみの場合の話。マルチバイト文字が入って来ると、また違って来ます。
json_encodeでマルチバイト文字をエスケープしたときって、「u3042」みたいに、一文字が5バイトになるのよ。通常、UTF-8のマルチバイト文字は3バイトだから、マルチバイト文字を多く含むデータを変換した場合は、json_encodeの方がデータ量が多くなる。
例えば……入力するデータを以下のように変えてみよう。
for($i = 0; $i < 10000; $i++) {
//マルチバイト文字に変更してみる
//$data[$i] = 'abcedfghijklmnopqrstuvwxyz';
$data[$i] = 'あいうえおかきくけこさしすせそたちつてとなにぬねの';
}
これで再度serializeとjson_encodeをやって、処理時間やデータ量を見てみる。
//serialize
バイト数・・・898900
処理時間・・・2ms
//json_encode
バイト数・・・1530001
処理時間・・・20ms
json_encodeの方がデータ量が多くなったね。処理時間も、serializeの方は変わらなかったのに、json_encodeの方はさらに3倍くらいになってる。エスケープされた文字を元に戻してるからかな?
まあ、json_encodeのときに、マルチバイト文字をエスケープしないようにすれば、やっぱりjson_encodeの方がデータ量は少なくなるけれども……こういうのって、エスケープしといた方が安全そうじゃん?
ってか、僕が仕事で使ってるサーバー、PHPのバージョンが5.3だしな……さっきも言ったけど、5.4じゃないとjson_encode時のエスケープキャンセルは行なえない。
さあ、そんなわけで、対決が終了しました。
今回の結果を見た感じでは、軍配はserializeの方に上がりそうなんだけど……僕みたいに、serializeだと配列に戻せないエラーが出るからjson_encodeを使うっていう場合もあるだろうし、どっちを使うべきだってのは、勝敗とは別問題ってことですね。これでjson_encodeの完全勝利なら、「serializeよりもjson_encodeを使え!」って、僕も声を大にして叫ぶとこなんですが。
serializeだとエラーが出るようになったのも、専用サーバーに鞍替えしてからだから、僕がやったサーバーの設定にどっかミスがあるせいだと思うのよね。だからserialize自体に脆弱性があるとかではないはず。でも確かめてないから自信はない。めんご。
ちなみに、エイリアンとプレデターは、映画を見たことないんだけど、どっちが勝つん?
分かり易い解説をありがとうございます。
本コメントは、両関数の処理速度やエラーの観点とは少し違うのですが、気になったので書かせていただきました。
PHPのHPでserializeの項目について見てみてると、ユーザーからの入力データを渡すのは危険なので、json_encodeを使ってくださいと書いてあります。
どんなに処理速度が速い関数でも、危険性が生じるとなると、やはり遅くても安全な関数(json_encode)に切り替えるべきなのでしょうか??
また、フォームのCHECKBOXなどからPOSTで入力データを受け取って、何らかの関数でエスケープしてからserializeして、最後にMYSQLなどのDBに格納する場合、json_encodeと比べるとどちらの処理速度が早いのでしょうか?