Collectionの中でとても好きなメソッドは、groupBy()です。Laravelが存在しなかった時代ではよくDBクエリから返ってくる1次元の配列をグループ化するため、汚いコードを書いて2次元の配列に変換していたものです。しかし、CollectionのgroupByがあるとコードがシンプルで綺麗に書けること書けること。

このような画面が欲しい

以下のようなデータがDBにあると仮定して、

mysql> select id, name, gender from users;
+----+---------------+-----------+
| id | name          | gender    |
+----+---------------+-----------+
|  1 | 山田 千代     | 無回答    |
|  2 | 佐藤 健一     | 男        |
|  3 | 加藤 篤司     | 無回答    |
|  4 | 山口 花子     | 女        |
+----+---------------+-----------+
4 rows in set (0.01 sec)

このデータを性別でグループ化して、以下の画面の表示となるようにしたいのです。

DBレベルでのデータのグループ化

DBレベルでは、データをグループ化して例えば性別(gender)ごとのユーザー数とかを以下のように取得できます。

>>> User::groupBy('gender')->selectRaw('gender, count(*)')->get();
=> Illuminate\Database\Eloquent\Collection {#3422
     all: [
       App\User {#4345
         gender: "女",
         count(*): 1,
       },
       App\User {#4348
         gender: "無回答",
         count(*): 2,
       },
       App\User {#4349
         gender: "男",
         count(*): 1,
       },
     ],
   }

ちなみに実行されたSQL文は、

>>> sql()
=> [
     [
       "query" => "select gender, count(*) from `users` group by `gender`",
       "bindings" => [],
       "time" => 0.56,
     ],
   ]

しかしこれでは先の欲しい画面のデータを表示するには、さらにそれぞれの性別において個々のDBレコードの情報取得が必要となります。つまり、以下のようなSQLの実行が3回必要となります。

>>> User::where('gender', '=', '男')->get();
=> Illuminate\Database\Eloquent\Collection {#4345
     all: [
       App\User {#4348
         id: 2,
         name: "佐藤 健一",
         gender: "男",
...
       },
     ],
   }

しかし、これではトータル4回のSQL文の実行となってしまい効率的でありません。

Collectionでのグループ化

今度は、DBからレコードを全部取得してからデータをグループ化します。

>>> User::all()->groupBy('gender');
=> Illuminate\Database\Eloquent\Collection {#4336
     all: [
       "無回答" => Illuminate\Database\Eloquent\Collection {#4120
         all: [
           App\User {#3392
             id: 1,
             name: "山田 千代",
...
           },
           App\User {#4361
             id: 3,
             name: "加藤 篤司",
 ...
           },
         ],
       },
       "男" => Illuminate\Database\Eloquent\Collection {#4345
         all: [
           App\User {#4188
             id: 2,
             name: "佐藤 健一",
...
           },
         ],
       },
       "女" => Illuminate\Database\Eloquent\Collection {#4276
         all: [
           App\User {#3403
             id: 4,
             name: "山口 花子",
...
           },
         ],
       },
     ],
   }

DBレベルのグループ化と違って、DBから取得した後にCollectionのgroupBy()を適用なので、データをグループ化するだけでなく個々のデータもコレクションにキープしてくれます。そして、実行されたSQL文をチェックしてみると以下のように1つのみです。

>>> sql()
=> [
     [
       "query" => "select * from `users`",
       "bindings" => [],
       "time" => 0.51,
     ],
   ]

コントローラの作成

CollectionのgroupByを使用して必要な構造データを取得できたところで、コントローラを作成です。

namespace App\Http\Controllers;

use App\User;

class UserController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('users')
            ->with(['genders' => User::all()->groupBy('gender')]);
    }
}

ブレードは以下のように、2つのループ(@foreach)を使い、グループのキーである性別と個々のレコードを分けて表示します。

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">ユーザー性別分け</div>
                <div class="card-body">
                    <table class="table">
                        <thead>
                            <tr>
                                <td class="col-1"></td>
                                <td class="col-1">ID</td>
                                <td>名前</td>
                            </tr>
                        </thead>
                        <tbody>
                        @foreach($genders as $gender => $users)
                            <tr>
                                <td colspan="3">{{ $gender }}</td>
                            </tr>
                            @foreach($users as $user)
                                <tr>
                                    <td></td>
                                    <td class="text-right">{{ $user->id }}</td>
                                    <td>{{ $user->name }}</td>
                                </tr>
                            @endforeach
                        @endforeach
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

これでめでたし欲しかった画面の完成です。

By khino