Laravel Eloquentでデータ更新を効果的に行う方法とベストプラクティス

基本文法・構文ガイド

LaravelのEloquent ORMは、データベースと連携したアプリケーションの開発を非常に効率的に行うための強力なツールです。Eloquentを使ってデータを更新する方法はいくつかあり、それぞれに適した使い所があります。この記事では、save()update()・一括更新の違いと使い分けを整理し、Mass Assignmentの注意点やトランザクションの活用方法まで詳しく解説します。

Eloquent更新方法の早見表

方法 書き方 モデルイベント updated_at 主な用途
save() $model->save() 発火する 自動更新 1件をインスタンス経由で更新
update()(インスタンス) $model->update([...]) 発火する 自動更新 1件をまとめて属性更新
where()->update() Model::where()->update([...]) 発火しない 自動更新 複数件を一括更新(高速)

Eloquentでの基本的なデータ更新方法

find() → 更新 → save() の基本パターン

最もシンプルな更新方法は、モデルインスタンスを取得して属性を変更し、save()で保存するパターンです。

$user = User::find(1); // IDが1のユーザーを取得
$user->name = 'New Name'; // 名前を更新
$user->save(); // 変更を保存

find()でモデルが見つからない場合はnullが返るため、実運用ではfindOrFail()を使ってモデルが存在しない場合に例外を投げる方法が安全です。

$user = User::findOrFail(1); // 見つからなければ ModelNotFoundException
$user->name = 'New Name';
$user->save();

インスタンスの update() メソッド

取得済みのモデルインスタンスに対してupdate()メソッドを呼ぶと、配列で複数の属性を一度に更新できます。

$user = User::findOrFail(1);
$user->update([
    'name'  => 'New Name',
    'email' => 'new@example.com',
]);

このメソッドはfillableで許可された属性のみを更新します(後述)。

save() と update() の違い

どちらもモデルインスタンスを使った更新ですが、挙動に微妙な違いがあります。

  • save(): 属性を個別に代入してから呼び出す。fillableの制約を受けない(直接代入のため)。
  • update(配列): 属性を配列で渡す。fillableに含まれない属性は無視される。
// save() は fillable に関係なく直接代入した属性を保存
$user->admin = true;
$user->save(); // fillable になくても保存される

// update() は fillable を通す
$user->update(['admin' => true]); // fillable になければ無視される

フォームからのリクエストデータを扱う際はupdate()(またはfill() + save())を使い、fillableで安全に制御するのが推奨パターンです。

効率的なデータ更新のためのテクニック

Model::where()->update() による一括更新

複数のレコードを一度に更新する場合、クエリビルダのupdate()を使うと単一のSQLクエリで処理できるため、パフォーマンスが向上します。

User::where('status', 'inactive')->update(['status' => 'active']);

上記の例では、statusinactiveである全てのユーザーを一度にactiveに更新します。ただしこの方法はモデルイベント(updating / updated)が発火しない点に注意してください。

dirty Checking の利用

Eloquentには「ダーティチェック」と呼ばれる機能があります。これは、モデルの変更された属性だけを検出し、必要がない限りデータベースの更新を防ぐ仕組みです。無駄な書き込みを防ぎ、パフォーマンスを向上させます。

$user = User::find(1);
$user->name = 'Same Name';

if ($user->isDirty()) {
    $user->save(); // 変更がある場合のみ保存
}

isDirty('name')のように属性名を渡して特定の属性だけを確認することもできます。

fillable / guarded と Mass Assignment

Eloquentでは Mass Assignment(配列で一括代入)を使う際に、意図しない属性が上書きされないよう保護する仕組みがあります。

  • fillable: 一括代入を許可する属性のホワイトリスト
  • guarded: 一括代入を拒否する属性のブラックリスト
class User extends Model
{
    // fillable で許可した属性のみ update() や create() で反映される
    protected $fillable = ['name', 'email'];

    // または guarded で守りたい属性だけを指定する(逆の考え方)
    // protected $guarded = ['id', 'admin'];
}

fillableの設定方法やguardedとの使い分けをさらに詳しく知りたい場合は、「Laravel Fillableの使い方完全ガイド」も参考にしてください。

フォームからの入力をそのままupdate($request->all())に渡すと、fillableの設定がない場合はMassAssignmentExceptionが発生します。fillableを適切に設定することで、例えばadminフラグを勝手に書き換えられるような攻撃を防げます。

// リクエストデータを安全に更新する例
$user = User::findOrFail($id);
$user->update($request->validated()); // validated() で検証済みデータのみ渡す

updated_at の挙動

Eloquentのモデルにはcreated_atupdated_atのタイムスタンプが自動管理されています。

  • save()update()(インスタンス)を呼ぶとupdated_atが現在時刻に自動更新される
  • where()->update()でもupdated_atは自動更新される
  • updated_atを更新させたくない場合は$timestamps = falseかメソッドにtimestampsオプションを使う
