🐈

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

2023/04/18に公開

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

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

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

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

ただ、上述したドキュメントページの下の方に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ページの内容をざっくりまとめます。
まず前提としてオブジェクトに対してシリアライズを実行すると以下のような形になります

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数年以上誰も気づかないなんてことあるんですね。

以上、上述している通りドキュメントを下までちゃんと見れば容易に答えまで辿り着ける内容でしたが、日本語情報がなく、また内容が興味深かったのでまとめてみた次第です。

Discussion