👋

Laravelのキューイングで陥りやすい罠:シリアライズ問題を理解する

に公開

Laravelのキューイングで陥りやすい罠:シリアライズ問題を理解する

はじめに

Laravelのキューイング機能は非同期処理を実装する上で非常に便利な機能です。しかし、その内部で行われているシリアライズ/デシリアライズの処理について理解していないと、思わぬ罠に陥る可能性があります。

この記事では、Laravelのキューイングにおけるシリアライズの仕組みと、よくある問題パターン、そして適切な実装方法について解説します。

結論 : ジョブのインスタンスに含めるものは、変数(プリミティブ・配列・モデルID等)に留めるのがベター

キューを保存する際、PHPのserialize()関数が使われているため、保存できる型がPHPの仕様により制限されます。
一部のオブジェクトやリソース型(ファイルポインタやDBコネクション)はserialize()が使えません。

キューイングの基本的な仕組み

まず、Laravelのキューイングの基本的な仕組みを理解しましょう。

  1. ジョブクラスを作成(php artisan make:job ExampleJob
  2. ジョブをディスパッチ(ExampleJob::dispatch()
  3. キューワーカーがジョブを処理(php artisan queue:work

これだけ見ると単純に見えますが、内部では以下のような処理が行われています:

  1. ジョブオブジェクトがシリアライズされてキューストレージ(DBやRedisなど)に保存
  2. ワーカーがキューからジョブを取り出し、デシリアライズして実行

シリアライズとデシリアライズとは?

シリアライズ(Serialization)

シリアライズとは、PHPのオブジェクトをストレージに保存可能な形式(文字列)に変換することです。

// オブジェクトをシリアライズする例
$user = new User(['name' => '山田太郎']);
$serialized = serialize($user);  // オブジェクトが文字列に変換される

デシリアライズ(Deserialization)

デシリアライズとは、シリアライズされた文字列を元のオブジェクトの状態に戻すことです。

// シリアライズされた文字列からオブジェクトを復元
$user = unserialize($serialized);  // 文字列から元のオブジェクトが復元される

今回問題になったパターン

1. サービスクラスをコンストラクタでインジェクションする

❌ 問題のあるコード:

class SendMailMagazineJob implements ShouldQueue
{
    private $mailService;
    private $data;

    public function __construct($data, MailMagazineService $mailService)
    {
        $this->data = $data;
        $this->mailService = $mailService;  // ここが問題
    }

    public function handle()
    {
        $this->mailService->send($this->data);
    }
}

このコードには以下の問題があります:

  1. MailMagazineServiceがシリアライズされようとする
  2. サービスクラスは通常、DB接続やその他のリソースを持っているためシリアライズできない
  3. 仮にシリアライズできても、デシリアライズ時に接続が無効な状態で復元される

2. 正しい実装方法

✅ 推奨される実装:

class SendMailMagazineJob implements ShouldQueue
{
    private $data;

    public function __construct($data)
    {
        $this->data = $data;  // シリアライズ可能なデータのみを保持
    }

    public function handle(MailMagazineService $mailService)
    {
        // handle()メソッドの引数でサービスを注入
        $mailService->send($this->data);
    }
}

この実装のポイント:

  1. コンストラクタではプリミティブ値やシリアライズ可能なデータのみを受け取る
  2. サービスクラスはhandle()メソッドの引数で注入する
  3. Laravelのサービスコンテナがhandle()実行時に新しいインスタンスを提供

シリアライズ可能なデータとは?

ジョブクラスのプロパティとして保持できるデータ:

✅ 安全なデータ型:

  • 文字列(string)
  • 数値(integer, float)
  • 配列(array)
  • シリアライズ可能なオブジェクト(Serializableを実装)
  • null

❌ 避けるべきデータ型:

  • リソース型(DB接続など)
  • クロージャ(無名関数)
  • 複雑な依存を持つサービスクラス
  • ストリーム

ベストプラクティス

  1. 必要最小限のデータのみを保持
// ❌ 避ける
public function __construct($data, $service, $logger, $cache)

// ✅ 推奨
public function __construct($userId, $emailData)
  1. サービスは常にhandle()で注入
public function handle(
    UserService $userService,
    EmailService $emailService,
    Logger $logger
) {
    // ここでサービスを使用
}
  1. シリアライズできないデータの扱い
class ImportJob implements ShouldQueue
{
    private $filePath;  // ファイルパスのみを保持

    public function __construct(string $filePath)
    {
        $this->filePath = $filePath;
    }

    public function handle(FileService $fileService)
    {
        $file = $fileService->open($this->filePath);  // handle()内で再取得
        // 処理続行...
    }
}

まとめ

Laravelのキューイングを使用する際は、以下の点を意識しましょう:

  1. ジョブクラスのプロパティには、シリアライズ可能なデータのみを保持する
  2. サービスクラスなどの依存は、handle()メソッドの引数で注入する
  3. シリアライズできないデータは、識別子(ID、パスなど)のみを保持し、実行時に再取得する

参考リンク

Discussion