9.

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 による論理削除になります。

編集
Post Share
子ページ
  1. モデルの作成
  2. $fillable $guarded $hiddenの説明
  3. テーブルの紐づけ
  4. 主キーの指定とインクリメント
  5. タイムスタンプ
  6. モデルでselect
  7. モデルでinsert
  8. モデルでupdate
  9. 現在値に加算する方法
  10. created_at/updated_atの別名指定
同階層のページ
  1. インストールと設定
  2. クイックスタート & チュートリアル(初心者向け)
  3. クイックスタート & チュートリアル(中級者向け)
  4. ルーティング
  5. Bladeテンプレート(ビュー/レイアウト)
  6. コントローラー
  7. マイグレーションとテーブル定義
  8. データベースの設定
  9. Eloquentモデル (ORM)
  10. SQLとクエリビルダー
  11. バリデーション
  12. .envファイルの設定値へのアクセス
  13. 動作環境による分岐処理
  14. configフォルダ配下の設定値へのアクセス
  15. assetヘルパーを利用したpublicフォルダへのアクセス
  16. storageフォルダへのアクセス
  17. アプリケーション名の変更
  18. メンテナンス
  19. ログイン画面(認証システム)の作成
  20. ログインの必須化
  21. ログインユーザー情報の取得
  22. ルートの認証化
  23. 本番サーバーへのデプロイ方法
  24. 多言語化
  25. csrf_field
  26. ファイルのダウンロード
  27. CSVのアップロードおよび読み込み(maatwebsite/excel)
  28. ページタイトルの設定
  29. コマンド一覧
  30. エラー一覧
  31. SQLの実行ログ出力方法
  32. キャッシュのクリア
  33. Selectの結果の最初もしくは最後に任意の値を追加する方法
  34. ajaxでPOST通信する際の注意点
  35. ソーシャルログインの実装
  36. セッション情報の確認
  37. ログイン、ユーザー登録、パスワードリセット後のリダイレクト先の変更方法
  38. redirectやreturn viewにメッセージを付与する方法
  39. クッキー(cookie)の設定と取得
  40. クラスの再読み込み
  41. csrfの有効時間を変更する方法
  42. ViewComposerを用いてviewに共通の値を付与する方法
  43. View::shareを用いて共通の値を各ビューに渡す方法
  44. ミドルウェアを用いた処理の共通化
  45. Middleware内でAuth::check()などを使用する方法
  46. Controller以外でリダイレクトする方法
  47. セッションの値の取得/保存/更新/削除
  48. $requestの値を変更する方法
  49. 常時SSL化
  50. ページング(ページネーション)をする方法
  51. vue.jsとの連携
  52. Vue.jsと連携するSPA実行環境構築
  53. .envの値をvue.jsで参照する方法
  54. vue.jsを本番環境にリリースする方法
  55. could not find driver(Windows, MySQL編)