Ship Logo Ship Logo
2025/04/01

Laravel Eloquent ORMの基本から高度な活用まで

author picture

伊東夏子

/

エンジニア

業務で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;

ユーザーとプロフィールの1対1リレーション

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;

ユーザーと投稿の1対多リレーション

多対多のリレーション (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://wingdoor.co.jp/blog/laravel%E3%81%AE%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E6%95%B4%E7%90%86%EF%BC%9A1%E5%AF%BE1%E3%81%AE%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%80%90hasone%E3%81%A8belon/

https://qiita.com/fujita-goq/items/afd4307e90daf95c4f14

https://blog.shonansurvivors.com/entry/laravel-pivot

https://qiita.com/morishi46rui/items/70a667d1e4c10f0f19e2

SHARE ON ❤️