【Laravel】groupByを重ねて複数使う

#はじめに

Laravelのコレクションの便利なメソッド`groupBy`。

あれこれ触ったので、覚えたことを忘れないよう、練習問題にしてみました。

タイトルの、groupByを重ねて複数使う、最後の問3です。
すぐにやり方を知りたいという方は[こちら](https://qiita.com/shonansurvivors/items/595c72b0a4a3e1dd3ae0#%E5%95%8F3%E3%81%AE%E8%A7%A3%E7%AD%94)へどうぞ。

# 問題

`Order`, `OrderDetail`, `Book`, `Publisher`モデルがあり、`OrderDetail`からリレーションのある`Book`, `Publisher`までを以下のように取得しました。

```php
$order_details = OrderDetail::with('book.publisher')->get();

print_r(json_encode($order_details, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT))
```

```:$order_detailsをJSON化したもの
[
    {
        "id": 10001,
        "order_id": 1,
        "book_id": 111,
        "selling_price": 3000,
        "book": {
            "id": 111,
            "publisher_id": 11,
            "name": "自習Laravel",
            "list_price": 3500,
            "is_electronic_book": false,
            "publisher": {
                "id": 11,
                "name": "AA出版"
            }
        }
    },
    {
        "id": 10002,
        "order_id": 1,
        "book_id": 222,
        "selling_price": 2500,
        "book": {
            "id": 222,
            "publisher_id": 22,
            "name": "基礎から学ぶPHP",
            "list_price": 3000,
            "is_electronic_book": true,
            "publisher": {
                "id": 22,
                "name": "BB出版"
            }
        }
    },
    {
        "id": 10003,
        "order_id": 1,
        "book_id": 333,
        "selling_price": 3000,
        "book": {
            "id": 333,
            "publisher_id": 22,
            "name": "パーフェクトLaravel",
            "list_price": 3000,
            "is_electronic_book": false,
            "publisher": {
                "id": 22,
                "name": "BB出版"
            }
        }
    }
]
```

##問1

`publisher_id`で`groupBy`してください。

##問2

問1の結果では、グループ化された配列のキーは`publisher_id`の値になります。(`11`や`22`)

```:
{
    "11": [
        {
            "id": 10001,
            "order_id": 1,
            "book_id": 111,
            "selling_price": 3000,
            "book": {
                "id": 111,
                "publisher_id": 11,
                "name": "自習Laravel",
                "list_price": 3500,
                "is_electronic_book": false,
                "publisher": {
                    "id": 11,
                    "name": "AA出版"
                }
            }
        }
    ],
    "22": [
        {
            "id": 10002,
            "order_id": 1,
            "book_id": 222,
//略
```

これを`0`から振り直してください。(JSON化した場合、以下のように出力されるようにしてください)

```
[
    [
        {
            "id": 10001,
            "order_id": 1,
            "book_id": 111,
            "selling_price": 3000,
            "book": {
                "id": 111,
                "publisher_id": 11,
                "name": "自習Laravel",
                "list_price": 3500,
                "is_electronic_book": false,
                "publisher": {
                    "id": 11,
                    "name": "AA出版"
                }
            }
        }
    ],
    [
        {
            "id": 10002,
            "order_id": 1,
            "book_id": 222,
//略
```

##問3

まず、`is_electronic_book`で`groupBy`し、その上でそれぞれの要素に対して、`publisher_id`で`groupBy`してください。

##補足

必要に応じてLaravel公式ドキュメントの`groupBy`ほかを参照してください。
https://readouble.com/laravel/5.8/ja/collections.html#method-groupby

ここから先、解答になります。

...

...

...

#解答

##問1の解答

>`publisher_id`で`groupBy`してください。

`publisher_id`は、`book`をキーとする値の中に、さらにキーとして存在していました。

```:$order_detailsをJSON化したものの抜粋
        "book": {
            "id": 111,
            "publisher_id": 11,
```

こういった場合は、`.`で繋いで指定すればOKです。

```php:問1の解答例
$answer1 = $order_details->groupBy('book.publisher_id');
```

```php:問1の別解
$answer1 = $order_details->groupBy('book.publisher.id');
```

##問2の解答

>問1の結果では、グループ化された配列のキーは`publisher_id`の値になります。(`11`や`22`)
>これを`0`から振り直してください。

キーの振り直しにはコレクションのメソッドである`values`が使えます。

>valuesメソッドはキーをリセット後、連続した整数にした新しいコレクションを返します。

>Laravel5.8 公式ドキュメント - values
https://readouble.com/laravel/5.8/ja/collections.html#method-values


```php:問2の解答例
$answer2 = $order_details->groupBy('book.publisher_id')
    ->values();
```

```:問2の解答例の結果をJSON化したもの
[
    [
        {
            "id": 10001,
            "order_id": 1,
            "book_id": 111,
            "selling_price": 3000,
            "book": {
                "id": 111,
                "publisher_id": 11,
                "name": "自習Laravel",
                "list_price": 3500,
                "is_electronic_book": false,
                "publisher": {
                    "id": 11,
                    "name": "AA出版"
                }
            }
        }
    ],
    [
        {
            "id": 10002,
            "order_id": 1,
            "book_id": 222,
            "selling_price": 2500,
            "book": {
                "id": 222,
                "publisher_id": 22,
                "name": "基礎から学ぶPHP",
                "list_price": 3000,
                "is_electronic_book": true,
                "publisher": {
                    "id": 22,
                    "name": "BB出版"
                }
            }
        },
        {
            "id": 10003,
            "order_id": 1,
            "book_id": 333,
            "selling_price": 3000,
            "book": {
                "id": 333,
                "publisher_id": 22,
                "name": "パーフェクトLaravel",
                "list_price": 3000,
                "is_electronic_book": false,
                "publisher": {
                    "id": 22,
                    "name": "BB出版"
                }
            }
        }
    ]
]
```

##問3の解答

`groupBy`を重ねて複数使うには、同じくコレクションのメソッドである`transform`も利用することが考えられます。

```php:問3の解答例
$answer3 = $order_details->groupBy('book.is_electronic_book')
    ->transform(function ($order_detail) {
        return $order_detail->groupBy('book.publisher_id')
            ->values(); // valuesは用途に応じてお好みで。詳細は問2参照。
        });
```

>transformメソッドはコレクションを繰り返し処理し、コレクションの各アイテムに指定したコールバックを適用します。コレクション中のアイテムはコールバックから返される値に置き換わります。

>Laravel5.8 公式ドキュメント - transform
https://readouble.com/laravel/5.8/ja/collections.html#method-transform

解答例では`is_electronic_book`で`groupBy`した結果、2つの要素に分かれていますので、この`transform`を使って各々の要素に対してさらに`publisher_id`で`groupBy`しています。

```:問3の解答例の結果をJSON化したもの
[
    [
        [
            {
                "id": 10001,
                "order_id": 1,
                "book_id": 111,
                "selling_price": 3000,
                "book": {
                    "id": 111,
                    "publisher_id": 11,
                    "name": "自習Laravel",
                    "list_price": 3500,
                    "is_electronic_book": false,
                    "publisher": {
                        "id": 11,
                        "name": "AA出版"
                    }
                }
            }
        ],
        [
            {
                "id": 10003,
                "order_id": 1,
                "book_id": 333,
                "selling_price": 3000,
                "book": {
                    "id": 333,
                    "publisher_id": 22,
                    "name": "パーフェクトLaravel",
                    "list_price": 3000,
                    "is_electronic_book": false,
                    "publisher": {
                        "id": 22,
                        "name": "BB出版"
                    }
                }
            }
        ]
    ],
    [
        [
            {
                "id": 10002,
                "order_id": 1,
                "book_id": 222,
                "selling_price": 2500,
                "book": {
                    "id": 222,
                    "publisher_id": 22,
                    "name": "基礎から学ぶPHP",
                    "list_price": 3000,
                    "is_electronic_book": true,
                    "publisher": {
                        "id": 22,
                        "name": "BB出版"
                    }
                }
            }
        ]
    ]
]
```

# 最後に

以上です。何問解けたでしょうか？

コレクションメソッドを使っていて、また何か気付きがあれば練習問題にしてみたいと思います:innocent:

# 参考

- [Laravel Collectionメソッド全実例 - CONSOLE DOT LOG](https://blog.capilano-fw.com/?p=727)