Laravel $user->posts()->get() と $user->posts の違いを理解する

# 環境
- PHP 7.2
- Laravel 5.6

# 前提
- ブログサービスを想定、Userが複数のPost（投稿）を持つ（1対多関係）
- Userテーブルはid, name, email, password, created_at, updated_atカラムを持つ
- Postテーブルはid, content, created_at, updated_atカラムを持つ

# リレーションメソッドと動的プロパティ

```php
$user = User::find($id); // ユーザを取得
$posts = $user->posts()->get(); // ユーザが持つ投稿一覧を取得
```

2行目は `user->posts` と書いても同じ結果が得られる。

[https://readouble.com/laravel/5.6/ja/eloquent-relationships.html](https://readouble.com/laravel/5.6/ja/eloquent-relationships.html)

> リレーションが定義できたらEloquentの動的プロパティを使って、関係したレコードを取得できます。動的プロパティによりモデル上のプロパティのようにリレーションメソッドにアクセスできます。

ドキュメントにならって `$user->posts()->get()` をリレーションメソッドを扱ったアクセス、 `$user->posts` を動的プロパティを扱ったアクセスと呼ぶことにする。

筆者はてっきり動的プロパティの記法はリレーションメソッドの省略形と思っていた。

しかしこの後の検証で動作上の違いを認識することができた。

LaravelのREPL環境である `php artisan tinker` を実行する。

```
>>> $user = User::find(5)
=> App\User {#2952
     id: 5,
     name: "test",
     created_at: "2019-02-26 20:12:50",
     updated_at: "2019-02-26 20:12:50",
   }
>>> $user
=> App\User {#2952
     id: 5,
     name: "test",
     created_at: "2019-02-26 20:12:50",
     updated_at: "2019-02-26 20:12:50",
   }
>>> $user->posts()->get()
=> Illuminate\Database\Eloquent\Collection {#2958
     all: [
       App\Post {#2950
         id: 67,
         content: "test",
         user_id: 5,
         created_at: "2019-02-26 20:13:14",
         updated_at: "2019-02-26 20:13:14",
       },
     ],
   }
>>> $user
=> App\User {#2952
     id: 5,
     name: "test",
     created_at: "2019-02-26 20:12:50",
     updated_at: "2019-02-26 20:12:50",
   }
>>> $user->posts
=> Illuminate\Database\Eloquent\Collection {#2955
     all: [
       App\Post {#2942
         id: 67,
         content: "test",
         user_id: 5,
         created_at: "2019-02-26 20:13:14",
         updated_at: "2019-02-26 20:13:14",
       },
     ],
   }
>>> $user
=> App\User {#2952
     id: 5,
     name: "test",
     created_at: "2019-02-26 20:12:50",
     updated_at: "2019-02-26 20:12:50",
     posts: Illuminate\Database\Eloquent\Collection {#2955
       all: [
         App\Post {#2942
           id: 67,
           content: "test",
           user_id: 5,
           created_at: "2019-02-26 20:13:14",
           updated_at: "2019-02-26 20:13:14",
         },
       ],
     },
   }
```

リレーションメソッドはレコードの結果を返す。
動的プロパティもレコードの結果を返す。
違いは、動的プロパティを使った場合はレシーバである `$user` インスタンスに `posts` プロパティが追加される点。

つまり、 `$user->posts` を実行すると `$user` に `posts` 情報がキャッシュされる。
これ以降、 `$user->posts` を実行した場合は、クエリを発行せずにキャッシュされたデータを返すようだ。

# 遅延ロード

この挙動は遅延ローディングとその目的であるN+1問題の解消のために存在しているようだ。

> 動的プロパティは「遅延ロード」されます。つまり実際にアクセスされた時にだけそのリレーションのデータはロードされます。そのため開発者は多くの場合にEagerローディングを使い、モデルをロードした後にアクセスするリレーションを前もってロードしておきます。Eagerロードはモデルのリレーションをロードするため実行されるSQLクエリを大幅に減らしてくれます。

ドキュメントではAuthorとBookの例で説明されている。

```php
$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}
```

`with` でN+1を防止できることは知っていたが、そちらばかりに注目がいっており動的プロパティは雑な理解に留まってしまっていた。

# 結論

まとめると下記のようになる。

- リレーションメソッドによるアクセスは常にクエリ発行する
- 動的プロパティによるアクセスは、キャッシュデータが存在しなければクエリ発行し、レシーバインスタンスにキャッシュとなるプロパティを生成する。キャッシュが存在すればそれを返すためクエリ発行しないという挙動をする

`App\Book::with('author')->get();` で返ってくるBooksコレクションにはその一つ一つにAuthorのデータがキャッシュされている状態になっており、キャッシュデータを利用するには動的プロパティでアクセスしなければならない。

すなわち、

```php
$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author()->first()->name; // リレーションメソッドによるアクセス（NGパターン）
}
```

このようにリレーションメソッドでアクセスすると都度クエリが発行されてしまうため駄目ですよ、遅延ロードの際は動的プロパティを使いましょうという話。

何か誤解があれば指摘頂きたいと思います。