nanisore oishisou

Webエンジニアあるまさんのゆるふわ奮闘記。

いろんなEager Loading(1)ひみつ道具編

こんばんは。ベル子です。
今は、夜中の2時です。眠いです。

1時くらいから書き始めたのですが、本当に無関係なことを書き出したら、話の収集がつかなくなったので、もう一度はじめから出直すことにしました。
人生と違ってブログはいつでも出直せるからいいですね。

さて、関係ない話で始まりましたが今日はEager Loadingのお話です。

突然ですが皆さんは以下のどれ派ですか?

1. Eager Loading
2. Eagerローディング
3. イーガーローディング
4. 事前に一括で関連データをロードしてくれるやつ
5. Eagerとか言わずに、要するに『先行ローディング』でよくないですか
6. Eager Loading??何それこわい。でも美味しいんですよね!知ってます。

私はですね、断然6番派だったんですよ。
Eager Loadingとか難しい名前がついていますが、分かってしまえば簡単です。
全国中学校放送コンテスト東京地区大会アナウンス部門第3位にして、N◯K評価が出場者の中で最も高かったという武勇伝を持つ放送部部長、兼、演劇部副部長の私ですからね。
小難しい名前の得たいの知れない機構すらも、ほんの4ヶ月くらいで理解できます。

私のように、内気だけど好奇心旺盛な初心者のために、分かりやすく説明したいと思います。
LaravelのORM(オブジェクト関係マッパー)であるEloquantを使うとデータベースtable同士の関係を定義することができます。


class User extends Eloquent {

    public function articles()
    {
        return $this->hasMany('Article');
    }

}

ユーザーがログインして記事を書くようなアプリの場合、ユーザーにはたくさんの記事が紐づきます。articlesというテーブルの行には同じuser_idが何回も出てくるような関係です。

このように関係を定義すると

$articles = User::find(1)->articles;

こんなふうに、usersテーブルのid=1の人が書いたarticlesテーブルのデータ全件が取得できます。

ここで私のようにphpオブジェクト指向フレームワークも人としての成熟度も初級でピヨピヨの人は、こんな疑問が湧くことでしょう。
メソッドで定義したのに、なぜプロパティでアクセスできるの?

そこのピヨ子さん、いい質問ですね!!
これは『動的プロパティ』といって、↓のように書くべきところを、上記のように書けばデータを取ってきてくれるというものです。

$articles = User::find(1)->articles()->get();

ピヨ子「省略してるだけじゃん、どこが動的なの?」

動的プロパティが賢いのは、関係してるデータが複数のデータなのか1つのデータなのかを判断して自動的にget()かfirst()でデータを取ってきてくれるんです。
分かったか、ピヨ子よ。

例えば、ユーザーの住所を別テーブルで管理したいような場合、
ユーザーアカウントのテーブルとユーザーの住所を保管するテーブルとの関係は1対1です。
その場合は、hasOneというメソッドで関係を定義できます。

    public function address()
    {
        return $this->hasOne('Address');
    }

そして以下のように書くと

$address = User::find(1)->address;

以下のようなデータを取ってきてくれます。

$address = User::find(1)->address()->first();

動的プロパティのさらに便利なところは、こんなふうにテーブルのカラムデータを取ってこれるところです。

$zip_code = User::find(1)->address->zip_code;

以下のように書くのと同義です。ずいぶん見やすいですよね。

$zip_code = User::find(1)->address()->first()->zip_code;

しかし、便利だけど混乱しやすいのが、この動的プロパティで

User::find(1)->articles->title;

としても記事のタイトルは取れません。

なぜかというと、記事は複数あるので、動的プロパティでデータを取得すると、get()したときと同じようにCollectionという配列をラップしたあの例のやつが返ってくるからです。
例のやつが返ってくるということは、以下のようにすれば、ちゃんとカラムのデータを取ってこれます。

$user = User::find(1);

foreach($user->articles as $article) {

    $title =  $article->title;

}

簡単ですね!
ちなみにメソッドで終わらせるとクエリが返ってくるようなので、気をつけてください。

$user = User::find(1)->address();
↑こういう終わり方をするとデータではなくクエリが返ってきます。

あと、このリレーションメソッドのメソッド名には(_)アンスコを含まないほうがいいです。
メソッド名にアンスコを含めると動的プロパティでのアクセスができなくなります。
メソッド名でクエリをつなげる場合はアンスコを含んでいても大丈夫ですが、リレーションメソッドとしての使い勝手が悪いので、リレーションメソッドにはアンスコは使えないと覚えたほうがいいでしょう。
メソッド名をキャメルケースで定義した場合、動的プロパティでアクセスする際はスネークケースでもキャメルケースでもアクセスはできます。

some_documents() ←これだとダメ
someDocuments() ←これだとOK
User::find(1)->some_documents ←これでもアクセスできる!
User::find(1)->someDocuments ←これは、もちろんOK 


さて、前置きが長くなりましたが、ここからが本題です!

hasManyの関係のときに下記のようなコードを書いた場合、
11本の記事があったとすると12回のクエリが走ってしまいます。
1回目はユーザーに紐づく記事を取得するクエリで
それ以外の11回は、それぞれの記事に紐づくタイトルを取得するクエリです。

foreach($user->articles as $article) {

    $title =  $article->title;

}


これがいわゆるN+1問題とかいうやつです。
初めて読んだときは全く意味が分からなかったのですが、コードを書いてデバッガーでクエリを見てみるとよく分かります。
viewに何か大量なデータを渡して、viewの中でforeachを回してリレーションしているデータを取得してみてください。

ごらん、同じクエリが何回も登場しているよ。
1000件のデータがあったら1000件分、同じクエリが登場しているよ。

そしてあなたは思うはずだ。
同じこと何回もするのは、やっぱり効率が悪いんじゃなかろうか。
どうにかならんの?これ。
ドーラーえーもーーーん。

ええ、そういうときに使うのが、Eager Loadingです!!テッテレッテテテーテッテー!

いや〜ちょっと長くなってしまいました。
本題に辿りつけませんでしたが、さすがに眠くてたまらないので、続きは次回にしましょう!
次回は、Eager Loadingのあんなパターンやこんなパターン、さらにムフフなパターンまでご紹介します。

お楽しみにね♥