業務でLaravelでの開発を行っているので自分へのインプットもこめてこのテーマで書かせていただきます。 2回目の登板になります伊東です!
今回の投稿ではLaravel Eloquent ORMの基本的な内容から高度なリレーションシップとカスタムクエリ・スコープを組み合わせ、より複雑なデータ操作を効率的に行う方法を紹介していきます。
1. そもそもLaravel Eloquent ORM とは
Eloquent ORM (Object-Relational Mapping) は、Laravel に標準で搭載されているデータベース操作ライブラリです。データベースのテーブルを PHP のオブジェクトとして扱い、SQL を直接記述することなく、直感的なコードでデータベース操作を行えます。
Eloquent ORM の主な特徴は以下です。
- モデル:
- データベースのテーブルを表現する PHP クラスです。各テーブルに対応するモデルを作成し、テーブルのレコードをオブジェクトとして操作します。
- リレーションシップ:
- データベースのテーブル間の関連性を表現する機能です。例えば、ユーザーと投稿、投稿とコメントといった関連を定義し、関連するデータを簡単に取得できます。
- クエリビルダ:
- 直感的なメソッドチェーンで SQL クエリを生成する機能です。where、orderBy、limit などのメソッドを組み合わせて、複雑なクエリも容易に記述できます。
- スコープ:
- 頻繁に使うクエリを再利用可能な形で定義する機能です。例えば、「アクティブなユーザーのみを取得する」といったクエリをスコープとして定義し、複数の場所で再利用できます。
- アクセサとミューテータ:
- モデルの属性の取得・設定時に値を変換する機能です。例えば、データベースに保存された日付をフォーマットして表示したり、パスワードをハッシュ化して保存したりできます。
まずは基本の内容から
2. クエリビルダについて
Eloquent ORM のクエリビルダは、データベースクエリを構築するためのツールです。SQL を直接記述する代わりに、PHP のメソッドチェーンを使ってクエリを組み立てることができます。これにより、コードの可読性が向上し、SQL インジェクションのリスクも低減されます。
クエリビルダの主な機能は以下です!
- 基本的なクエリ:
- select、where、orderBy、limit、offsetなどのメソッドを使って、基本的な SELECT クエリを構築できます。
- 集計クエリ:
- count、sum、avg、max、min などのメソッドを使って、集計関数を適用したクエリを構築できます。
- 結合クエリ:
- join、leftJoin、rightJoin などのメソッドを使って、複数のテーブルを結合したクエリを構築できます。
- グループ化クエリ:
- groupBy、having などのメソッドを使って、結果をグループ化するクエリを構築できます。
- 挿入、更新、削除クエリ:
- insert、update、delete などのメソッドを使って、データの挿入、更新、削除を行うクエリを構築できます。
クエリビルダは、Eloquent モデルの query()
メソッドを使ってインスタンスを取得します。
$query = User::query();
この $query
変数を使って、クエリを組み立てていきます。
下記は、クエリビルダを使ってデータベースからデータを取得する方法を示しています。where、orderBy、paginate などのメソッドを組み合わせることで、より複雑なクエリを構築することも可能です。
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function index()
{
// 全てのユーザーを取得
$users = User::all();
// 特定の条件でユーザーを取得
$users = User::where('status', 'active')->get();
// 特定の条件で最初のユーザーを取得
$user = User::where('status', 'active')->first();
// 特定のIDのユーザーを取得
$user = User::find(1);
// ユーザーをIDで検索して見つからない場合は例外を発生させる
$user = User::findOrFail(1);
// ユーザーをページネーションで取得
$users = User::paginate(15);
// ユーザーを名前で昇順に取得
$users = User::orderBy('name', 'asc')->get();
// ユーザー数を取得
$count = User::count();
return view('users.index', compact('users', 'count'));
}
}
3. リレーションシップについて
Eloquent ORM では、一般的なデータベースのリレーションシップを簡単に定義できます。
1対1のリレーション (hasOne/belongsTo)
一つのモデルが、別のモデルと1対1で関連付けられる場合に使用します。
例)ユーザーとプロフィール
// User モデル
public function profile()
{
return $this->hasOne(Profile::class);
}
// Profile モデル
public function user()
{
return $this->belongsTo(User::class);
}
// ユーザーのプロフィールを取得
$user->profile;
// プロフィールのユーザーを取得
$profile->user;
hasOneとbelongsToの使い分け
- hasOne:
- 親モデルから子モデルへのアクセスに使用します。
- 「私は1つの〇〇を持っている」という関係を表します。
- 関連先のテーブルに外部キーが存在します。
- belongsTo:
- 子モデルから親モデルへのアクセスに使用します。
- 「私は〇〇に属している」という関係を表します。
- 自分のテーブルに外部キーが存在します。
1対多のリレーション (hasMany/belongsTo)
一つのモデルが、別のモデルと1対多で関連付けられる場合に使用します。
例)ユーザーとブログの投稿
// User モデル
public function posts()
{
return $this->hasMany(Post::class);
}
// Post モデル
public function user()
{
return $this->belongsTo(User::class);
}
// ユーザーの投稿を取得
$user->posts;
// 投稿のユーザーを取得
$post->user;
多対多のリレーション (belongsToMany)
一つのモデルが、別のモデルと多対多で関連付けられる場合に使用します。
例)ブログの投稿とタグ
// Post モデル
public function tags()
{
return $this->belongsToMany(Tag::class);
}
// Tag モデル
public function posts()
{
return $this->belongsToMany(Post::class);
}
// 投稿のタグを取得
$post->tags;
// タグの投稿を取得
$tag->posts;
ここからが応用編
4. 高度なリレーションシップ(多対多とポリモーフィック)
今回は多対多リレーションシップとポリモーフィックリレーションシップの高度な使い方を紹介します。
多対多リレーションシップのカスタマイズ
多対多リレーションシップでは、中間テーブルを使って関連テーブルを紐付けます。pivot プロパティを使うと、中間テーブルのカスタム属性にアクセスできます。
例)Laravelで投稿(Post)とタグ(Tag)の多対多リレーションシップを実装し、中間テーブルからタグ付け日時も取得
withPivotメソッドで中間テーブルの追加カラムにアクセスできるようにしています。
// Post モデル
public function tags()
{
return $this->belongsToMany(Tag::class)->withPivot('created_at');
}
// 投稿に紐づくタグとその作成日時を取得
$post->tags->each(function ($tag) {
echo $tag->name . ' - ' . $tag->pivot->created_at;
});
Post::tags()
:Post
モデルに関連付けられたTag
モデルのコレクションを取得します。->each(function ($tag) { ... })
: 取得したTag
モデルのコレクションをループ処理します。$tag->pivot->created_at
: 中間テーブル (post_tag
) のcreated_at
属性にアクセスします。
※pivot プロパティとは
- 多対多リレーションシップにおいて、中間テーブルの属性にアクセスするためのプロパティです。
- 中間テーブルには、通常、関連する2つのテーブルのIDが保存されますが、追加の属性(例:作成日時、関連のタイプなど)を保存することもできます。
もし、pivotプロパティを使わない場合…
記述の一例になりますが、クエリビルダで直接取得する方法になります。記述的には割とシンプルですが、標準オブジェクト扱いになるためEloquentモデルの機能が使用できなくなります。
// Post モデル - リレーションシップは通常通り定義
public function tags()
{
return $this->belongsToMany(Tag::class);
}
// 使用例 - クエリビルダで直接結合して取得
$postId = $post->id;
$tagsWithCreatedAt = DB::table('tags')
->join('post_tag', 'tags.id', '=', 'post_tag.tag_id')
->where('post_tag.post_id', $postId)
->select('tags.name', 'post_tag.created_at')
->get();
foreach ($tagsWithCreatedAt as $tag) {
echo $tag->name . ' - ' . $tag->created_at;
}
この方法以外にも長短はありますが、中間テーブルをカスタムモデルとして扱ったり、中間テーブルを含む複雑なリレーションシップを定義するなどの代替方法があります。
ポリモーフィックリレーションシップの活用
ポリモーフィックリレーションシップを使うと、一つのモデルが複数の異なるタイプのモデルと関連付けられます。
例)コメントモデルを投稿や動画に関連付ける
// Comment モデル
public function commentable()
{
return $this->morphTo();
}
// Post モデル
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
// Video モデル
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
// 投稿に関連するコメントを取得
$post->comments;
// 動画に関連するコメントを取得
$video->comments;
// コメントが関連付けられているモデルを取得
$comment->commentable;
Comment::commentable()
: Comment モデルに関連付けられたモデル(Post または Video)を取得します。$post->comments
: 投稿に関連するコメントを取得します。$video->comments
: 動画に関連するコメントを取得します。
5. カスタムクエリとスコープ:より柔軟なデータ取得
カスタムクエリとスコープを使うと、データ取得をより柔軟に行えます。
カスタムクエリによる複雑な条件指定
whereHas や whereDoesntHave を使うと、リレーションシップに基づいた条件指定が可能です。
// タグが3つ以上紐づく投稿を取得
Post::whereHas('tags', function ($query) {
$query->havingRaw('count(*) >= 3');
})->get();
Post::whereHas('tags', function ($query) { ... })
:Post
モデルに対して、tags
リレーションシップに関連する条件を指定します。function ($query) { ... }
:tags
のデータを取得する際の条件を細かく設定できる「無名関数」です。この部分を使うと、「特定の名前のタグだけ取得する」「追加日時で並べ替える」など、データ取得方法をカスタマイズできます。$query->havingRaw('count(*) >= 3')
:tags
リレーションシップの数をカウントし、3つ以上の場合に絞り込みます。->get()
: 絞り込まれたPost
モデルのコレクションを取得します。
スコープによるクエリの再利用
スコープを使うと、頻繁に使うクエリをモデル内で定義し、再利用できます。
// Post モデル
public function scopePopular($query)
{
return $query->where('likes', '>=', 100);
}
// 人気のある投稿を取得
Post::popular()->get();
- public function scopePopular($query):
- Post モデル内に定義されたローカルスコープです。
- scopePopular という名前のスコープを定義しており、このスコープは「人気のある投稿」を取得するためのクエリを組み立てます。
- $query はクエリビルダのインスタンスであり、これに対して条件を追加していくことでクエリを構築します。
- return $query->where('likes', '>=', 100);:
- この行がスコープの主要な部分です。
- $query->where('likes', '>=', 100) は、「likes」カラムの値が 100 以上であるレコードを抽出するという条件をクエリに追加します。
- つまり、このスコープは「いいね!」の数(
likes
)が 100 以上の投稿を抽出するための条件を定義しています。
- Post::popular()->get();:
- スコープの呼び出し部分です。
- Post::popular() は、先ほど定義した
popular
スコープを適用します。 - get() は、スコープが適用されたクエリを実行し、結果をコレクションとして取得します。
- 結果として、「いいね!」の数が 100 以上の投稿がコレクションとして返されます。
6. 高度なリレーションシップとカスタムクエリの組み合わせ
高度なリレーションシップとカスタムクエリを組み合わせると、より複雑なデータ取得が可能です。
// タグが3つ以上紐づき、かつ人気のある投稿を取得
Post::whereHas('tags', function ($query) {
$query->havingRaw('count(*) >= 3');
})->popular()->get();
Post::whereHas('tags', function ($query) { ... })
: tags リレーションシップに関連する条件を指定します。function ($query) { ... }
: tags リレーションシップに対するクエリを構築するためのクロージャです。$query->havingRaw('count(*) >= 3')
: tags リレーションシップの数をカウントし、3つ以上の場合に絞り込みます。popular()
: popular スコープを適用します。get()
: 絞り込まれた Post モデルのコレクションを取得します。
プラスα
7. Eloquent ORM を使用しない場合との比較
Eloquent ORM を使用しない場合、データベース操作は主に以下の方法で行われます。
- 生の SQL クエリ:
DB::select()
、DB::insert()
、DB::update()
、DB::delete()
などのメソッドを使用して、生の SQL クエリを実行します。
- クエリビルダのみを使用:
- Eloquent ORM のモデルを使用せず、クエリビルダのみを使用してデータベース操作を行います。
Eloquent ORM を使用しない場合の長所:
- パフォーマンス:
- 生の SQL クエリは、Eloquent ORM のオーバーヘッドがないため、パフォーマンスが向上する場合があります。
- 柔軟性:
- 生の SQL クエリは、複雑なクエリや特殊なデータベース機能を使用する場合に、より柔軟に対応できます。
Eloquent ORM を使用しない場合の短所:
- コードの複雑さ:
- 生の SQL クエリは、コードが冗長になりやすく、可読性や保守性が低下する可能性があります。
- SQL インジェクションのリスク:
- 生の SQL クエリは、適切にエスケープ処理を行わないと、SQL インジェクションのリスクが高まります。
- 開発効率:
- Eloquent ORM の便利な機能(リレーションシップ、スコープなど)を使用できないため、開発効率が低下する可能性があります。
Eloquent ORM を使用すべきケース:
- 一般的な CRUD 操作や、複雑でないクエリを行う場合。
- コードの可読性や保守性を重視する場合。
- 開発効率を重視する場合。
生の SQL クエリを使用すべきケース:
- パフォーマンスが非常に重要な場合。
- 複雑なクエリや特殊なデータベース機能を使用する必要がある場合。
8. まとめ
Laravel Eloquent ORMは、データベース操作をオブジェクト指向的に行うための強力なツールです。基本的なCRUD操作から高度なリレーションシップ、カスタムクエリ、スコープまで、様々な機能を提供されています。
これらの機能を適切に組み合わせることで、複雑なデータベース操作を効率的に行うことができます。また、コードの可読性や保守性も向上し、開発効率も高まります。
Eloquent ORMの機能を活用することで、より効率的で柔軟なデータベース操作を意識してこれからも開発業務を行っていきます!!
最後まで読んでいただきありがとうございました◎
※ 本記事のコード例はLaravel 8以降を前提に記述しています。Laravel 7以前では一部記法が異なる場合があります。
参考資料
https://qiita.com/hitochan/items/9cd9f2cbbbdd35916a96
https://qiita.com/fujita-goq/items/afd4307e90daf95c4f14