> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 集合

> 介绍如何使用 Laravel 的 Collection 类高效地操作数据。

## 什么是集合

`Illuminate\Support\Collection` 类是操作数组数据的流式封装。
不必逐个调用 PHP 数组函数，你可以通过方法链直观地对数据进行加工。

```php theme={null}
// 普通数组操作
$names = array_filter(
    array_map(fn ($user) => $user['name'], $users),
    fn ($name) => $name !== null
);

// 使用集合
$names = collect($users)
    ->pluck('name')
    ->filter()
    ->values();
```

<Info>
  集合是**不可变**的。每个方法都不会修改原集合，而是返回新的集合实例。你可以在保留原数据的同时安全操作。
</Info>

## 创建集合

### `collect()` 辅助函数

最常用的方式。传入数组生成集合：

```php theme={null}
use Illuminate\Support\Collection;

// 从数组创建集合
$users = collect([
    ['name' => '山田太郎', 'age' => 28, 'role' => 'admin'],
    ['name' => '铃木花子', 'age' => 34, 'role' => 'editor'],
    ['name' => '佐藤次郎', 'age' => 22, 'role' => 'viewer'],
]);

// 嵌套数组同样可以
$products = collect([
    ['name' => '笔记本电脑', 'price' => 120000, 'stock' => 5],
    ['name' => '鼠标', 'price' => 3500, 'stock' => 20],
    ['name' => '键盘', 'price' => 8000, 'stock' => 12],
]);
```

### `Collection::make()`

等同于 `collect()`。若你偏好门面风格，可以用它。

```php theme={null}
$collection = Collection::make([1, 2, 3]);
```

### `Collection::fromJson()`

从 JSON 字符串创建集合，便于处理外部 API 响应。

```php theme={null}
$collection = Collection::fromJson('[{"name":"山田太郎"},{"name":"铃木花子"}]');
```

## 常用方法

### `map` — 转换每个元素

对每个元素执行处理，返回转换后的新集合。

```php theme={null}
$users = collect([
    ['name' => '山田太郎', 'email' => 'yamada@example.com'],
    ['name' => '铃木花子', 'email' => 'suzuki@example.com'],
]);

// 邮箱脱敏
$masked = $users->map(function (array $user) {
    $parts = explode('@', $user['email']);
    return [
        'name' => $user['name'],
        'email' => substr($parts[0], 0, 2) . '***@' . $parts[1],
    ];
});

// [['name' => '山田太郎', 'email' => 'ya***@example.com'], ...]
```

### `filter` / `reject` — 按条件过滤

`filter()` 保留满足条件的元素，`reject()` 排除满足条件的元素。

```php theme={null}
$products = collect([
    ['name' => '笔记本电脑', 'price' => 120000, 'in_stock' => true],
    ['name' => '鼠标', 'price' => 3500, 'in_stock' => false],
    ['name' => '键盘', 'price' => 8000, 'in_stock' => true],
]);

// 仅取库存商品
$inStock = $products->filter(fn ($product) => $product['in_stock']);

// 排除无库存（等同于反向 filter）
$available = $products->reject(fn ($product) => ! $product['in_stock']);

// 不带参数的 filter() 会移除 falsy 值
$names = collect(['山田', '', null, '铃木', false])->filter()->values();
// ['山田', '铃木']
```

<Tip>
  `filter()` 之后索引会出现空洞。调用 `values()` 可以重新编号从 0 开始。
</Tip>

### `first` / `last` — 获取单个元素

返回满足条件的第一个或最后一个元素。

```php theme={null}
$orders = collect([
    ['id' => 1, 'status' => 'shipped', 'amount' => 5000],
    ['id' => 2, 'status' => 'pending', 'amount' => 12000],
    ['id' => 3, 'status' => 'pending', 'amount' => 3500],
]);

// 第一个待处理订单
$nextOrder = $orders->first(fn ($order) => $order['status'] === 'pending');

// 无匹配时的默认值
$order = $orders->first(fn ($order) => $order['status'] === 'cancelled', null);

// 最后一个
$latest = $orders->last();
```

