Laravel の Collection::each() は便利な反面、
return
やbreak
が効かず副作用を招きやすい──本記事では Pest で罠を再現しながら、安全な使い方・代替メソッド・例外処理テクニックを徹底解説します。今回のPestのテストはこちらにupしてますので、確認しながら学習がおすすめ → https://github.com/wasipo/syugyou/blob/main/tests/Unit/laravel-collection-each-side-effects.php
本記事は「Laravel Collectionにおけるeach()の挙動と注意点」に特化した技術記事です。
mapやfilterとの比較や入門解説ではなく、「eachでハマりがちな罠」「安全な使い方」「Pestでの挙動確認」など、
挙動ベースの深掘り検証にフォーカスしています!!
mapなどの解説を見ながら学びたい方は👉️ map vs each完全ガイド
mapの応用をコード付きで学びたい方は👉️ Collection実践キャンプ
当たり前ですが、どっちも無料です。(キャンプってお金かかりそうだし)
each でループを途中で止めたい?実は止まりません。
Laravel Collection の each()
メソッド、foreach の代わりに使いたくなるあの手軽さ。でも待って。
実は、便利そうに見える each()
は return や break が効かず、副作用を招きがちな設計です。
本記事では Pest を使い、each()
の落とし穴を検証しながら、安全な使い方と代替手段を紹介します。
注:本記事のタイトルでCollection::each()
とありますが実際に静的呼び出しすることはできません。
- 1. each でループを途中で止めたい?実は止まりません。
- 2. each は return が効かない(実装と検証)
- 2.1. Pest テストコード(目的:ループが中断されないことの確認)
- 3. break や continue が効かない(Breakできるのか?)
- 3.1. Pest テストコード(目的:foreach なら break 可能なことの確認)
- 4. 副作用の処理には向かない
- 4.1. Pest テストコード(目的:例外が飛ぶと後続が実行されないことの確認)
- 5. try-catch で each を安全に使う
- 5.1. Pest テストコード(目的:例外を catch して処理継続することの確認)
- 6. map を使えば副作用を回避できる
- 7. Laravelの他のループ処理とは?
- 8. それぞれの使い時は?
- 9. 🎯 まとめ
- 9.1. 次のおすすめはコチラ
- 10. FAQ
each は return が効かない(実装と検証)
collect([1, 2, 3])->each(function ($item) {
if ($item === 2) {
return; // 2だけスキップ、ループは止まらない
}
echo $item;
});
上記は 1
と 3
を出力します。2
のとき return
しても ループは止まりません。
Pest テストコード(目的:ループが中断されないことの確認)
describe('Collection::each の挙動検証', function () {
it('does not break each with return', function () {
$output = [];
collect([1, 2, 3])->each(function ($item) use (&$output) {
if ($item === 2) {
return;
}
$output[] = $item;
});
// 想定出力: [1, 3]。2 はスキップされるが、ループは止まらない。
expect($output)->toBe([1, 3]);
});
});
break や continue が効かない(Breakできるのか?)
foreach ($items as $item) {
if ($item->shouldStop()) {
break;
}
}
この「途中で止まる」処理、each()
では 物理的に不可能 です。
Pest テストコード(目的:foreach なら break 可能なことの確認)
it('can break with foreach but not with each', function () {
$items = collect([1, 2, 3, 4]);
$output = [];
foreach ($items as $item) {
if ($item === 3) {
break;
}
$output[] = $item;
}
// 想定出力: [1, 2]
expect($output)->toBe([1, 2]);
});
副作用の処理には向かない
collect($users)->each(fn($u) => $u->save());
見た目はキレイ。でも save()
が失敗したら、どのデータが失敗したか分からず、ログも残らず、ループも止まる 危険があります。
Pest テストコード(目的:例外が飛ぶと後続が実行されないことの確認)
it('throws and halts on save failure with each', function () {
$users = collect([
Mockery::mock(User::class)->shouldReceive('save')->andThrow(new Exception('save failed'))->getMock(),
Mockery::mock(User::class)->shouldNotReceive('save')->getMock(),
]);
expect(function () use ($users) {
$users->each(fn($u) => $u->save());
})->toThrow(Exception::class);
});
try-catch で each を安全に使う
collect($users)->each(function ($user) {
try {
$user->save();
} catch (\Throwable $e) {
Log::error("保存失敗: {$user->id}");
}
});
Pest テストコード(目的:例外を catch して処理継続することの確認)
it('logs error and continues when using try-catch inside each', function () {
Log::shouldReceive('error')->once()->with('保存失敗: 1');
$users = collect([
Mockery::mock(User::class)->shouldReceive('save')->andThrow(new Exception)->getMock(),
Mockery::mock(User::class)->shouldReceive('save')->once()->getMock(),
]);
$users->each(function ($user, $index) {
try {
$user->save();
} catch (\Throwable $e) {
Log::error("保存失敗: " . ($index + 1));
}
});
expect(true)->toBeTrue();
});
map を使えば副作用を回避できる
副作用を避けたいなら map()
を使って新しいコレクションを返す形にしましょう。
$names = collect([
['name' => 'Alice'],
['name' => 'Bob'],
])->map(fn($user) => $user['name']);
// 結果: ['Alice', 'Bob']
このように、純粋関数的に値を返す用途には map()
の方が安全かつ明示的。
Laravelの他のループ処理とは?
メソッド | 説明 |
---|---|
each | クロージャ実行、return/break 非対応 |
map | 新しいコレクション返す(副作用なし) |
filter | 条件でフィルタリング |
reject | 条件を除外 |
reduce | 値を一つにまとめる |
tap | メソッドチェーン内に副作用挟む(メモリに注意) |
それぞれの使い時は?
- each → 副作用が軽い、ログ出力、view 描画など(中断できない)
- foreach → 中断・例外処理が必要な時(多くの処理に適してる)
- map → 値を加工・変換する時(新コレクションが必要)
- filter → 条件に合った要素だけ残す時
- reduce → 合計や統計など、まとめ処理に
- tap → メソッドチェーンの途中に副作用を差し込みたい時
- mapとeachを詳しくまなべる記事は 👉️ こちら
🎯 まとめ
each()
は return / break が効かない「止まらない列車」- 副作用があるなら try-catch + ログは必須
- 制御性・安全性が必要なら
foreach
/map
/chunkById
を検討 - Pest で「each は止まらないこと」をテストして保証しよう
- 副作用を避けたいなら
map()
でデータ変換に徹するのが吉 - 本記事のテストはGithubに追加してるよ!
次のおすすめはコチラ
実践 Laravel Collection パターン7選
Laravelの多対多リレーション実践運用大全(テストもあるよ!)
FAQ
-
each() はパフォーマンス的に遅い?
-
ほぼ同じ要素数なら foreach と大差ありません。ただし return/break が効かず “最後まで回り切る” ため、早期脱出できる処理と比べるとムダが増えるケースがあります。
-
途中で save() が失敗したらロールバックできる?
-
each() 単体ではトランザクション制御ができません。
-
each() の代替として map() を使うべきタイミングは?
-
純粋関数的に値を変換したいだけ
戻り値のコレクションが必要
副作用を避けたい/テストしやすさを重視
-
デバッグ時に each() の中で何が起こっているか確認したい
-
each処理内で
dump()
を使うのが一番お手軽。
コメントを残す