1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

withの後に起こることとリレーション動的プロパティは実際何をしているのか

Posted at

概要

Eagerローディングについて整理しつつ、動的プロパティが何をしているのか追ってみた。

Eagerローディングについて振り返る

Eagerローディング

N+1クエリ問題の解決法として使われる。

  • 1+ループ回数分のクエリが発生する

     $articles = Article::all();
    
     foreach ($articles as $article) {
     	$comment = $article->comment;
     }
    
  • 2つのクエリで済む

     $articles = Article::with('comment')->get();
    
     foreach ($articles as $article) {
     	$comment = $article->comment;
     }
    

取得するリレーション先に対して、制約をかけることも出来る。

$articles = Article::with([
    'tags' => function ($query) {
        $query->where('slug', 'like', '%business%');
    }
])->find(1);

リレーション先のデータの取得

Eagerロード実行後はクエリビルダを用いてリレーション先データを取得することが出来ない。

例として、制約をかけたEagerロード済みのクエリを、クエリビルダと動的プロパティで取得した結果を dd() で確認して比較する。

$articles = Article::with([
    'tags' => function ($query) {
        $query->where('slug', 'like', '%business%');
    }
])->find(1);

dd([$articles->tags, $articles->tags()->get()]);

クエリビルダで取得すると制約がかかっていない(Eagerロードされていない)データが取れる。

結果
array:2 [▼
  0 => Illuminate\Database\Eloquent\Collection {#383 ▼
    #items: array:2 [▶]
  }
  1 => Illuminate\Database\Eloquent\Collection {#367 ▼
    #items: array:4 [▶]
  }
]

さらに制約をかけたい時

動的プロパティで取得するとコレクションとして返ってくるので、コレクションの操作で制約をかける。

$tags = $articles->tags;

$tags = $tags->filter(function ($tag) {
    return $tag->id > 2;
});

なぜ動的プロパティでしか取得出来ないのか

動的プロパティについての公式ドキュメントの記述

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

クエリビルダで取得する時

belongsToMany() について追ってみたところ BelongsToManyをnewしていた。

/**
 * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
 */
public function tags()
{
    return $this->belongsToMany('App\Models\Tag');
}

動的プロパティでデータを取得する時

呼び出したプロパティが既にロードされているリレーションであれば、新しくロードしない。
つまり with で登録された(ロードされた)リレーションシップであるのならばそれを使う。

Illuminate\Database\Eloquent\Concerns\HasAttributes
/**
 * Get a relationship.
 *
 * @param  string  $key
 * @return mixed
 */
public function getRelationValue($key)
{
    // If the key already exists in the relationships array, it just means the
    // relationship has already been loaded, so we'll just return it out of
    // here because there is no need to query within the relations twice.
    if ($this->relationLoaded($key)) {
        return $this->relations[$key];
    }

    // If the "attribute" exists as a method on the model, we will just assume
    // it is a relationship and will load and return results from the query
    // and hydrate the relationship's value on the "relationships" array.
    if (method_exists($this, $key)) {
        return $this->getRelationshipFromMethod($key);
    }
}

If the key already exists in the relationships array, it just means the relationship has already been loaded, so we'll just return it out of here because there is no need to query within the relations twice.

プロパティ名がリレーションシップ配列に存在する場合、既にロードされているということであり、2度リレーションシップを実行する必要がないのでここから返す。

そのため動的プロパティで呼び出す必要がある。

おわり

コードを追ってみることで、動的プロパティが実際何をしているのか知れてよかった。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?