こんにちは、Morimotoです。
Laravelで開発をしていて、「リレーション先の件数が何件あるかだけ知りたいな…」ということがあり、方法を調べるとwithCount()
メソッドなるものがあることを知りました。
withCount()
メソッドをクエリに追加するだけで、簡単にリレーション先の件数が取得できちゃいます。
ということで今回も自分の備忘録と知識定着のために、Laravelでのデータベース操作をより効率的で読みやすくしてくれる withCount()
メソッド について解説します。
withCount() の使い方
withCount()
メソッドの使い方は非常にシンプルです。
今回は例として、Userモデルに紐づく投稿(Post)の件数を取得するという場面で考えてみましょう。
「ユーザー」と「投稿」が 1対多 の関係となっており、「1人のユーザーが複数の投稿を持つことができる」という関係性です。
「投稿」がリレーション先ということになります。
基本的な使い方
withCount()
の引数には、件数を取得したいリレーションメソッドの名前を指定します。
// Userモデル
class User extends Authenticatable
{
// Userは複数のPostを持つ
// メソッド名の「posts」がwithCount()メソッドの引数になる
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
上記のUser
モデルに定義されたposts
リレーションを使って、各ユーザーの投稿数を取得してみましょう。
// Userモデルに紐づく投稿(posts)の件数を取得
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo "{$user->name} の投稿数: {$user->posts_count} 件<br>";
}
リレーションメソッド名が posts
であれば、自動的に posts_count
という名前のプロパティがモデルに追加されます。このプロパティに件数が格納されています。
複数のリレーションの件数を同時に取得
複数のリレーションの件数を取得したい場合は、配列で指定します。
// Userモデルに紐づく投稿(posts)とコメント(comments)の件数を取得
$users = User::withCount(['posts', 'comments'])->get();
foreach ($users as $user) {
echo "{$user->name} の投稿数: {$user->posts_count} 件<br>";
echo "{$user->name} のコメント数: {$user->comments_count} 件<br>";
}
カウントの条件を指定する
特定の条件を満たす件数だけを取得したい場合は、クロージャ(無名関数)を使用します。
// Userモデルに紐づく、公開済み投稿の件数を取得
$users = User::withCount(['posts' => function ($query) {
// 公開済みの投稿のみをカウント
$query->where('status', 'published');
}])->get();
foreach ($users as $user) {
echo "{$user->name} の公開済み投稿数: {$user->posts_count} 件<br>";
}
なぜ withCount() を使うのか?
withCount()
を使わない場合と使う場合を比較して、そのメリットを見ていきましょう。
❌ 悪い例: count() を使う場合
もし、各ユーザーの投稿数を表示したい場合に以下のようなコードを書いたとします。
$users = User::all();
foreach ($users as $user) {
// ループ内で都度クエリが実行される
$count = $user->posts()->count();
echo "{$user->name} の投稿数: {$count} 件<br>";
}
このコードの問題点は、foreach
ループが回るたびに、データベースへの問い合わせ(クエリ)が実行されてしまうことです。ユーザーが100人いれば、クエリも100回実行されることになり、パフォーマンスが大幅に低下してしまいます。
これは「N+1問題」と呼ばれる、Laravel開発で避けるべき典型的なアンチパターンです。
✅ 良い例: withCount() を使う場合
withCount()
を使うと、以下のようにシンプルに書くことができます。
// withCount() を使って、一回のクエリで件数を取得
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo "{$user->name} の投稿数: {$user->posts_count} 件<br>";
}
withCount('posts')
を指定するだけで、Laravelはたった1つのクエリで、すべてのユーザーとそれに紐づく投稿の件数を同時に取得してくれます。これにより、サーバーへの負荷を劇的に減らすことができます。
withCount() で追加されるプロパティ名について
「基本的な使い方」のところで「リレーションメソッド名が posts
であれば、自動的に posts_count
という名前のプロパティがモデルに追加されます。」と記載した通り、withCount()
で追加されるプロパティ名は、Laravelの命名規則に従って自動で決まります。
リレーション名が camelCase
(キャメルケース)で書かれていても、プロパティ名は自動的に snake_case
(スネークケース)に変換され、末尾に _count
が付与されます。
リレーションメソッド名 | 追加されるプロパティ名 |
---|---|
posts | posts_count |
comments | comments_count |
userProfile | user_profile_count |
これは、PHPコード(キャメルケース)とデータベース(スネークケース)の慣例を統一するための、Laravelの「おもてなし」機能です。
キャメルケースの例のように、リレーションメソッド名に_count
を付けるだけではない場合があることに注意しましょう。
まとめ
withCount()
は、リレーション先の件数を効率的に取得するための必須メソッドです。withCount()
を使えば、データベースへの問い合わせを1回に抑え、パフォーマンスを改善できます。- 引数にリレーション名を指定するだけで、基本的に
{リレーション名}_count
というプロパティがモデルに追加されます。 - 複数のリレーションや、特定の条件でのカウントも可能です。
まだ使ったことがなかった方は、ぜひ今日からあなたのLaravelプロジェクトで試してみてください。コードがよりスマートで効率的になりますよ!
それでは。