Skip to main content

once() とは

once() はコールバックを実行し、その結果をリクエストの間メモリにキャッシュするグローバルヘルパーです。同じ呼び出し箇所から同じコールバックで再度呼ばれると、キャッシュされた結果を返します。
オブジェクトインスタンスのメソッド内から呼び出すと、キャッシュはそのインスタンス単位で独立します。
この「呼び出し箇所ごと・インスタンスごと」にキャッシュが独立する仕組みは、memoize のような単純なメモ化ヘルパーとは異なります。仕組みを理解するには Illuminate\Support\OnceIlluminate\Support\Onceable の2クラスを見る必要があります。

helpers.php での呼び出し

Illuminate\Support\helpers.phponce() 関数本体はごくシンプルです。
ポイントは、once() 自体はキャッシュを持たず、debug_backtrace() で取得した呼び出し元の情報から Onceable インスタンスを組み立てて Once クラスに処理を委譲していることです。

Onceable — 呼び出し箇所を特定するハッシュ計算

Onceable クラスは「どの呼び出し箇所か」を一意に特定するハッシュを、バックトレースから計算する役割を持ちます。
ハッシュの元になる情報は、ファイルパス + クラス名 + 関数名 + 行番号 + クロージャがuseしている変数の値 です。つまり、同じ行から呼ばれた once() でも、use している変数の値が変われば別のキャッシュ扱いになります
eval() されたコード内から once() を呼ぶと hashFromTrace()null を返し、キャッシュは一切行われません。Bladeのコンパイル済みビューなどeval経由の実行を想定した安全策です。
object は「呼び出し元がインスタンスメソッドかどうか」を示します。$trace[1]['object']debug_backtrace() の1階層上(once() を呼び出した側)のオブジェクトなので、インスタンスメソッド内なら $this が入り、静的メソッドやグローバル関数なら null になります。

Once — WeakMapによるキャッシュ本体

実際のキャッシュ保持は Illuminate\Support\Once が担います。
キーとなる技術は WeakMap です。$onceable->object(呼び出し元インスタンス)をキーにして、値としてハッシュ→結果の連想配列を保持します。object が無い場合(グローバル関数や静的メソッド)は Once 自身の $this がキーになり、事実上プロセス全体で共有される単一のキャッシュ領域になります。
WeakMap を使っているため、キーとなるオブジェクトへの参照が他になくなれば、そのオブジェクトに紐づくキャッシュもガベージコレクションの対象になります。once() を大量のオブジェクトから呼んでもメモリリークになりにくい設計です。

テストでの活用 — enable / disable / flush

Once::disable() を呼ぶとキャッシュが完全に無効化され、once() は毎回コールバックを実行するようになります。テストで「毎回新しい値が欲しい」場合に便利です。
Once::flush() はキャッシュ全体を破棄し、次回アクセス時に新しい WeakMap を作り直します。Artisanコマンドのテストや、Octaneのようにプロセスが使い回される環境でリクエストをまたいでキャッシュを持ち越したくない場合に使います。

PreventsCircularRecursion — Onceableの応用例

Onceable::tryFromTrace()once() 専用ではなく、Eloquentの Illuminate\Database\Eloquent\Concerns\PreventsCircularRecursion トレイトでも再利用されています。こちらは「同じオブジェクトの同じ呼び出し箇所を、コールスタック内で再入させない」ためのトレイトです。
Once との決定的な違いは、finally ブロックで 呼び出し完了後にキャッシュをクリアしている点です。Once は「リクエスト中は永続的にキャッシュする」のに対し、PreventsCircularRecursion は「今実行中のコールスタックの間だけキャッシュ(実質的にはロック)する」ために同じ Onceable の仕組みを流用しています。 Eloquentモデルでの典型的な使用例は、モデルの toArray() やアクセサが自分自身を再帰的に参照してしまうケースの防止です。

パッケージ開発での応用

自作パッケージで「同じインスタンスの同じメソッド呼び出しを1リクエスト中は1回だけ実行したい」場合、once() をそのまま使うのが最も簡単です。Onceable/Once を直接使う機会は少ないですが、以下のような場面では内部実装の理解が役立ちます。
  • Artisanコマンドやテストで Once::disable() / Once::flush() を使ってキャッシュ挙動を制御したいとき
  • 自作トレイトで「呼び出し箇所単位」のキャッシュや再入防止を実装したいとき(PreventsCircularRecursion と同じパターンが使えます)
  • once() の結果が想定と異なりデバッグが必要なとき、ハッシュの元になる要素(ファイル・クラス・関数・行・use変数)を理解しておくと原因を特定しやすくなります
once() はクロージャが use している変数までハッシュに含めるため、ループの中で毎回異なる値をキャプチャするクロージャを渡すと、意図せず毎回別キャッシュになり実質的にキャッシュされないことがあります。ループ内で使う場合は、キャッシュキーとして意図した粒度になっているか確認してください。

関連ページ

tap() ヘルパーと Tappable トレイト

副作用を挟みつつ値を返すtap()ヘルパーの実装パターン。

Eloquent Observers とモデルイベント

Eloquentモデルのイベントとオブザーバーによる一元管理。
最終更新日 2026年9月6日