LaravelのCollectionとは？

### 目次
- [1. はじめに](#1-はじめに)
- [2. Collectionとは？](#2-collectionとは)
- [3. よく使うコレクションメソッド16選](#3-よく使うコレクションメソッド16選)
- [4. 実践的な使用例](#4-実践的な使用例)
- [4.1 APIレスポンスの整形](#41-apiレスポンスの整形)
- [4.2 よくあるエラーと対処法](#42-よくあるエラーと対処法)
- [5. まとめ](#5-まとめ)

## 1. はじめに
Laravelを使っていて、複雑な配列からデータを抽出する際にCollectionに関するエラーに遭遇したことはありませんか？
本記事では、Collectionってそもそも何？どういうメリットがあるの？よく遭遇するエラーは？といったところをまとめた記事にしたいと思います。

## 2. Collectionとは？
LaravelのCollectionは、配列をより便利に扱うためのラッパークラスです。  
通常のPHPの配列ではなく、Collectionを使うことで[メソッドチェーン](https://qiita.com/A-hirose/items/59aaaf3a9ece0818f38e)を活用しながら直感的にデータ操作ができます。

#### 配列とCollectionの比較
| 項目 | PHPの配列 | LaravelのCollection |
|------|----------|-------------------|
| 型 | 配列 (`array`) | `Illuminate\Support\Collection` クラス |
| 操作方法 | array_map(), array_filter() など | メソッド (map(), filter() など) |
| メソッドチェーン | ❌ 不可 | ✅ 可能 |
| 高度なデータ操作 | ❌ 手動実装が必要 | ✅ 多くの便利メソッドあり |
| 変更後のデータ | 直接変更 | 新しいCollectionを返す |

- ラッパークラス
⇒基本データ型や既存のデータ構造をオブジェクトとして扱うためのクラスのこと。
-  メソッドチェーン
⇒オブジェクト指向プログラミングで複数のメソッドを連続して呼び出す書き方のこと

#### Collectionの生成
```php
use Illuminate\Support\Collection;

// collect()関数を使用して、配列をCollectionオブジェクトに変換
$collection = collect([1, 2, 3, 4, 5]);
dump($collection);

/* 出力結果
Illuminate\Support\Collection {#1234
  #items: array:5 [
    0 => 1
    1 => 2
    2 => 3
    3 => 4
    4 => 5
  ]
}
*/
```
##### 解説
- Illuminate\Support\Collection {#1234}
→ Collection クラスのインスタンスであることを示します。（#1234 はオブジェクトIDなので実行ごとに変わる）
- #items: array:5
→ Collection 内部で管理されている配列データ ([1, 2, 3, 4, 5]) が表示される。
- 0 => 1, 1 => 2, ...
→ 配列のキーと値がリスト形式で表示される。


## 3. よく使うコレクションメソッド16選
#### 1. map()
コレクション内の各要素に処理を行い、**新しいコレクション**を返す。
```php
$numbers = collect([1, 2, 3]);
$mapped = $numbers->map(fn($num) => $num * 2);
print_r($mapped->all()); // [2, 4, 6]
```

#### 2. filter()
コレクション内の要素を**条件でフィルタリング**し、新しいコレクションを返す。
```php
$numbers = collect([1, 2, 3, 4, 5]);
$filtered = $numbers->filter(fn($num) => $num % 2 === 0);
print_r($filtered->all()); // [2, 4]
```

#### 3. reduce()
コレクションのすべての要素を**1つの値にまとめる**。
合計だけでなく、最小値や最大値、その他の演算にも利用可能。
```php
$numbers = collect([1, 2, 3]);
$sum = $numbers->reduce(fn($carry, $num) => $carry + $num, 0);  // 合計を計算する例
echo $sum; // 6
```

#### 4. pluck()
コレクションの要素から**指定したキーの値を取り出す**。
```php
$products = collect([
    ['name' => 'Apple', 'price' => 100],
    ['name' => 'Banana', 'price' => 150],
]);

$prices = $products->pluck('price');
print_r($prices->all()); // [100, 150]
```

#### 5. sort() / sortBy()
コレクションを**昇順に並べ替え**する。
```php
$numbers = collect([5, 1, 3, 2, 4]);
$sortedNumbers = $numbers->sort();
print_r($sortedNumbers->all()); // [1, 2, 3, 4, 5]
```
sortBy() は、**指定したキー**で並べ替えする。
下記の例は、'price'を基準に昇順にソートしている。
```php
$products = collect([
    ['name' => 'Banana', 'price' => 150],
    ['name' => 'Apple', 'price' => 100],
]);

$sortedByPrice = $products->sortBy('price');
print_r($sortedByPrice->values()->all());

/* 出力結果
Array
(
    [0] => Array
        (
            [name] => Apple
            [price] => 100
        )

    [1] => Array
        (
            [name] => Banana
            [price] => 150
        )
)
*/
```
#### 6. first()
コレクションの**最初の要素**を返す。
```php
$numbers = collect([1, 2, 3, 4, 5]);
$firstNumber = $numbers->first();
echo $firstNumber; // 1
```

#### 7. each()
コレクションの**各要素に処理を実行**する。戻り値は**元のコレクション**。
map()と似ているが、eachの場合、新しいコレクションは返さず出力のみ行う。
```php
$numbers = collect([1, 2, 3]);
$numbers->each(fn($num) => print($num * 2)); // 出力: 2, 4, 6
```

#### 8. groupBy()
コレクションを**指定したキーでグループ化**する。
```php
$products = collect([
    ['name' => 'Apple', 'category' => 'Fruit'],
    ['name' => 'Banana', 'category' => 'Fruit'],
    ['name' => 'Carrot', 'category' => 'Vegetable'],
]);

$grouped = $products->groupBy('category');
print_r($grouped->all());

/* 出力結果
Array
(
    [Fruit] => Array
        (
            [0] => Array
                (
                    [name] => Apple
                    [category] => Fruit
                )

            [1] => Array
                (
                    [name] => Banana
                    [category] => Fruit
                )
        )

    [Vegetable] => Array
        (
            [0] => Array
                (
                    [name] => Carrot
                    [category] => Vegetable
                )
        )
)
*/
```

#### 9. toArray()
コレクションを**配列に変換**します。
```php
$numbers = collect([1, 2, 3]);
$array = $numbers->toArray();
print_r($array); // [1, 2, 3]
```

#### 10. contains()
コレクションに**指定した値が含まれているか**をチェックします。
```php
$numbers = collect([1, 2, 3, 4, 5]);
$isPresent = $numbers->contains(3); // true
echo $isPresent;
```

#### 11. count()
コレクション内の要素数を返します。
```php
$numbers = collect([1, 2, 3, 4, 5]);
$count = $numbers->count();
echo $count; // 5
```

#### 12. unique()
コレクション内の**重複を排除**し、ユニークな要素だけのコレクションを返します。
```php
$numbers = collect([1, 2, 2, 3, 3, 3, 4]);
$unique = $numbers->unique();
print_r($unique->all()); // [1, 2, 3, 4]
```

#### 13. flatten()
コレクションの多次元配列を**1次元に平坦化**します。
```php
$numbers = collect([[1, 2], [3, 4], [5, 6]]);
$flattened = $numbers->flatten();
print_r($flattened->all()); // [1, 2, 3, 4, 5, 6]
```

#### 14. keyBy()
コレクション内の要素を**指定したキーでインデックス化**します。
```php
$products = collect([
    ['id' => 1, 'name' => 'Apple'],
    ['id' => 2, 'name' => 'Banana'],
]);

$keyed = $products->keyBy('id');
print_r($keyed->all());

/* 出力結果
Array
(
    [1] => Array
        (
            [id] => 1
            [name] => Apple
        )

    [2] => Array
        (
            [id] => 2
            [name] => Banana
        )
)
*/
```

#### 15. forget()
コレクションから**指定したキーの要素を削除**します。
```php
$numbers = collect([1, 2, 3, 4, 5]);
$numbers->forget(2); // 3番目の要素（インデックス2）を削除
print_r($numbers->all()); // [1, 2, 4, 5]
```

#### 16. sum()
コレクション内の**合計値**を取得します。
```php
$numbers = collect([1, 2, 3, 4, 5]);
$total = $numbers->sum();
echo $total; // 15
```

## 4. 実践的な使用例
### 4.1 APIレスポンスの整形
LaravelのCollectionはAPIレスポンスの整形に非常に便利！！
APIから取得した生データを必要な形式に整形する際に使用する。
#### 例: ユーザーのAPIレスポンスを整形
```php
use Illuminate\Support\Collection;

$users = collect([
    ['id' => 1, 'name' => 'John', 'email' => 'john@example.com'],
    ['id' => 2, 'name' => 'Jane', 'email' => 'jane@example.com'],
]);

// ユーザーの名前とメールアドレスだけを取得
$transformed = $users->map(function ($user) {
    return [
        'user_name' => $user['name'],
        'user_email' => $user['email']
    ];
});

// JSONレスポンスとして返却
return response()->json($transformed);
```

---

### 4.2 よくあるエラーと対処法
LaravelのCollectionを扱う際によく発生するエラーと、その対処法について。

#### 4.2.1 Call to undefined function collect() エラー
##### エラー内容
```php
$collection = collect([1, 2, 3, 4, 5]);
```
```javascript
Call to undefined function collect()
```
##### 原因
collect()はLaravelの Illuminate\Support\Collectionクラスの関数であるが、
Laravel以外の環境（純粋なPHPスクリプトなど）では利用できません。

##### 対処法
Laravelのフレームワークを正しくロードしているか確認する。
または、手動でCollectionクラスをインポートする。
```php
use Illuminate\Support\Collection;

$collection = new Collection([1, 2, 3, 4, 5]);
```

#### 4.2.2 Trying to get property of non-object エラー
##### エラー内容
```php
$collection = collect([
    ['id' => 1, 'name' => 'Alice'],
    ['id' => 2, 'name' => 'Bob'],
]);

$names = $collection->map(function ($user) {
    return $user->name; // ここでエラー
});
```
```vb
Trying to get property of non-object
```
##### 原因
$userは配列なのに、
$user->nameとオブジェクトのプロパティにアクセスしようとしている。

##### 対処法
配列のキーを使ってアクセスするように修正。
```php
$names = $collection->map(function ($user) {
    return $user['name']; // 正しく配列のキーを指定
});
```
もしオブジェクトとして扱いたい場合は、キャストするかcollect()にtoObject()を適用する。
```php
$collection = collect([
    (object) ['id' => 1, 'name' => 'Alice'],
    (object) ['id' => 2, 'name' => 'Bob'],
]);

$names = $collection->map(function ($user) {
    return $user->name; // 正常に動作
});
```

#### 4.2.3 Collection::map() の返り値が意図しない型になる
##### エラー内容
```php
$collection = collect([1, 2, 3]);

$collection->map(function ($item) {
    echo $item * 2;
});
```
```php
// 出力結果
246
```
##### 原因
map()は新しいCollectionを返すが、
上記のコードではechoしているため、返り値がnullになり、意図しない結果になる。

##### 対処法
新しい値をreturnするように修正。
```php
$modified = $collection->map(function ($item) {
    return $item * 2;
});

dump($modified->all()); // [2, 4, 6]
```

#### 4.2.4. Undefined array key エラー
##### エラー内容
```php
$collection = collect([
    ['id' => 1, 'name' => 'Alice'],
    ['id' => 2] // 'name' キーがない
]);

$names = $collection->map(function ($user) {
    return $user['name']; // 'name' キーが存在しない場合にエラー
});
```
```php
Undefined array key "name"
```
##### 原因
配列の中に 'name'キーがない要素があるため、アクセス時にエラーが発生。

##### 対処法
キーが存在するかチェックしてからアクセスする。
```php
$names = $collection->map(function ($user) {
    return $user['name'] ?? 'No Name'; // デフォルト値を設定
});
```

#### 4.2.5. Cannot use object of type stdClass as array エラー
##### エラー内容
```php
$collection = collect([
    (object) ['id' => 1, 'name' => 'Alice'],
    (object) ['id' => 2, 'name' => 'Bob'],
]);

$names = $collection->map(function ($user) {
    return $user['name']; // エラー発生
});
```
```sql
Cannot use object of type stdClass as array
```
##### 原因
$userはオブジェクトなのに、配列のように['name']でアクセスしようとしている。

##### 対処法
オブジェクトのプロパティとしてアクセスする。
```php
$names = $collection->map(function ($user) {
    return $user->name; // 正しい書き方
});
```
もし配列として扱いたい場合は、オブジェクトを配列に変換する。
```php
$collection = $collection->map(function ($user) {
    return (array) $user;
});
```

## 5. まとめ
業務では既にcollectionが使われている箇所の改修を任されたため、
複数のメソッドを連続して簡潔にソースを書くことができるということを知りませんでした。
今回はCollectionの基本から学ぶことができたので、
複雑な配列からデータを取得する際や、Collectionに関するエラーに遭遇した際などに今回学んだことを役立てたいと思います。

#### 参考
- [Laravel Collection公式ドキュメント](https://laravel.com/docs/11.x/collections)
- [Laravel Collection: 業務でよく使うメソッドを解説](https://kojichu.photoruction.com/entry/adcale20231206)
- [【Laravel】Collectionで使用できるメソッドの返り値](https://qiita.com/__suzuki/items/977b2edeb10f72a3cfd9)