// updated_at を更新せずに保存する
$user->timestamps = false;
$user->name = 'New Name';
$user->save();
$user->timestamps = true; // 元に戻す

// または updated_at を明示的に指定する
$user->update([
    'name'       => 'New Name',
    'updated_at' => now()->subDays(7), // 7日前の日時を指定
]);

トランザクションを使った安全な更新

複数のテーブルをまたいで更新するなど、一連の操作が全部成功するか全部失敗するかを保証したい場合はトランザクションを使います。

トランザクションが必要なケース

  • ユーザー情報と関連プロフィールを同時に更新するとき
  • 在庫数を減らしつつ注文レコードを作成するとき
  • 支払い処理と購入履歴の記録を同時に行うとき
DB::transaction(function () use ($user) {
    $user->name = 'New Name';
    $user->email = 'newemail@example.com';
    $user->save();

    // 関連するプロフィールも同時に更新
    $profile = UserProfile::where('user_id', $user->id)->first();
    $profile->bio = 'Updated bio';
    $profile->save();
});
// いずれかで例外が発生するとロールバックされる

DB::transaction()のクロージャ内で例外が投げられると、自動的にロールバックされます。手動で制御したい場合はDB::beginTransaction() / DB::commit() / DB::rollBack()を使います。

モデルイベントを用いた更新処理の拡張

Eloquentモデルは、様々なイベントをサポートしており、更新時に特定のロジックを追加するのに非常に役立ちます。updatingupdatedイベントを使用しておくと、更新前後にロジックを実行できます。

class User extends Model
{
    protected static function boot()
    {
        parent::boot();

        static::updating(function ($user) {
            // 更新前の処理(例: ログ記録、バリデーション)
        });

        static::updated(function ($user) {
            // 更新後の処理(例: キャッシュクリア、通知送信)
        });
    }
}

なお、where()->update()ではこれらのモデルイベントが発火しないため、イベントが必要な場合はインスタンス経由のsave()またはupdate()を使いましょう。

よくある質問(FAQ)

Eloquentでデータを更新するには?

最もシンプルな方法はfind()でモデルを取得し、属性を変更してsave()する方法です。

$user = User::findOrFail(1);
$user->name = '新しい名前';
$user->save();

複数の属性をまとめて更新する場合はupdate(['key' => 'value', ...])を使うと簡潔に書けます。

save() と update() の違いは?

save()はモデルに個別に属性を代入した後に呼び出すメソッドで、fillableの制約を受けません。一方、update(配列)は配列で属性をまとめて渡すメソッドで、fillableに含まれない属性は自動的に無視されます。フォーム入力など外部データを扱う際はupdate()を使うほうが安全です。

複数件をまとめて更新できる?

できます。where()->update()を使えば、条件に一致するレコードを一度のSQLで一括更新できます。

// status が inactive の全ユーザーを active に変更
User::where('status', 'inactive')->update(['status' => 'active']);

ただし、この方法ではモデルイベント(updating/updated)は発火しないため、イベント処理が必要な場合はeach()で1件ずつ処理するか、別の方法を検討してください。

「無ければ作成、あれば更新」したい場合

ここまでは既存レコードを更新する前提で解説してきましたが、「レコードがあれば更新し、無ければ新規作成する」という処理を1行で書きたい場合はupdateOrCreate()が便利です。詳しい使い方は「LaravelのupdateOrCreateの使い方|firstOrCreate・upsertとの違いも解説」で解説しています。

まとめ

Laravel Eloquentでのデータ更新方法を整理すると、次のようになります。

  • 1件をインスタンス経由で更新する場合はfind()save() または update(配列)
  • 複数件を一括更新する場合はwhere()->update()(モデルイベントは発火しない)
  • Mass Assignmentの保護にはfillableを必ず設定する
  • 複数テーブルをまたぐ更新にはトランザクションで整合性を担保する
  • updated_atはデフォルトで自動更新されるが、必要に応じて制御できる

これらの使い分けを理解することで、安全で効率的なEloquentの更新処理を実装できます。

レン (Wren)

こんにちは。レンです。

Laravelのコードの森に住んでいる、小さな案内役です。
ルーティングの枝やクラスの影を歩きながら、コードの流れや仕組みを眺めています。

このサイトでは、Laravelの基本から実装のコツまで、開発で役立つポイントを静かに整理しています。
難しいことを増やすのではなく、コードの見通しが少し良くなるヒントを届けるのが役目です。

「この処理はどこに書くのがいいのか」
「Laravelではどう考えると整理できるのか」

そんな疑問に、小さなメモを残すような気持ちで記事を書いています。

コードを書いている途中で迷ったとき、
このサイトが少し立ち止まって整理できる場所になればうれしいです。

レン (Wren)をフォローする

コメント