【laravel】withでカラム指定しつつwhereも使う方法

製作 プログラム

最終更新日:2023/10/18

ラムネグから一言:寝る前に読むとくだらなすぎて逆に寝れると好評なすごい適当なブログをこっちではじめてます.

laravelのwith、便利ですよね。

イーガーロード?っていう言葉自体は横文字苦手なのではよくわかりませんが、ようは紐づくデータを1回のクエリで取得できるから速くなるよー!っていうのがwith。

ここではlaravelのwithで、取得する子テーブルのカラムを指定しつつwhereで絞り込む方法を紹介します。

  1. いきなりやり方
  2. 何にハマったのか
  3. 公式マニュアルの組み合わせだとエラーが出る…
    1. カラム指定の公式マニュアル
    2. whereによる絞り込みの公式マニュアル
    3. 合わせるとエラーが出る
  4. まとめ

いきなりやり方

まずは解決方法から。これでOKです。

この例ではuserが親、postが子、そしてそのユーザーの書いた記事の中からカテゴリが「初心者向け」に設定されいてる記事一覧をwithで取得したい、さらにpostテーブルはカラム指定で「id,title,url」の3カラムのみ取得。という場合です(…長いですね)。

$user = User::where('id', $user_id)->with(['post' => function($query){
	$query->select(['id', 'title', 'url'])->where('category', '初心者向け');
}])->get();

getQueryLogで実際のSQL文も確認しましたが、期待通りのものになっていました。

何にハマったのか

はい、もう解決したので、ここから下は興味がある人だけ読んでみてくださいね。

正直上の記述を見ると特にハマりそうな部分がない、ように見えると思うんです。

ただ、今回laravelでwith使って、カラム指定しつつwhereで条件指定もして…、というのを実装するのにかなりハマりました。

おさらい

withのおさらいをサクッと。

laravelで例えばuserテーブルが親、んでpostテーブルが子として「このユーザーが書いた記事一覧もユーザー情報と一緒に取得したい!」って言うときにwithを使います。

withを使わずに普通にbladeファイルで、

bladeファイル内

@foreach( $user->posts as $post )
<li>{{ $post->title }}</li>
@endforeach

としても表示上は問題なく表示されるんですが、「$post->title」するたびに一回一回SQL文が発行されるんで遅くなるんです。例えばそのユーザーが20記事書いてたら20回SQLが発行されるわけです。

なので…!

Contolloerファイル内

$user = User::where('id', $user_id)->with('post')->get();

からのbladeファイル内で

@foreach( $user->posts as $post )
<li>{{ $post->title }}</li>
@endforeach

と、コントローラー部分でuserデータを取得する際に、withでそのユーザーに紐づく記事情報(post)も最初に全部読みこみを済ませておく、というのがwithです。

こうすることでwithを使わない場合だとpostを読みだすSQLが20回だったのに対し、たった1回のSQLで全部の記事情報を読み込めるんですね。(具体的にはwhereInを使って記事番号を全部まるっと1回で指定してます)

公式マニュアルの組み合わせだとエラーが出る…

そう、laravelの公式ドキュメントってすごい丁寧でわかりやすいんですが、これをそのまま組み合わせるとエラーになるのが今回のケースなんですよね。

https://readouble.com/laravel/10.x/ja/eloquent-relationships.html

カラム指定の公式マニュアル

便利なwithですが、例えば「postのカラム全部必要じゃなくてtitleとurlだけでいいんだけど。create_atとかいらないし…」みたいな感じでwithで取得するカラムを絞りたい事があると思います(特に子テーブルにパスワードカラムが含まれてたりすると心象的にもそういったカラムは除外したい、と思います)。

そういう時のかき方はコレ。

$user = User::where('id', $user_id)->with(['post:id,title,url'])->get();

withでカラム指定する場合「id」は公式マニュアルによると必須らしいので必ず含めてくださいね。

whereによる絞り込みの公式マニュアル

さらにさらに話は進んでいって、「いや今回はpostの中でもcategoryが”初心者向け”のやつだけ一覧にしたいなー」とかなまじwithが便利なものだから、withでやりたいことが増えてくると思います。

これもできて、書き方はコレ。

$user = User::where('id', $user_id)->with(['post' => function($query){
	$query->where('category', '初心者向け');
}])->get();

functionとか出てくると「うぇっ」と反射的に思っちゃいますが、laravel公式のやり方なので仕方ないです。慣れるしかないです。

合わせるとエラーが出る

という事でこの2つを組み合わせたらいいんだな!楽勝!楽勝!と思っていざコードを書いてみると…、

$user = User::where('id', $user_id)->with(['post:id,title,url' => function($query){
	$query->where('category', '初心者向け');
}])->get();

そしてこんなエラーが出る

Call to undefined relationship

結局postに対してwithでカラム指定したタイミングで、もうそのpostはeloquentじゃなくただのコレクションになってるみたい。なのでModelで指定してるリレーション(hasManyとか)の関数が使えなくなってるみたい。

なのでfunctionの中でselectを使ってカラム指定してやろう!というのが今回紹介した方法です。

上でも書きましたが念のためgetQueryLogで実際に発行されたSQLを確認しましたが、期待通りのものとなっていたのでこの方法でOKだと思います。

まとめ

今回はlaravelのwithで、カラム指定しつつさらにwhereで絞り込む時の書き方を紹介しました。

答えを知ってしまえば、とても普通の書き方なんですが、公式ドキュメントを繋げるとまんまと罠にハマる仕様になっているのでけっこう躓きやすい部分じゃないかなーと思います。

参考にしてみてくださいね。

【おしらせ、というか完全なる宣伝】

文体がもうぜんぜん適当すぎてあれだけどものすごい自由に書いてるブログ「檸檬だくだく」もよろしく.寝る前に読める恐ろしくくだらないやつです.

こんなにも一ミリも目を引かれないタイトルを取り扱ってます: ココア20g / ハイチュウとかってさ / なぜ米と小麦を食べようと思ったのかの謎 /