- カテゴリ: collection
- 掲載バージョン: Laravel 12・PHP 8.4
- 名前空間 / FQCN:
Illuminate\Support\Collection::countBy - 関連:
count/groupBy/mapToGroups/sum/collect - 変更履歴: 目立った仕様変更なし(初期から存在)
要点(TL;DR)
- コレクション内の要素を「値ごとの件数」に集計するメソッド
collect($items)->countBy()で「値 → 出現回数」のコレクションを返す- 罠:戻り値もコレクション/
LazyCollectionでは全件走査/groupByと用途が違う
概要
countBy は、コレクション内にある値の出現回数を「カテゴリ別の件数」として集計するメソッドです。
アクセスログからステータスコードごとの件数、ユーザー一覧から都道府県別の人数などを集計するのに便利です。groupBy が「グループごとの配列」を返すのに対して、countBy は「グループごとの件数」を直接返します。
構文 / シグネチャ
public function countBy($callback = null): static
引数
| 引数 | 型 | 必須 | 既定値 | 説明 |
|---|---|---|---|---|
$callback | `callable | string | null` | いいえ |
callable のシグネチャ(典型例):
function ($value, $key): mixed
戻り値
Illuminate\Support\Collection(static)- キー:集計対象の値(またはコールバックの戻り値)
- 値:各キーに該当する要素数(
int)
例外 / 副作用
- 直接的な例外はなし
- LazyCollection に対して呼ぶと、全件イテレーションが走る(ストリーム処理では性能に注意)
使用例
最小例(値そのままを集計)
<?php
use Illuminate\Support\Collection;
$numbers = collect([1, 2, 2, 3, 3, 3]);
$result = $numbers->countBy();
dump($result->all());
// [
// 1 => 1,
// 2 => 2,
// 3 => 3,
// ]
実務例1:ユーザー一覧から都道府県別人数を集計
<?php
use App\Models\User;
$users = User::all();
// 都道府県別のユーザー数
$prefCounts = $users->countBy(function ($user) {
// null の場合は '不明' として集計
return $user->prefecture ?? '不明';
});
// 例: ['東京' => 120, '大阪' => 80, '不明' => 5]
foreach ($prefCounts as $pref => $count) {
echo "{$pref}: {$count}人" . PHP_EOL;
}
実務例2:ログ配列からステータスコード別の件数
<?php
$logs = collect([
['status' => 200],
['status' => 200],
['status' => 404],
['status' => 500],
['status' => 500],
['status' => 500],
]);
$statusCounts = $logs->countBy('status');
dump($statusCounts->toArray());
// [
// 200 => 2,
// 404 => 1,
// 500 => 3,
// ]
実務例3:時刻帯ごとのアクセス数を集計
<?php
use Carbon\Carbon;
$accessLogs = collect([
['accessed_at' => '2025-11-23 09:12:00'],
['accessed_at' => '2025-11-23 09:35:00'],
['accessed_at' => '2025-11-23 10:01:00'],
]);
$hourlyCounts = $accessLogs->countBy(function ($log) {
$dt = Carbon::parse($log['accessed_at']);
// 「09時」「10時」のような文字列で集計
return $dt->format('H時');
});
dump($hourlyCounts->toArray());
// ['09時' => 2, '10時' => 1]
よくある落とし穴・注意
- 戻り値も Collection
countBy()の結果に対してさらに->sortDesc()などチェーンできるが、単純な件数合計が欲しいだけなら->sum()や->count()との混同に注意。
- キーが
stringにキャストされることがあるtrue/false/nullなどをそのまま集計すると、1/""などにまとまるケースがあるため、必要ならコールバックで明示的にラベル化する。
- LazyCollection は全件走査
- 大量データを Eloquent でストリーミングしている場合、
countBy()した時点で全件メモリに展開されるので注意。 - 件数を SQL で出せるなら、DB の
GROUP BY+COUNT(*)のほうが高速。
- 大量データを Eloquent でストリーミングしている場合、
代替・関連APIとの比較
| メソッド | 目的・特徴 |
|---|---|
count() | コレクション全体の件数を 1 つの数値で取得 |
countBy() | 値ごとの件数を「値 → 件数」のマップで取得 |
groupBy() | 値ごとに要素を配列にグルーピング(件数は自分で ->map->count() などが必要) |
sum() | 数値列の合計を取得(件数ではなく合計値) |
選び方の目安
- 「全体で何件あるか」→
count - 「都道府県ごとの人数」「ステータスコードごとの件数」→
countBy - 「グループごとにさらに別の集計・処理をしたい」→
groupBy+mapなど
チェーン可否・破壊性・計算量
- チェーン可否: 可能(戻り値も Collection)
- 破壊的/非破壊: 非破壊(元のコレクションは変更されない)
- LazyCollection 対応: 対応するが全件走査になる
- 計算量: おおよそ O(n)(要素数 n に比例して 1 回ずつ見る)
簡単な入出力イメージ:
collect(['a', 'b', 'a'])->countBy()->toArray();
// 入力: ['a', 'b', 'a']
// 出力: ['a' => 2, 'b' => 1]
テスト例(Pest)
<?php
use Illuminate\Support\Collection;
it('counts items by value', function () {
$c = collect(['ok', 'ng', 'ok', 'ok']);
$result = $c->countBy();
expect($result->get('ok'))->toBe(3)
->and($result->get('ng'))->toBe(1);
});
it('counts items by callback key', function () {
$c = collect([1, 2, 3, 4, 5]);
// 偶数・奇数ごとに件数集計
$result = $c->countBy(fn ($n) => $n % 2 === 0 ? 'even' : 'odd');
expect($result->get('even'))->toBe(2)
->and($result->get('odd'))->toBe(3);
});
トラブルシュート(エラー別)
| 症状 / エラー | 原因 | 対処 |
|---|---|---|
| 想定と違うキーで集計される | null / true / false などがそのままキーになっている | コールバックで '不明' や 'yes'/'no' など明示的な文字列キーに変換する |
| メモリ使用量が急増する | LazyCollection に対して大量データを countBy() している | DB 側 GROUP BY で集計するか、件数が必要な範囲だけに対象を絞る |
| 件数だけ欲しいのに扱いが複雑 | countBy の結果が Collection で、その後の処理が複雑化している | groupBy()->map->count() との比較を行い、必要に応じてロジックを整理 |
参考リンク
- Laravel Docs — Collections(countBy)
https://laravel.com/docs/12.x/collections - Laravel API Documentation —
Illuminate\Support\Collection
https://laravel.com/api/12.x/Illuminate/Support/Collection.html
