🐈

<PHP>謎の下位互換性のない変更点について調べてみた

2023/04/18に公開

*諸事情により再投稿です
案件でPHPのバージョンアップに際しての影響調査をやっており、その中で意味がよく分からない変更があったのでそれについて調べてみました。内容は以下。

シリアル化
シリアル化フォーマット o は削除されました。 PHP によって生成されなくなるので、手動で組まれた文字列のシリアル化を復元する場合のみ、壊れる場合があります。

https://www.php.net/manual/ja/migration74.incompatible.php

ドキュメントのserializeとunserializeのページを読んでもピンとくる記述はなし。
php シリアル化 フォーマット oでググっても日本語情報はゼロ。

英語なら何か出てくるかとThe o serialization format has been removedで検索すると
StackOverflowに質問がありました。

https://stackoverflow.com/questions/65289729/what-was-phps-o-serialization-format-for

こちらに対する回答が完全なる「答え」でした。
私見を交えつつざっくりまとめます。
まず前提としてオブジェクトに対してシリアライズを実行すると以下のような形になります

Class Hoge {}

$hoge = new Hoge();
var_dump(serialize($hoge));
//string(15) "O:4:"Hoge":0:{}"

左端が大文字のOですね。しかし、PHP3の頃は左端の文字が小文字のoだったようです。
PHP4系以降のオブジェクトに対するシリアライズでは、上述の通り左端を大文字のOとするように変更されたのですが、PHP3との互換性を残すために小文字のoのシリアライズでもアンシリアライズは可能としたようです。

//PHP4の場合
var_dump(unserialize('o:0:{}'));
/*
object(stdClass)(0) {
}
*/

最初に提示したのはこの仕様を廃する変更のようです。

ところが、実はこの動きは15年以上前から既に壊れており、現にPHP5.3で上述のコードを実行するとエラーが発生します。

//PHP5.3の場合
var_dump(unserialize('o:0:{}'));
/*
Noticeエラーが表示されます
Notice: unserialize(): Error at offset 0 of 6 bytes in /home/user/scripts/code.php on line 2
bool(false)
*/

PHP3との互換性を気にする人が誰もいなかったということでしょう。
動く想定のコードが動かず、それに10数年以上誰も気づかないなんてことあるんですね。

以上、正直StackOverflowの回答を直接Deeplに突っ込めば十分な所ではあるのですが、日本語情報がなく、また事の顛末が興味深かったのでまとめてみた次第です。

ここが一番重要です

さて、最も重要なことを書きそびれていました。一番上に貼ったドキュメントのページを開いて一番下までスクロールしてください。和訳されていないUser Contributed Notesというブロックがありますね。これはユーザーからの知見共有の文章にあたります。こちらをDeeplに突っ込んでみましょう

Re: "oシリアライズフォーマットは削除されました。これは PHP で生成されることがないため、手動で細工した文字列のシリアライズ解除を壊すだけかもしれません。"
このlittle-oシリアライズ形式はPHP3で使用されていましたが、PH PHP4以上では生成されることはありませんでした。 しかし、PHP3との後方互換性のために、デシリアライズコードはまだこれを認識していました。
しかし、ちょっと調べてみると、このコードは15年ほど前から壊れていたようで、非推奨と記載されていますが、実際にはそうではありませんでした。
Stack Overflow のこの質問には、この件に関してもっと詳しく書かれた、とても素晴らしい回答があります: https://stackoverflow.com/questions/65289729/what-was-phps-o-serialization-format-for

上述したStackOverflowの質問にたどり着くまで1時間ほどかかってしまったのですが、答えへ至る道筋は最初から示されていました。ドキュメントは英語の所もちゃんと読まなきゃダメですね。

Discussion