### `pluck` — 抽取指定键的值

从嵌套数组或模型中抽出特定字段。

```php theme={null}
$users = collect([
    ['id' => 1, 'name' => '山田太郎', 'department' => '开发部'],
    ['id' => 2, 'name' => '铃木花子', 'department' => '销售部'],
    ['id' => 3, 'name' => '佐藤次郎', 'department' => '开发部'],
]);

// 抽取名字
$names = $users->pluck('name');
// ['山田太郎', '铃木花子', '佐藤次郎']

// 以 id 为键做映射
$nameById = $users->pluck('name', 'id');
// [1 => '山田太郎', 2 => '铃木花子', 3 => '佐藤次郎']
```

### `groupBy` — 按键分组

```php theme={null}
$users = collect([
    ['name' => '山田太郎', 'department' => '开发部'],
    ['name' => '铃木花子', 'department' => '销售部'],
    ['name' => '佐藤次郎', 'department' => '开发部'],
    ['name' => '田中美咲', 'department' => '销售部'],
]);

$byDepartment = $users->groupBy('department');

// 用闭包动态指定分组键
$byFirstChar = $users->groupBy(fn ($user) => mb_substr($user['name'], 0, 1));
```

### `sortBy` / `sortByDesc` — 排序

```php theme={null}
$products = collect([
    ['name' => '笔记本电脑', 'price' => 120000],
    ['name' => '鼠标', 'price' => 3500],
    ['name' => '键盘', 'price' => 8000],
]);

// 价格升序
$cheapFirst = $products->sortBy('price');

// 价格降序
$expensiveFirst = $products->sortByDesc('price');

// 多键排序
$sorted = $products->sortBy([
    ['price', 'asc'],
    ['name', 'asc'],
]);
```

### `each` — 对每个元素执行处理

用于有副作用的操作（日志、发邮件等）。不会改变集合本身。

```php theme={null}
$orders = collect([
    ['id' => 1, 'user_id' => 10, 'amount' => 5000],
    ['id' => 2, 'user_id' => 11, 'amount' => 12000],
]);

$orders->each(function (array $order) {
    \Log::info("处理订单 #{$order['id']}", ['amount' => $order['amount']]);
});

// 返回 false 可提前退出循环
$orders->each(function (array $order) {
    if ($order['amount'] > 10000) {
        return false;
    }
});
```

### `flatMap` — 映射后再拍平一层

将每个元素映射为数组，并将整体拍平为一维。

```php theme={null}
$users = collect([
    ['name' => '山田太郎', 'tags' => ['php', 'laravel']],
    ['name' => '铃木花子', 'tags' => ['javascript', 'vue']],
]);

$allTags = $users->flatMap(fn ($user) => $user['tags']);
// ['php', 'laravel', 'javascript', 'vue']
```

### `reduce` — 聚合

把整个集合归约为单一值。

```php theme={null}
$orders = collect([
    ['product' => '笔记本电脑', 'quantity' => 1, 'price' => 120000],
    ['product' => '鼠标', 'quantity' => 2, 'price' => 3500],
    ['product' => '键盘', 'quantity' => 1, 'price' => 8000],
]);

$total = $orders->reduce(
    fn ($carry, $order) => $carry + ($order['price'] * $order['quantity']),
    0
);
// 135000
```

<Tip>
  单纯求和可用 `sum()`：`$orders->sum(fn ($o) => $o['price'] * $o['quantity'])`
</Tip>

### `reduceInto` — 聚合到对象

与 `reduce` 类似，但回调不需要返回值。适合直接修改已有对象。

