対象読者
以下の基本的な知識がある人
リレーション
APIリソース
with、whereHasメソッド
実行環境
PHP 8.0.15
Laravel 8.83.1
定番のUser, Postモデルを例に解説していきます。
各モデルのリレーションメソッドは定義済みとします。
通常の書き方
id = 1のユーザー
のユーザーの今日以降の投稿
上記データを同時に取得したいとします。
リレーション先のEager Loadingと検索を同時に行いたいときは、
withとwhereHasを組み合わせます。
$users = User::with('posts')
->whereHas('posts', function ($q) {
$q->where('created_at', '>=', today());
})->find(1);
問題点
うっかりwithを書き忘れたとしましょう。
$users = User::whereHas('posts', function ($q) { // withがない!!!
$q->where('created_at', '>=', today());
})->find(1);
さらに、APIリソースではロードしたときのみpostsを返すようになっていました。
UserResource.php
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'posts' => $this->whenLoaded('posts') // ロードしていたらpostsを返却
];
}
}
Eager Loadingしていないため、本来存在する投稿データがレスポンスから消えてしまいます
(筆者はこれでしばらくハマってしまいました)。
array:1 [
"data" => array:2 [
"id" => 1
"name" => "山田太郎"
// postsがない!
]
]
独自scopeの実装
同時に実行するメソッドがあればこのようなミスは防げます。
Modelに独自scopeを定義しましょう。
User.php
public function scopeWithWhereHas($query, $relation, $constraint)
{
return $query->whereHas($relation, $constraint)
->with([$relation => $constraint]);
}
以下のように書くと、withとwherehasを組み合わせたときと同じ挙動になります。
コード量も減るのですっきりしますね。
$users = User::withWhereHas('posts', function ($q) {
$q->where('created_at', '>=', today());
})->find(1);
postsフィールドもしっかり返却されました。
array:1 [
"data" => array:2 [
"id" => 1
"name" => "山田太郎"
"posts" => array:2 [
0 => array:5 [
"id" => 1
"user_id" => 1
"content" => "投稿1"
"created_at" => "2022-03-16T02:02:21.000000Z"
"updated_at" => "2022-03-16T02:02:21.000000Z"
]
1 => array:5 [
"id" => 2
"user_id" => 1
"content" => "投稿2"
"created_at" => "2022-03-16T02:02:21.000000Z"
"updated_at" => "2022-03-16T02:02:21.000000Z"
]
]
]
]
おわりに
.*ServiceProviderにmacroとして定義すると、実装していないUserモデル以外でも
使えるようになります。より便利ですね。
では!
参考文献
↧