この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:11
ページ更新者:爽健
更新日時:2026-06-11 07:07:02

タイトル: Eloquentモデル (ORM)
SEOタイトル: Laravel Eloquent ORM 入門 — モデル定義/CRUD/リレーション/Eager Loading 総まとめ

この記事の要点
  • Eloquent は Laravel 標準の Active Record 系 ORM。class User extends Model 1 行でテーブルに紐づくモデルを定義可能
  • CRUD は User::create() / ::find() / ::where()->get() / $user->update() / $user->delete()
  • 一括代入には $fillable(許可リスト)または $guarded(拒否リスト)の指定が必須
  • 型変換は $castsarray / datetime / boolean / Enum 等に自動キャスト
  • リレーションは hasOne / hasMany / belongsTo / belongsToMany。N+1 問題は with() による Eager Loading で解決
  • クエリスコープ(scopeActive)/ アクセサ・ミューテタ(getXxxAttribute)で再利用と整形を集約

Eloquent とは

Eloquent は Laravel に同梱されている ORM(Object Relational Mapper)です。1 つのテーブルに 1 つの PHP クラスを対応させ、SQL を書かずに CRUD を行えます。Active Record パターンに基づき、「モデル=レコード」として扱えるのが特徴です。

モデルの作成と最小定義

# users テーブルに対応するモデルを生成
php artisan make:model User

# マイグレーションも同時に生成
php artisan make:model Post -m

# コントローラ・ファクトリ・シーダもまとめて
php artisan make:model Post -mfsc
 'datetime',
        'is_admin'          => 'boolean',
        'settings'          => 'array',
        'role'              => UserRole::class, // Enum
    ];

    // JSON シリアライズ時に隠すカラム
    protected $hidden = ['password', 'remember_token'];
}

CRUD の基本

use App\Models\User;

// ============== Create ==============
// 1) 一括代入(fillable に列挙が必要)
$user = User::create([
    'name'     => 'Taro',
    'email'    => 'taro@example.com',
    'password' => bcrypt('secret'),
]);

// 2) 属性ごとに代入して save
$user = new User();
$user->name = 'Taro';
$user->save();

// ============== Read ==============
User::all();                          // 全件
User::find(1);                        // 主キー 1 件、なければ null
User::findOrFail(1);                  // 無ければ 404
User::where('email', 'taro@example.com')->first();
User::where('age', '>=', 18)->orderBy('id', 'desc')->limit(10)->get();
User::count();
User::pluck('email');                 // email 列だけ Collection
User::whereIn('id', [1, 2, 3])->get();

// ============== Update ==============
$user = User::find(1);
$user->name = 'Jiro';
$user->save();

// 一括 update(fillable 不要、直接 UPDATE 文)
User::where('is_admin', false)->update(['status' => 'inactive']);

// 取得と更新を 1 行
User::find(1)->update(['name' => 'Jiro']);

// ============== Delete ==============
User::find(1)->delete();
User::where('status', 'inactive')->delete();
User::destroy([1, 2, 3]);             // 主キー指定で複数削除

$fillable と $guarded(マスアサインメント保護)

外部入力($request->all() 等)をそのまま create()update() に渡すと、意図しないカラム(例: is_admin)まで書き換えられるセキュリティリスクがあります。これを防ぐのが Mass Assignment Protection です。

プロパティ意味用途
$fillable許可リスト列挙したカラムだけ一括代入可(推奨)
$guarded拒否リスト列挙したカラムだけ拒否。[] で全許可(危険)

$casts による型変換

protected $casts = [
    'is_admin'    => 'boolean',  // 0/1 → true/false
    'price'       => 'integer',
    'rate'        => 'float',
    'options'     => 'array',    // JSON ←→ PHP 配列
    'meta'        => 'object',
    'published_at'=> 'datetime', // string ←→ Carbon
    'birthday'    => 'date',
    'role'        => UserRole::class, // PHP 8.1+ Enum
    'password'    => 'hashed',   // Laravel 10+: 自動 bcrypt
];

