Laravelでクエリ結果を配列で取得する方法

# [Laravel] クエリ結果を配列で取得する方法
最近、Laravelで開発をやっておりまして
DBデータ取得の速度を向上させるためにPDOを使った経験についてメモっておきます。

## 検証環境
- PHP 7.2.13
- Laravel 5.7.19
- MySQL 5.7.24

## 検証方法
Laravelに用意されている[Userモデル](https://github.com/laravel/laravel/blob/v5.7.19/app/User.php)を使って検証を行います。

### [Usersテーブルの作成](https://github.com/laravel/laravel/blob/v5.7.19/database/migrations/2014_10_12_000000_create_users_table.php)
```bash
$ php artisan migrate
```

## tinker上でテスト用のUserデータを作成
```bash
$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.2.13 — cli) by Justin Hileman
>>> 
```

### Userデータの作成
```php:データ10,000件作成
$faker = \Faker\Factory::create('ja_JP');
// テストデータ作成 (数分かかります)
$values = [];
foreach (range(1, 10000) as $_) {
    $values[] = [
        'name'  => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => Carbon\Carbon::now(),
        'password' => \Illuminate\Support\Facades\Hash::make('password'),
        'remember_token' => str_random(10)
    ];
}
// データのbulk insert
\App\User::insert($values);
```

## Laravel側で用意しているEloquentモデルから取得
厳密に言うとクエリ結果を一度Collectionクラスに格納して再度配列に変換する方法です。
大量のデータを取得しようとすると、パフォーマンスが落ちることになります。
ただ、取得したデータがCollectionクラスに格納されているので、データ加工は楽になりますね。

個人的にはWeb上で大量のデータを取得することはサービスの質が落ちる原因になるので、
大量のデータを一括で取得する処理は必要であればバッチやジョブで処理を行うべきかなと思います。

```php:Eloquentモデルを使った例
// select `id`, `name`, `email` from `users` where `id` <= 100 order by `created_at` desc
$builder = \App\User::select(['id', 'name', 'email']);
$builder = $builder->where('id', '<=', 100);
$builder = $builder->orderBy('created_at', 'desc');

$array = $builder->get()->toArray();
```

## PDOを使ってデータを取得
こちらの方法はLaravelに限らず、PHPのPDOを使ったデータ取得方法となります。

PDOフェッチモードのFETCH_ASSOCでクエリを実行すると結果の行を連想配列で取得することができます。
取得の時点でデータが連想配列になっているため、Eloquentモデルのようにデータを再加工が不要です。
ですので、その分Eloquentモデルからのデータ取得よりパフォーマンスは良いです。

以下の例はLaravelフレームワークからPDOを使う方法です。

```php:PDOを使った例
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;

function fetch_assoc($builder): array
{
    $is_builder_type = ($builder instanceof QueryBuilder || $builder instanceof EloquentBuilder);
    if ($is_builder_type === false) {
        throw new \TypeError('$builder is not builder type.');
    }

    $query    = $builder->toSql();
    $bindings = $builder->getBindings();

    $pdo  = \DB::connection()->getPdo();
    $stmt = $pdo->prepare($query);
    $stmt->execute($bindings);

    return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}

/**
 * select `id`, `name`, `email` from `users` where `id` <= 100 order by `created_at` desc
 */
// Eloquentモデルを使ってクエリを発行し、データを配列で取得
$builder = \App\User::select(['id', 'name', 'email']);
$builder = $builder->where('id', '<=', 100);
$builder = $builder->orderBy('created_at', 'desc');
$array   = fetch_assoc($builder);

// クエリビルダを使ってクエリを発行し、データを配列で取得
$builder = \DB::table('users')->select(['id', 'name', 'email']);
$builder = $builder->where('id', '<=', 100);
$builder = $builder->orderBy('created_at', 'desc');
$array   = fetch_assoc($builder);
```

生クエリを直接に発行できず、必ずビルダクラスを使ってクエリを発行するような実装をすることで
クエリの書き方が謝り、SQLインジェクションが生まれることを防止できます。

## 速度テスト
せっかくテストデータを用意したので、上記テストデータ10,000件を全件取得してみることで
Eloquentモデルを使った場合とPDOを使った場合の速度を確認してみました。

| Eloquentモデル | PDO |
|:------------------:|:------------------:|
| 0.35881400108337 ms| 0.18002486228943 ms|
PDOの方が半分くらい早いですね。


## 個人的な感想
PDOを使ってDBデータを配列のデータを取得するのが速度面では良いですが、
配列のデータを加工するのはかなり面倒です。
データ加工はやはりLaravelで提供しているCollectionを使った方が楽だと思います

なので、自分は以下のような方針でバランスよく使い分けをしても良いかなと思います。

- 加工する必要がない量が多いデータはPDOから取得する。
- 加工する必要がある量が少ないデータはEloquentモデルやクエリビルダを使って取得する。
- 加工する必要があり、量が多いデータはバッチを検討し、どうしてもWebでやる場合はPDOの使用を検討。