メモ PHP

    PHPのunserializeがError at offsetエラーが起きた際の対処方法

    2021.05.04 2020.12.06

    とあるデータベースの内容をCSVに出力して欲しいという簡単な仕事がありました。目的のテーブルをみてみると以下のような記録がありました。

    a:2:{i:0;s:5:“hello”;i:1;s:5:“world”;} //サンプルです
    

    「なんじゃこれ」と調べていると、これはシリアライズされたデータでした。PHPのオブジェクトのインスタンス、配列を文字列として保存したい時にserialize()を使用し、その時に生成される文字でした。

    serialize ( mixed $value ) : string

    値の保存可能な表現を生成します。 型や構造を失わずに PHP の値を保存または渡す際に有用です。 シリアル化された文字列を PHP の値に戻すには、 unserialize() を使用してください。

    https://www.php.net/manual/ja/function.serialize.php

    元に戻す際はunserialize()を用いると元の配列やオブジェクトになります。上の例は

    array(‘hello’, ‘world’)
    

    となります。今回の記事ではそんなunserialize()の時に起きたエラーです。その時に以下のようなエラーが発生しました。

    Fatal error: Uncaught ErrorException: unserialize(): Error at offset XX of XX bytes
    

    このエラーの概要としてはunserialize()する際にs:5:“world”などの部分を頼りに値を元に戻すのですが、この部分が文字コードや色々な都合で文字列数が合わない時があります。そうすると上記のようなエラーがおきます。

    「unserialize(): error at offset」 「unserialize(): エラー」

    と調べました。似たような症状で悩んでいる人が多いためか、すぐに解決策が出ました。最終的には以下の記事が最善の解決策でした。

    $data = 's:4:"abc"';
    
    $data = preg_replace_callback('!s:(\d+):"([\s\S]*?)";!', function($m) {
      return 's:' . strlen($m[2]) . ':"' . $m[2] . '";';
    }, $data);
    
    echo $data; // => 's:3:"abc"'
    
    $text = unserialize($data);
    

    上記のコードのようにpreg_replace_callbackを用いてunserialize()できないデータにある、's:4:"abc"'などの文字列数などを正規表現を用いて再計算し、正しい文字列数にすることでエラーが解決できます。

    そもそもなぜserializeしたデータの文字列数が間違っているのかなど根本的な原因としては、PHPのバージョンの違いや変換する文字コード、保存するDBでの文字コードなど様々な要因があります。私もローカルのPHPは7.4ですが、データを保存している環境はPHP5やmysql6という古い環境で全く異なっていただったりと、エラーになる要素は満載でした。

    とにかく原因とその解決方法である正規表現は正直初見殺しなので、解説記事は非常に助かりました。ありがとうございます。

    Copyright © 2021 jun. All rights reserved.