- カテゴリ: collection
- 対応バージョン: Laravel 12・PHP 8.2
- 名前空間 / FQCN / コマンド:
Illuminate\Support\Collection::chunkWhile - 関連: chunk / split / groupBy / partition / sliding
- 変更履歴: —
要点(TL;DR)
- 隣接する要素を「条件が崩れるまで」まとめて小さなコレクションに分割する。
collect($xs)->chunkWhile(fn($v,$k,$chunk) => $chunk->isEmpty() || $v - $chunk->last() <= 1)- 罠: 初回要素で
$chunk->isEmpty()を考慮する/未ソート入力だと期待と異なる/chunk($size)(固定サイズ)やDBのquery()->chunk()と混同しない。
概要
chunkWhile は、連続する要素に対して与えた条件が 真のあいだ 同じグループ(チャンク)へ詰め、偽になった時点で新しいチャンクを開始します。時系列データの連続区間、同一カテゴリが連続するレコード、値の変化点検出などに有効です。結果は「コレクションのコレクション」になります。
構文 / シグネチャ
use Illuminate\Support\Collection;
/**
* @param callable(mixed $value, mixed $key, Collection $chunk): bool $callback
* @return Collection<int, Collection> // 外側: チャンク配列、内側: 各チャンク
*/
public function chunkWhile(callable $callback): Collection;
引数(表)
| 引数 | 型 | 必須 | 既定値 | 説明 |
|---|---|---|---|---|
$callback | callable(mixed $value, mixed $key, Collection $chunk): bool | ✅ | なし | 「現在の要素を今のチャンクに追加するか」を返すコールバック。false を返すと新しいチャンクを開始。$chunk は「現在構築中のチャンク」。 |
- 戻り値:
Collection<int, Collection>(外側はチャンクの配列、内側は各チャンク)。内側は元のキーを保持。 - 例外/副作用:コールバック内の例外はそのまま伝播。メモリは全要素+分割結果を保持(eager)。
コレクション特性(collectionカテゴリ規約)
- チェーン可否: 可能(
->map(),->flatten()等と併用) - 破壊性: 非破壊(元コレクションは変更しない)
- キー保持: 内側チャンクで保持(必要に応じて
->values()で詰め直す) - LazyCollection: 対応(
collect()->lazy()/LazyCollectionでも利用可、ストリーム処理で省メモリ) - 計算量: O(n)(コールバックは各要素1回実行)
入出力イメージ
collect([1,2,3,10,11,20])
->chunkWhile(/* 連続値の差が1以下 */)
==>
[
[1,2,3], // 1,2,3 は近接
[10,11], // 10,11 は近接
[20], // 単独
]
使用例
最小例(連続増加の塊に分割)
<?php
use Illuminate\Support\Collection;
$numbers = collect([1, 2, 3, 10, 11, 20]);
$chunks = $numbers->chunkWhile(function ($v, $k, Collection $chunk) {
// 先頭要素は常に開始
if ($chunk->isEmpty()) return true;
// 「前要素との差が 1 以下なら同じ塊」にする例
return ($v - $chunk->last()) <= 1;
});
// 出力確認([[1,2,3],[10,11],[20]])
print_r($chunks->map->values()->values()->toArray());
実務例(時系列ログを「同じ時刻帯で連続」ごとに処理)
<?php
use Illuminate\Support\Collection;
use Carbon\CarbonImmutable;
$logs = collect([
['ts' => CarbonImmutable::parse('2025-09-01 10:00:10'), 'level' => 'info', 'msg' => 'A'],
['ts' => CarbonImmutable::parse('2025-09-01 10:15:02'), 'level' => 'warn', 'msg' => 'B'],
['ts' => CarbonImmutable::parse('2025-09-01 10:50:21'), 'level' => 'info', 'msg' => 'C'],
['ts' => CarbonImmutable::parse('2025-09-01 11:01:03'), 'level' => 'error', 'msg' => 'D'],
['ts' => CarbonImmutable::parse('2025-09-01 11:20:00'), 'level' => 'info', 'msg' => 'E'],
]);
// 同一「時」内の連続部分で塊化し、塊ごとに集計する例
$hourlyChunks = $logs->sortBy('ts')->chunkWhile(function ($row, $k, Collection $chunk) {
if ($chunk->isEmpty()) return true;
return $row['ts']->isSameHour($chunk->last()['ts']);
});
$summary = $hourlyChunks->map(function (Collection $chunk) {
$hour = $chunk->first()['ts']->format('Y-m-d H:00');
return [
'hour' => $hour,
'count' => $chunk->count(),
'levels' => $chunk->groupBy('level')->map->count()->all(),
];
});
print_r($summary->values()->toArray());
// 例:
// [
// ['hour' => '2025-09-01 10:00', 'count' => 3, 'levels' => ['info'=>2,'warn'=>1]],
// ['hour' => '2025-09-01 11:00', 'count' => 2, 'levels' => ['error'=>1,'info'=>1]],
// ]
よくある落とし穴・注意
- 初回要素の扱い:
$chunk->isEmpty()を明示しないと、最初の要素が除外されるロジックになりがち。 - 未ソート入力:
chunkWhileは隣接にのみ作用。ソートされていないと期待と異なる塊になる。必要なら事前に->sortBy()。 chunk($size)との差:chunkは固定サイズで分割、chunkWhileは条件が切り替わる点で分割。目的に応じて選択。- DBクエリの
chunk()と混同注意:Eloquent/QueryBuilder のchunk($size)はDBから分割取得する仕組み。chunkWhileはメモリ上のコレクションに対する操作。 - メモリ:大きな配列を丸ごと扱うとメモリ圧迫。ストリーム処理が必要なら
LazyCollectionを検討。
代替・関連APIとの比較
chunk($size):固定サイズで等分割。レコード数ごとにバッチ処理したいとき。groupBy($key|$callback):同じキーの要素を全体からまとめる(隣接関係は無視)。カテゴリ集計に最適。partition($callback):条件に合う/合わないで2分割。split($number):指定数のグループへ均等に分割。sliding($size, $step=1):移動窓の切り出し(オーバーラップあり)。時系列の局所計算に。
テスト例(Pest)
<?php
use Illuminate\Support\Collection;
it('chunks consecutive numbers into groups', function () {
$xs = collect([1,2,3,10,11,20]);
$chunks = $xs->chunkWhile(function ($v, $k, Collection $chunk) {
if ($chunk->isEmpty()) return true;
return ($v - $chunk->last()) <= 1;
});
expect($chunks->count())->toBe(3)
->and($chunks->values()->get(0)->values()->all())->toBe([1,2,3])
->and($chunks->values()->get(1)->values()->all())->toBe([10,11])
->and($chunks->values()->get(2)->values()->all())->toBe([20]);
});
トラブルシュート(エラー別)
| 症状/エラー | 原因 | 対処 |
|---|---|---|
Call to undefined method ... chunkWhile() | Laravel が古い/コレクションでない配列に対して呼び出し | フレームワークを更新する/collect($array) で包む |
| 期待通りに分割されない | 入力が未ソート、条件が連続性を正しく表していない | ->sortBy() を挟む/$chunk->isEmpty() と「直前値/先頭値」を使って条件を見直す |
| メモリ不足 | 大規模データを全件保持 | LazyCollection でストリーム処理/DB側 chunk() で分割取得して都度処理 |
参考リンク
- Laravel公式ドキュメント(Collections / chunkWhile): https://laravel.com/docs/12.x/collections#method-chunkwhile
- Laravel公式ドキュメント(Lazy Collections): https://laravel.com/docs/12.x/collections#lazy-collections
- GitHub: illuminate/collections(EnumeratesValues 実装): https://github.com/laravel/framework/tree/12.x/src/Illuminate/Support/Traits/EnumeratesValues

