Home > ブログ > Laravel13への更新作業

ブログ

Laravel13への更新作業

古い PHP を使った Web システムの更新作業をご依頼いただくことがちょくちょくあります。 Laravel を使ったシステムですと、PHP のバージョンアップと共に Laravel も一緒にバージョンアップしなければならないことがあります。 今年3月に Laravel13 が出てから、Laravel13 への更新事例も出てきました。

Laravel12 から Laravel13 への差分の小さいアップグレードなら、母体のシステムがそれほど大きなものでなければアップグレードもすんなりいく印象です。

Laravel13 のアップグレードガイドによるといくつか影響度の大きい変更が出ていますが、その中の一つで config/cache.phpserializable_classes という設定が増えています。

Laravel のキャッシュでは PHP のオブジェクトもキャッシュに格納できるようになっていますが、この設定はキャッシュに PHP のクラスオブジェクトをキャッシュしていた場合のアンシリアライズ(unserialize)動作に影響します。 Laravel において、何らかのデータ(PHP オブジェクト型を含む)をキャッシュに保存する場合、データはキャッシュドライバ(RedisStore, MemcachedStore等)によってシリアライズされて保存されます。 逆にキャッシュから値を取得する際、アンシリアライズされデータが復元されます。 serializable_classes は PHP オブジェクトをアンシリアライズする際、本設定で指定した型のオブジェクトのみアンシリアライズするというものです。

未許可のクラスオブジェクトをアンシリアライズするとどうなるか?

serializable_classes で指定していない型のクラスをキャッシュに格納していた場合はどうなるのでしょうか?

以下のようなキャッシュ処理があるとします。これはキャッシュ(Key は 'foo')から Member オブジェクトの取得を試みて、キャッシュになければ新規にキャッシュに保存しています。

キャッシュ処理の例

class Member
{
    public $name = 'foo';
}

function test()
{
    $value = Cache::remember('foo', 60, function () {
        return new Member();
    });

    print_r($value);
}

serializable_classesnull に設定されていた場合、あらゆる型の PHP オブジェクトのアンシリアライズが許可されます。この場合、キャッシュに格納されていた Member オブジェクトは以下のように正常に復元されます。

App\Console\Commands\Member Object
(
    [name] => foo
)

一方、serializable_classesfalse に設定されていた場合、あらゆる型の PHP オブジェクトのアンシリアライズが拒否されます。結果としては、Member オブジェクトが __PHP_Incomplete_Class オブジェクトに変換されて返されます。

__PHP_Incomplete_Class Object
(
    [__PHP_Incomplete_Class_Name] => App\Console\Commands\Member
    [name] => foo
)

実装の話になりますが、Laravel においてキャッシュの処理はキャッシュ種別(Redis, Memcached, etc.)に応じたキャッシュドライバで処理されます。キャッシュドライバには以下のようなものがあります。

  • Illuminate\Cache\RedisStore
  • Illuminate\Cache\MemcachedStore

キャッシュドライバの実装によりますが、serializable_classes の機能は、各キャッシュドライバがアンシリアライズを PHP の unserialize() 関数で処理することを前提とした機能です。 例えば Redis のキャッシュドライバである Illuminate\Cache\RedisStore も unserialize() 関数を使ってキャッシュ済みデータをアンシリアライズしています。

一方、PHP の unserialize() 関数には allowed_classes オプションといものがあり、このオプションでアンシリアライズ対象のクラスを制限できるようになっています。受付対象外のクラスをアンシリアライズしようとした場合は、__PHP_Incomplete_Class に変換されます。 serializable_classes で指定した config 値はキャッシュドライバを経由して最終的に unserialize() の allowed_classes オプションに渡されるので、Laravel にキャッシュにおいても__PHP_Incomplete_Class が返されるというわけです。

このように、既存のコードで PHP オブジェクトをキャッシュに保存する処理があった場合、serializable_classes の設定によっては、キャッシュ取得時の型が変わってしまうため注意が必要です。

どう修正すべきか

この変更を取り込む前に事前にシステムの動作を確認しておく必要があります。 とれる方法は以下の3つになります。

(1) 未定義とする

cache.php は修正せず serializable_classes は未定義のままとする。もしくはnullを設定する。

この場合は、unserialize()関数のallowed_classesオプションは指定されないので、Laravel12までと同じ動作となります。

(2) 明示的にクラスを指定する(false指定含む)

キャッシュしているオブジェクトの型をすべて列挙する。 falseを指定した場合は、すべてのPHPオブジェクトのアンシリアライズを許さない。 PHP オブジェクトはすべて __PHP_Incomplete_Class に変換される。

キャッシュにどのようなデータを格納しているのかを完全に把握できているのなら、この設定がベストになります。

(3) trueを指定する

すべてのPHPオブジェクトをアンシリアライズ可能にする。

これは(1)と同じ動作になります。

新規に開発するなら厳密に指定するのがいいのですが、 旧バージョンからのバージョンアップで現状のキャッシュ内容に確証が持てないのなら、とりあえずは(1),(3)とするのがいいでしょう。

Laravel13 のデフォルトの config/cache.php から serializable_classes の設定をコピペして false 設定のままでオブジェクトをキャッシュしているとキャッシュからの取得時に型が化けるため、テスト時に「何かおかしい。。。」ということになりますので注意が必要です。

Memcachedを使ったキャッシュでは無視される

余談ですが、キャッシュデータのアンシリアライズは各キャッシュドライバで行っているといいました。Memcached のキャッシュドライバである Illuminate\Cache\MemcachedStore ではアンシリアライズに unserialize() を使用していないため serializable_classes の設定は無視されます。

Memcahed のキャッシュ処理の実体は PHP のmemcached 拡張で処理されており(MemcachedStore は memcached 拡張に転送しているだけ)、アンシリアライズ処理も memcached 拡張内で処理され Laravel が介入できないためです。

元々なんのための機能なのか

serializable_classes 設定は元々なんのために追加された機能なのでしょうか?

gitのlog(commit bd6972d0)には以下のようにあります。

support a serializable classes value on caches

キャッシュに PHP オブジェクトをキャッシュできるようにしたということのようです。 厳密には今までにも PHP オブジェクトをキャッシュすることは可能でしたが、 allowed_classes オプションを指定できるようにして少しでも Gadget Chain 攻撃のリスクを小さくできる形で明確にサポートしたということでしょう。

ただし、serializable_classes による制限設定ができたとしても、PHP オブジェクトを unserialize する場合は「信用できるデータ以外はアンシリアライズしない」という原則は守る必要があります。PHP オブジェクトをキャッシュする際は、それが適切に構築されたオブジェクトであるかに注意を払うようにしましょう。 unserialize() 関数のリファレンスにも以下の記述があります。

警告

options の allowed_classes の値にかかわらず、 ユーザーからの入力をそのまま unserialize() に渡してはいけません。 アンシリアライズの時には、オブジェクトのインスタンス生成やオートローディングなどで コードが実行されることがあり、悪意のあるユーザーがこれを悪用するかもしれないからです。 シリアル化したデータをユーザーに渡す必要がある場合は、安全で標準的なデータ交換フォーマットである JSON などを使うようにしましょう。 json_decode() および json_encode() を利用します。

投稿日:2026/07/28 00:20

タグ: laravel

Top

アーカイブ