```php theme={null}
class OrderStats
{
    public int $total = 0;
    public int $count = 0;
}

$orders = collect([
    ['amount' => 100],
    ['amount' => 250],
    ['amount' => 50],
]);

$stats = $orders->reduceInto(new OrderStats, function (OrderStats $stats, array $order) {
    $stats->total += $order['amount'];
    $stats->count++;
});

$stats->total; // 400
$stats->count; // 3
```

<Tip>
  `reduce` 通过回调返回值作为下一次 `$carry`，适合聚合基本类型。`reduceInto` 则一直修改传入的对象，写法更简洁。
</Tip>

若目标是标量或数组，可在回调中使用引用（`&`）。

```php theme={null}
$collection = collect([1, 2, 3, 4, 5]);

$even = $collection->reduceInto([], function (array &$result, int $value) {
    if ($value % 2 === 0) {
        $result[] = $value;
    }
});

// [2, 4]
```

### `chunk` — 分块

将大集合按指定大小切分。适合分批处理或分页展示。

```php theme={null}
$users = collect(range(1, 100))->map(fn ($i) => ['id' => $i, 'name' => "用户{$i}"]);

// 每 10 条一块
$chunks = $users->chunk(10);
// $chunks->count() === 10

$chunks->each(function (\Illuminate\Support\Collection $batch) {
    // 每批处理
});
```

## 方法链

集合的价值体现在方法链上。可以将多个操作串联，实现复杂的数据变换。

```php theme={null}
$orders = collect([
    ['customer' => '山田太郎', 'status' => 'completed', 'amount' => 5000, 'category' => '电子设备'],
    ['customer' => '铃木花子', 'status' => 'pending',   'amount' => 12000, 'category' => '电子设备'],
    ['customer' => '佐藤次郎', 'status' => 'completed', 'amount' => 3500, 'category' => '文具'],
    ['customer' => '田中美咲', 'status' => 'completed', 'amount' => 8000, 'category' => '电子设备'],
    ['customer' => '伊藤健一', 'status' => 'cancelled', 'amount' => 2000, 'category' => '文具'],
]);

$result = $orders
    ->filter(fn ($order) => $order['status'] === 'completed')
    ->filter(fn ($order) => $order['category'] === '电子设备')
    ->sortByDesc('amount')
    ->map(fn ($order) => [
        'customer' => $order['customer'],
        'amount'   => number_format($order['amount']) . '円',
    ])
    ->values();
```

## 与 Eloquent 联动

Eloquent 查询结果始终以 `Illuminate\Database\Eloquent\Collection` 实例返回。
它继承自基础 `Collection`，上述方法都可以使用。

```php theme={null}
use App\Models\User;
use App\Models\Order;

// get() 返回集合
$users = User::where('is_active', true)->get(); // Collection

// 集合方法可以直接使用
$adminEmails = User::all()
    ->filter(fn ($user) => $user->role === 'admin')
    ->pluck('email');

// 关联加载后的数据也是集合
$orders = Order::with('items')->where('status', 'completed')->get();

$summary = $orders->map(fn ($order) => [
    'id'         => $order->id,
    'customer'   => $order->user->name,
    'item_count' => $order->items->count(),
    'total'      => $order->items->sum('price'),
]);
```

<Warning>
  能通过数据库完成的过滤和排序，请使用查询构建器（`where`、`orderBy` 等），而不是先转为集合再操作。将全部数据加载到内存后再过滤会浪费资源。
</Warning>

### Eloquent 集合特有方法

`Eloquent\Collection` 提供额外方法：

```php theme={null}
$users = User::all();

// 按 ID 查找
$user = $users->find(1);

// 取所有主键
$ids = $users->modelKeys();

// 批量加载关联
$users->load('orders', 'profile');

// 差集与交集
$diff = $users->diff($otherUsers);
$intersect = $users->intersect($otherUsers);
```

## Lazy Collections

普通集合会把全部数据加载到内存。`LazyCollection` 借助 PHP Generator 逐条处理，适合数万条以上的大数据集，可节省内存。

