[Laravel 5.7] Eloquent リレーションのクエリまとめ

リレーションにクエリを投げる時、「hasだっけ？whereHasだっけ？」となることがちょくちょくあるので、 `Eloquent: Relationships > Querying Relations` を読んでまとめてみた。

### 公式ドキュメント
https://laravel.com/docs/5.7/eloquent-relationships#querying-relations

### 2018.12.5 追記
5.7 のドキュメントで追記された内容についても記載したので、↑のURLも5.7に変更しました。

## 単純にクエリを投げる

Eloquentの全てのタイプのリレーションはメソド経由で定義され、リレーションにもクエリビルダが使えるのでメソドチェーンでかける。
例えば `User` モデルに `Posts` モデルがリレーションがされている場合、

```php
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get all of the posts for the user.
     */
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}
```

```php
$user = App\User::find(1);

$user->posts()->where('active', 1)->get();
```

こんな感じで `posts()` からさらに制約が追加できる。
もちろん `where` メソド以外のクエリビルダメソドも使えます。

### リレーションが存在しているか、のクエリ

リレーションの存在有無を元にレコードを取りたいときは `has` `orHas` メソドが使える。

```php
// comments がある Post だけをとってくる
$posts = App\Post::has('comments')->get();

// 3つ以上の comments がある Post だけをとってくる
$posts = App\Post::has('comments', '>=', 3)->get();

// ネストされたリレーションも .（ドット） を使って対象にできる
$posts = App\Post::has('comments.votes')->get();
```

もっとカスタマイズしたい場合は `whereHas` `orWhereHas` を使う

```php
$posts = App\Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();
```

### リレーションが存在していない、のクエリ

さっきと逆だが使い方はほぼ同じで、 `doesntHave ` `orDoesntHave ` を使えばよい。

```php
// comments がない Post だけをとってくる
$posts = App\Post::doesntHave('comments')->get();
```

よりカスタマイズしたい場合は `whereDoesntHave ` `orWhereDoesntHave ` 。

```php
$posts = App\Post::whereDoesntHave('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

// ネストされたリレーション
$posts = App\Post::whereDoesntHave('comments.author', function ($query) {
    $query->where('banned', 1);
})->get();
```

### リレーションモデルのレコード数をとる

なんと、「リレーションデータの内容は読み込まず、レコード数だけをとる」という方法がある。（知らなかった）
`withCount` メソドを使って、カラムを `{relation}_count` とするだけ。超簡単。

```php
// comments のレコード数をとる
$posts = App\Post::withCount('comments')->get();

foreach ($posts as $post) {
    echo $post->comments_count;
}

// comments、votes のレコード数をとる
$posts = App\Post::withCount(['votes', 'comments' => function ($query) {
    $query->where('content', 'like', 'foo%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
```

また、レコード数にエイリアスをつけることも可能。

```php
$posts = App\Post::withCount([
    'comments',
    'comments as pending_comments_count' => function ($query) {
        $query->where('approved', false);
    }
])->get();

echo $posts[0]->comments_count;

echo $posts[0]->pending_comments_count;
```

#### `withCount` と `select` を一緒に使うときは順序に注意
5.7 ドキュメントに追記されている内容。
（新機能というより、注意書きとして追記されたと思われる）

**`select` のあとに `withCount` を呼び出してね** と。

```php
$query = App\Post::select(['title', 'body'])->withCount('comments');

echo $posts[0]->title;
echo $posts[0]->body;
echo $posts[0]->comments_count;
```

## まとめ
- リレーションはメソドで定義する
    - リレーションメソドにはクエリビルダが使え、チェーンメソドが書ける
- 「リレーションが存在しているか」の判定には `has` `orHas`
    - もっとカスタマイズしたいときは `whereHas` `orWhereHas`
- 「リレーションが存在していないか」の判定には `doesntHave ` `orDoesntHave `
    - もっとカスタマイズしたいときは `whereDoesntHave ` `orWhereDoesntHave `
- リレーションモデルのレコード数をとるには `withCount` を使って、 `{relation}_count` で取れる
    - エイリアスをつけることも可能
    - `select` と一緒に使うときは順序に注意