unique — コレクションの重複要素を除去

collection
  • カテゴリ: collection
  • 掲載バージョン: Laravel 12・PHP 8.4
  • 名前空間 / FQCN / コマンド: Illuminate\Support\Collection::unique
  • 関連: uniqueStrict / duplicates / values / pluck / distinct(Query Builder)
  • 変更履歴: 特筆なし

要点(TL;DR)

  • 指定キー(またはコールバック)の値が同じ要素を先勝ちで1件に絞る
  • collect([...])->unique('email')
  • 罠:既定は緩やかな比較(==)/キーは保持/大量データはDB側の distinct() を検討

概要

unique はコレクション内の重複を取り除きます。キー名やコールバックで「重複判定に使う値」を指定できます。既定は緩やかな比較(==)で、最初に現れた要素が残ります。Eloquentの大量行ではメモリ使用が増えるため、DBクエリでの distinct() や集計に切り替えるのが実務的です。

構文 / シグネチャ

// Collection
public function unique($key = null, bool $strict = false): static;
public function uniqueStrict($key = null): static;
  • 引数(Collection::unique) 引数 型 必須 既定値 説明 $key `string callable null` $strict bool false 厳密比較(===)にするか。true なら型も区別
  • 戻り値Collection非破壊。先勝ちで要素採用/元キー保持
  • 例外/副作用:ユーザー定義コールバックでの例外以外に特記なし

使用例

最小例

use Illuminate\Support\Collection;

$numbers = collect([1, '1', 2, 2, 3]);
$loose   = $numbers->unique();           // 既定は緩やか: [1, 2, 3](キー保持)
$strict  = $numbers->unique('', true);   // 厳密: [1, '1', 2, 3]

// 連番にしたい場合
$reindexed = $loose->values();           // [1, 2, 3]

実務例:メールアドレスで重複ユーザーを除去

use App\Models\User;
use Illuminate\Support\Collection;

$users = User::query()
    ->select(['id','name','email'])
    ->latest('id')
    ->take(1000)              // 例:直近1000件だけを見る
    ->get();

$uniqueByEmail = $users->unique('email'); // 重複メールは先に取得したユーザーのみ残す

foreach ($uniqueByEmail as $user) {
    // 通知対象などに利用
}

ネストキー/コールバック

$data = collect([
    ['user' => ['id' => 10, 'name' => 'A']],
    ['user' => ['id' => 10, 'name' => 'A alt']],
    ['user' => ['id' => 11, 'name' => 'B']],
]);

// ドット記法
$byUserId = $data->unique('user.id');

// コールバック(メールを正規化して比較)
$records = collect([
    ['email' => 'Foo+tag@Example.com'],
    ['email' => 'foo@example.com'],
]);
$unique = $records->unique(fn ($row) => strtolower(preg_replace('/\+.*/', '', $row['email'])));

よくある落とし穴・注意

  • 比較が既定で緩やか1'1' は同一扱い。型も区別したいなら $strict=trueuniqueStrict()
  • キーが保持されるvalues() で整列しないと [0,1,2...] にならない。
  • オブジェクト比較の期待外れ:オブジェクト同士の同一性ではなく、キーやコールバックで比較値を明示する。
  • 大規模データのメモリ:アプリ側の unique は全件を保持。DBで select('email')->distinct() や集約、ストリーム処理(LazyCollection)を検討。
  • 先勝ち:後から来た重複は捨てられる。必要なら事前に並び順を調整。

代替・関連APIとの比較

  • uniqueStrict($key = null):厳密比較(===)固定。型も区別。
  • duplicates()/duplicatesStrict():重複している値の抽出に使う(原因調査向け)。
  • array_unique()(PHP):配列ベースで近いが、コレクションのチェーンやコールバック/ドット記法は不可。
  • Query Builder distinct():DB側で重複排除。件数が多い/I/O削減が目的ならこちらを優先。

テスト例(Pest)

<?php

use Illuminate\Support\Collection;

it('removes duplicates with loose compare and preserves keys', function () {
    $c = collect([10 => 1, 11 => '1', 12 => 2]);
    $u = $c->unique();
    expect($u->keys()->all())->toBe([10, 12])   // 先勝ち・キー保持
        ->and($u->values()->all())->toBe([1, 2]);
});

it('can use strict compare', function () {
    $c = collect([1, '1', 1]);
    expect($c->unique('', true)->all())->toBe([1, '1']);
});

it('accepts dot notation and callback', function () {
    $c = collect([
        ['user' => ['id' => 1]], ['user' => ['id' => 1]], ['user' => ['id' => 2]],
    ]);
    expect($c->unique('user.id')->values()->count())->toBe(2);

    $d = collect([['email' => 'A@ex.com'], ['email' => 'a@EX.com']]);
    $u = $d->unique(fn($r) => strtolower($r['email']));
    expect($u->count())->toBe(1);
});

トラブルシュート(エラー別)

症状/エラー原因対処
重複が除去されない比較キー未指定でオブジェクトや配列をそのまま比較'key' を指定、またはコールバックで比較値を返す
1'1' が同一扱いになる既定が緩やかな比較$strict=trueuniqueStrict() を使う
結果のキーが 0 始まりでない元キー保持仕様values() で再インデックス
メモリ使用量が多い全件をアプリ側に読み込みDBの distinct() / 集約、chunk()LazyCollection に切替
後から来た値を優先したい先勝ち仕様事前に sortByDesc() などで並び替える

参考リンク

長野県・北アルプス地方在住のフリーランスWebプログラマー。
「落ち着くためのWeb開発」をテーマに、訪れる人が安心して使えるサービスづくりを心がけています。

LaravelやWordPressなどのWebアプリケーション開発を得意とし、技術面の安定性はもちろん、運用後も長く活用できる設計を大切にしています。
静かな山間の暮らしから生まれる視点で、シンプルかつ本質的な解決策をご提案します。

野鳥観察も趣味のひとつで、特にミソサザイ(Wren)に魅力を感じています。
小さな体に反して力強く上向きの尾羽、そして澄んだ鳴き声が遠くまで響く姿に、静かな存在感と芯の強さを感じます。
このサイト名「Laravel Wren」には、そんなミソサザイのように、小さくても確かな価値を届けたいという想いを込めています。

信頼できるパートナーとして、そして気軽に相談できる存在として、あなたのWebプロジェクトをサポートします。

Yudai Tsuyuzakiをフォローする