株式会社WR

株式会社WR

WEB TOTAL CONSULTING

Laravel Eloquentをもっと使いこなす——スコープ・リレーション・Eagerロード
ブログ一覧へ
技術ブログ

Laravel Eloquentをもっと使いこなす——スコープ・リレーション・Eagerロード

Laravel EloquentのQueryScope・HasMany/BelongsTo・Eagerローディングを理解することで、N+1問題を防ぎ、読みやすいコードが書けるようになります。

Eloquentとは——LaravelのORM

Eloquentは、LaravelのORM(Object-Relational Mapper)です。テーブルとモデルクラスを紐付け、SQLを直接書かずにオブジェクト指向でデータベース操作ができます。本記事では、基本的なCRUDを超えた実践的なEloquentの使い方を解説します。


ローカルスコープ——クエリの再利用

ローカルスコープを使うと、よく使うクエリ条件をモデルのメソッドとして定義し、どこからでも呼び出せます。

// app/Models/Post.php
class Post extends Model
{
    // scopeXxx という名前のメソッドを定義
    public function scopePublished(Builder $query): Builder
    {
        return $query->where('status', 'published')
                     ->where('published_at', '<=', now());
    }

    public function scopeCategory(Builder $query, string $category): Builder
    {
        return $query->where('category', $category);
    }
}
// 呼び出し側(scope プレフィックスなし)
$posts = Post::published()->category('技術ブログ')->latest()->get();

グローバルスコープ——常に適用されるフィルタ

グローバルスコープはモデルへのあらゆるクエリに自動適用されます。例えば「テナントIDが一致するレコードだけ取得する」マルチテナントシステムに有用です。

class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('tenant_id', auth()->user()->tenant_id ?? 0);
    }
}

// モデルに登録
protected static function booted(): void
{
    static::addGlobalScope(new TenantScope);
}

リレーション——テーブル間の関係

HasOne / HasMany

// 1対多:投稿に対するコメント
class Post extends Model
{
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}

// 取得
$post = Post::find(1);
$comments = $post->comments; // commentテーブルから自動取得

BelongsTo

// コメントから投稿を参照
class Comment extends Model
{
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}

BelongsToMany(多対多)

// 投稿とタグの多対多
class Post extends Model
{
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class, 'post_tag');
    }
}

// タグ付け・取り外し
$post->tags()->attach([1, 2, 3]);
$post->tags()->sync([2, 3]); // 2と3だけにする

Eagerロード——N+1問題の解決

N+1問題はEloquentの最も典型的なパフォーマンス問題です。

// NG:N+1問題が発生(投稿数+1回のクエリ)
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // ← ループのたびにSQLが発行される
}

// OK:withでEagerロード(2回のクエリで完結)
$posts = Post::with('author')->get();
foreach ($posts as $post) {
    echo $post->author->name; // キャッシュ済みのデータを使用
}

ネストしたリレーションのEagerロード

// 投稿→著者→プロフィールまで一括ロード
$posts = Post::with('author.profile')->get();

// 条件付きEagerロード
$posts = Post::with(['comments' => function ($query) {
    $query->where('approved', true)->latest();
}])->get();

withCount——集計値をカラムとして取得

// コメント数を posts.comments_count として取得
$posts = Post::withCount('comments')->orderByDesc('comments_count')->get();

foreach ($posts as $post) {
    echo "{$post->title}: {$post->comments_count}件";
}

アクセサ・ミューテタ——データの加工を一元管理

class User extends Model
{
    // フルネームをアクセサで生成
    protected function fullName(): Attribute
    {
        return Attribute::make(
            get: fn () => "{$this->last_name} {$this->first_name}",
        );
    }

    // パスワードをミューテタで自動ハッシュ化
    protected function password(): Attribute
    {
        return Attribute::make(
            set: fn ($value) => bcrypt($value),
        );
    }
}

まとめ

Eloquentのスコープ・リレーション・Eagerロードを使いこなすと、クエリの再利用性が上がり、N+1問題を防ぎ、コードの見通しが良くなります。弊社では複雑なビジネスロジックをEloquentのモデル層に集約し、コントローラーをシンプルに保つ設計を心がけています。

LaravelによるWebシステム開発のご相談はお気軽にどうぞ。

Category 技術ブログ

Related Posts

関連記事

開発・技術のご相談はお気軽に

お見積りは無料です。まずはお気軽にご相談ください。

お問い合わせ →