// 取得時に Carbon オブジェクトになる
$user->published_at->format('Y-m-d');
$user->published_at->diffForHumans();

// options を配列のまま扱える
$user->options = ['theme' => 'dark'];
$user->save();  // DB には JSON で保存

リレーション

// ===== 1 対 1(hasOne / belongsTo)=====
// users.id ← profiles.user_id
class User extends Model {
    public function profile() {
        return $this->hasOne(Profile::class);
    }
}
class Profile extends Model {
    public function user() {
        return $this->belongsTo(User::class);
    }
}

// 使い方
$user = User::find(1);
$user->profile->bio;            // hasOne を辿る
$profile->user->name;           // belongsTo を辿る

// ===== 1 対 多(hasMany)=====
class User extends Model {
    public function posts() {
        return $this->hasMany(Post::class);
    }
}
$user->posts;                   // Collection
$user->posts()->where('published', true)->get();

// ===== 多 対 多(belongsToMany)=====
// users  ─ role_user ─ roles
class User extends Model {
    public function roles() {
        return $this->belongsToMany(Role::class)
            ->withPivot('assigned_at')
            ->withTimestamps();
    }
}
$user->roles;                   // Collection
$user->roles()->attach($roleId);
$user->roles()->detach($roleId);
$user->roles()->sync([1, 2, 3]);

N+1 問題と Eager Loading

リレーションを使うときに最も注意すべきが N+1 問題です。

// ❌ N+1: posts 1 回 + 各 post の user で N 回、合計 N+1 クエリ
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name;     // 毎ループで SELECT が走る
}

// ✅ Eager Loading: 合計 2 クエリで済む
$posts = Post::with('user')->get();
foreach ($posts as $post) {
    echo $post->user->name;     // 追加クエリなし
}

// 複数リレーション
Post::with(['user', 'comments', 'tags'])->get();

// ネスト
Post::with('comments.user')->get();

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

クエリスコープ(再利用可能な where)

class Post extends Model
{
    // ローカルスコープ: scope プレフィックス
    public function scopePublished($query) {
        return $query->where('status', 'published');
    }

    public function scopeOfAuthor($query, int $authorId) {
        return $query->where('author_id', $authorId);
    }
}

// 利用側はメソッドチェーンで
Post::published()->ofAuthor(1)->latest()->get();

アクセサ・ミューテタ

use Illuminate\Database\Eloquent\Casts\Attribute;

class User extends Model
{
    // Laravel 9+ の新記法
    protected function fullName(): Attribute
    {
        return Attribute::make(
            get: fn ($value, $attributes) => $attributes['first_name'] . ' ' . $attributes['last_name'],
            set: fn ($value) => ['first_name' => explode(' ', $value)[0]],
        );
    }
}

echo $user->full_name;          // アクセサ経由で取得
$user->full_name = 'Taro Sato'; // ミューテタ経由で代入

よく使う追加メソッド

  • User::firstOrCreate(['email' => $e], ['name' => $n]) — 存在すれば取得、無ければ作成
  • User::updateOrCreate(['email' => $e], ['name' => $n]) — UPSERT 相当
  • User::chunk(200, function ($users) { ... }) — 大量データを分割処理
  • User::cursor() — メモリ効率の良いイテレータ
  • $user->toArray() / ->toJson() — シリアライズ
  • $user->refresh() / ->fresh() — DB から再取得

FAQ

Q: save()update() の違いは?
A: save() は属性代入後にインスタンスを保存。update() は一括代入+保存を一度に行うショートカット(fillable が効きます)。

Q: 生 SQL を書きたい
A: DB::select('SELECT ...')User::whereRaw('YEAR(created_at) = ?', [2026]) が使えます。

Q: ソフトデリート(論理削除)したい
A: use SoftDeletes; トレイトを追加し、マイグレーションで $table->softDeletes(); を入れると deleted_at による論理削除になります。