```php theme={null}
use Illuminate\Support\LazyCollection;

// 普通集合：全部载入内存
$users = User::all(); // 10 万条就有 10 万条在内存里

// LazyCollection：逐条处理
User::cursor()->each(function (User $user) {
    // 一次只留一条在内存
});
```

### 创建 LazyCollection

```php theme={null}
use Illuminate\Support\LazyCollection;

// 从闭包（generator）创建
$lazy = LazyCollection::make(function () {
    $handle = fopen('large-file.csv', 'r');

    while (($line = fgetcsv($handle)) !== false) {
        yield $line;
    }

    fclose($handle);
});

// Eloquent 的 cursor() 返回 LazyCollection
$lazy = User::where('is_active', true)->cursor();
```

### 大数据批处理

```php theme={null}
use App\Models\Order;

// 逐条处理全部订单（内存友好）
Order::cursor()
    ->filter(fn ($order) => $order->amount > 10000)
    ->each(fn ($order) => $order->sendConfirmationEmail());

Order::where('status', 'completed')
    ->cursor()
    ->filter(fn ($order) => $order->amount > 10000)
    ->each(fn ($order) => $order->sendConfirmationEmail());
```

<Info>
  `cursor()` 逐条从数据库取回，处理大量记录时可大幅节省内存。但数据库连接会持续到处理完成。
</Info>

### 用 `take` 与 `skip` 分页

```php theme={null}
$lazy = LazyCollection::make(function () {
    foreach (range(1, 1000000) as $i) {
        yield $i;
    }
});

// 跳过前 1000，取接下来的 100
$page = $lazy->skip(1000)->take(100)->values();
```

## 小结

<AccordionGroup>
  <Accordion title="常用方法汇总">
    | 方法                            | 说明        |
    | ----------------------------- | --------- |
    | `collect($array)`             | 创建集合      |
    | `map($callback)`              | 转换每个元素    |
    | `filter($callback)`           | 过滤        |
    | `reject($callback)`           | 排除匹配      |
    | `first($callback)`            | 满足条件的第一个  |
    | `pluck($key)`                 | 抽取指定键     |
    | `groupBy($key)`               | 按键分组      |
    | `sortBy($key)`                | 升序        |
    | `sortByDesc($key)`            | 降序        |
    | `each($callback)`             | 逐个执行（副作用） |
    | `flatMap($callback)`          | 映射并拍平     |
    | `reduce($callback, $initial)` | 聚合为单个值    |
    | `chunk($size)`                | 按大小分块     |
    | `values()`                    | 重排索引      |
    | `sum($key)`                   | 求和        |
    | `count()`                     | 计数        |
  </Accordion>

  <Accordion title="集合 vs 数组函数">
    集合在可读性上远胜数组函数。

    ```php theme={null}
    // 使用数组函数
    $result = array_values(array_filter(
        array_map(fn ($u) => $u['name'], $users),
        fn ($name) => strlen($name) > 2
    ));

    // 使用集合
    $result = collect($users)
        ->pluck('name')
        ->filter(fn ($name) => strlen($name) > 2)
        ->values()
        ->all();
    ```
  </Accordion>

  <Accordion title="普通集合 vs LazyCollection">
    * **普通集合**：几百到几千条数据，直观简洁。
    * **LazyCollection**：数万条以上大数据，节省内存。搭配 `cursor()` 效果最佳。
    * Eloquent 的 `get()` 返回普通集合，`cursor()` 返回 LazyCollection。
  </Accordion>
</AccordionGroup>


## Related topics

- [Eloquent 集合](/zh/eloquent-collections.md)
- [集合的高阶消息](/zh/advanced/higher-order-messages.md)
- [Eloquent API 资源](/zh/eloquent-resources.md)
- [Macroable trait](/zh/advanced/macroable.md)
- [Laravel AI SDK](/zh/ai-sdk.md)
