mcpField
E
eloquent-best-practices

eloquent-best-practices

github.com/iserter開発481.3k 回インストール27 週間前に更新
リポジトリ

eloquent-best-practices について

eloquent-best-practices は、Laravel 開発者向けの Eloquent 実践ガイドスキルです。クエリ効率、リレーション、安全な更新、大規模データ処理を扱います。

主な機能

  1. 01リレーションの先読みと列の選択
  2. 02再利用可能なクエリスコープ
  3. 03リレーションの戻り値型と件数
  4. 04一括代入対策と型キャスト
  5. 05チャンク処理とデータベース更新
  6. 06インデックスと遅延読み込みの確認

活用シーン

  1. 01モデル処理での N+1 クエリ削減
  2. 02メモリを抑えた大規模データ処理
  3. 03モデルの安全性と型処理の点検
  4. 04インデックスによるクエリ性能改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/iserter/laravel-claude-agents --skill eloquent-best-practices

インストール後、Claude Code で/eloquent-best-practicesを入力して読み込まれたか確認します。

詳細

Eloquent のベストプラクティス

クエリの最適化

リレーションを常に Eager Load する
// ❌ N+1 Query Problem
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name; // N additional queries
}

// ✅ Eager Loading
$posts = Post::with('user')->get();
foreach ($posts as $post) {
    echo $post->user->name; // No additional queries
}
必要なカラムだけを選択する
// ❌ Fetches all columns
$users = User::all();

// ✅ Only needed columns
$users = User::select(['id', 'name', 'email'])->get();

// ✅ With relationships
$posts = Post::with(['user:id,name'])->select(['id', 'title', 'user_id'])->get();
クエリスコープを使用する
// ✅ Define reusable query logic
class Post extends Model
{
    public function scopePublished($query)
    {
        return $query->where('status', 'published')
                    ->whereNotNull('published_at');
    }
    
    public function scopePopular($query, $threshold = 100)
    {
        return $query->where('views', '>', $threshold);
    }
}

// Usage
$posts = Post::published()->popular()->get();

リレーションのベストプラクティス

戻り値の型を定義する
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
    
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}
件数には withCount を使用する
// ❌ Triggers additional queries
foreach ($posts as $post) {
    echo $post->comments()->count();
}

// ✅ Load counts efficiently
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
    echo $post->comments_count;
}

マスアサインメントを保護する

class Post extends Model
{
    // ✅ Whitelist fillable attributes
    protected $fillable = ['title', 'content', 'status'];
    
    // Or blacklist guarded attributes
    protected $guarded = ['id', 'user_id'];
    
    // ❌ Never do this
    // protected $guarded = [];
}

型の安全性にはキャストを使用する

class Post extends Model
{
    protected $casts = [
        'published_at' => 'datetime',
        'metadata' => 'array',
        'is_featured' => 'boolean',
        'views' => 'integer',
    ];
}

大規模なデータセットにはチャンク処理を使用する

// ✅ Process in chunks to save memory
Post::chunk(200, function ($posts) {
    foreach ($posts as $post) {
        // Process each post
    }
});

// ✅ Or use lazy collections
Post::lazy()->each(function ($post) {
    // Process one at a time
});

データベースレベルの操作

// ❌ Slow - loads into memory first
$posts = Post::where('status', 'draft')->get();
foreach ($posts as $post) {
    $post->update(['status' => 'archived']);
}

// ✅ Fast - single query
Post::where('status', 'draft')->update(['status' => 'archived']);

// ✅ Increment/decrement
Post::where('id', $id)->increment('views');

モデルイベントを適切に使用する

class Post extends Model
{
    protected static function booted()
    {
        static::creating(function ($post) {
            $post->slug = Str::slug($post->title);
        });
        
        static::deleting(function ($post) {
            $post->comments()->delete();
        });
    }
}

避けるべきよくある落とし穴

ループ内でクエリを実行しない
// ❌ Bad
foreach ($userIds as $id) {
    $user = User::find($id);
}

// ✅ Good
$users = User::whereIn('id', $userIds)->get();
インデックスを忘れない
// Migration
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->index();
    $table->string('slug')->unique();
    $table->string('status')->index();
    $table->timestamp('published_at')->nullable()->index();
    
    // Composite index for common queries
    $table->index(['status', 'published_at']);
});
開発環境では遅延読み込みを防ぐ
// In AppServiceProvider boot method
Model::preventLazyLoading(!app()->isProduction());

チェックリスト

  • 必要な箇所でリレーションを Eager Load している
  • 必要なカラムだけを選択している
  • 再利用性のためにクエリスコープを使用している
  • マスアサインメント保護を設定している
  • 適切なキャストを定義している
  • 外部キーとクエリ対象のカラムにインデックスを設定している
  • 可能な場合はデータベースレベルの操作を使用している
  • 大規模なデータセットにチャンク処理を使用している
  • モデルイベントを適切に使用している
  • 開発環境で遅延読み込みを防いでいる

コメント