Laravelでの論理削除を理解する:実装方法とメリットを徹底解説

読み物・開発コラム

Laravelの論理削除(ソフトデリート)は、データベースからレコードを物理的に削除せず、deleted_atカラムに削除日時を記録して「削除扱い」にする機能です。モデルにSoftDeletesトレイトを使用するだけで、通常のクエリから削除済みレコードが自動的に除外され、必要に応じてwithTrashed()での全件取得やrestore()での復元が行えます。

この記事では、論理削除の基本概念からマイグレーション・モデル設定、削除・復元・完全削除の各メソッドの使い方、実務で役立つベストプラクティスと注意点まで体系的に解説します。

論理削除(ソフトデリート)とは?

データベースの削除操作には「物理削除」と「論理削除」の2種類があります。

  • 物理削除(Physical Delete):データベースからDELETE FROM文を実行してレコードを完全に抹消します。ディスク容量は解放されますが、誤削除時の復元は困難です。
  • 論理削除(Logical Delete / Soft Delete):レコード自体は残したまま、deleted_atなどの削除日時カラムに値をセットします。アプリケーションからは「削除された」状態に見えますが、後から復元や履歴追跡が可能です。

LaravelのEloquent ORMには、この論理削除を標準でサポートする強力な仕組み(SoftDeletes)が備わっています。

Laravelでの論理削除の実装手順

Laravelで論理削除を有効化する手順はシンプルで、マイグレーションの追加とモデルへのトレイト適用の2ステップで完了します。

1. テーブルへのdeleted_atカラム追加(マイグレーション)

対象テーブルにdeleted_atカラム(TIMESTAMP型・NULL許容)を追加します。マイグレーションファイル内でsoftDeletes()メソッドを使用します。

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->softDeletes(); // deleted_at (TIMESTAMP, nullable) を追加
        });
    }

    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropSoftDeletes(); // deleted_at を削除
        });
    }
};

2. モデルにSoftDeletesトレイトを追加

対象のEloquentモデルでIlluminate\Database\Eloquent\SoftDeletesトレイトをuseします。

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;

    protected $fillable = ['title', 'content', 'status'];
}

この設定を行うと、グローバルスコープ(SoftDeletingScope)が自動適用され、通常の検索クエリにWHERE deleted_at IS NULLが自動的に付加されます。

論理削除における主要メソッドの使い方

3. レコードの論理削除:delete()

モデルインスタンスに対してdelete()を実行すると、deleted_atカラムに現在日時が保存されます。

$post = Post::find(1);
$post->delete(); // deleted_at に現在時刻がセットされる(レコードは残る)

削除処理をトランザクション内で安全に実行する方法や、クエリビルダでの一括削除との挙動の違いについては、Laravelでの安全な削除操作:deleteメソッドの使い方と注意点で詳しく解説しています。

4. 削除されたレコードの復元:restore()

論理削除されたレコードを元の状態に戻すには、restore()メソッドを使用します。deleted_atカラムがnullに更新されます。

// 削除済みレコードを取得して復元
$post = Post::withTrashed()->find(1);
$post->restore(); // deleted_at が NULL に戻る

5. 論理削除レコードの取得:withTrashed() / onlyTrashed()

通常のクエリでは削除済みデータは取得されませんが、専用メソッドを使うことで取得対象を制御できます。

// 通常クエリ: 有効なレコードのみ(deleted_at IS NULL)
$activePosts = Post::all();

// withTrashed(): 削除済みも含めた全レコードを取得
$allPosts = Post::withTrashed()->get();

// onlyTrashed(): 削除されたレコードのみを取得
$deletedPosts = Post::onlyTrashed()->get();

withTrashed()onlyTrashed()の具体的なクエリ構築パターンやリレーション取得での使い分けは、Laravel withTrashed()の使い方|論理削除データの取得とonlyTrashed()との違いで詳細に解説しています。

6. 完全に削除する(物理削除):forceDelete()

論理削除されたレコードをデータベースから物理的に完全削除したい場合は、forceDelete()を使用します。

// 削除済みレコードを物理削除(完全に消去)
$post = Post::withTrashed()->find(1);
$post->forceDelete();

論理削除のメリット

  • 誤操作によるデータ消失の防止:ユーザーや管理者が誤って削除しても、管理画面等から即座にrestore()で復元できます。
  • 監査ログ・履歴管理:いつ誰が削除したかの履歴を残せるため、コンプライアンスやデータ分析の要件を満たしやすくなります。
  • 関連データとの不整合防止:注文データに関連するユーザーが削除された場合でも、外部キー制約エラーを回避しつつ整合性を維持できます。

注意点とベストプラクティス

1. クエリビルダ直接利用時はスコープが無効

DB::table('posts')->get()のようにクエリビルダを直接呼び出すと、EloquentのSoftDeletingScopeが適用されず、削除済みレコードも取得されてしまいます。クエリビルダを使う場合は->whereNull('deleted_at')を明示するか、Eloquentモデルを経由してクエリを発行してください。

2. ユニーク制約(一意性制約)との競合

テーブルのemailなどのカラムにユニークインデックスが設定されている場合、論理削除されたレコードが残っていると同じメールアドレスで再登録できない問題が発生します。複合ユニークインデックス(email + deleted_at)にするか、バリデーションルールで論理削除を無視する設定(Rule::unique('users')->whereNull('deleted_at'))を行いましょう。

3. 除外条件クエリ(whereNot / whereNotIn)との組み合わせ

「特定ステータス以外のレコードを取得しつつ、削除済みレコードも含める」といった複雑な条件を指定する場合は、whereNot()whereNotIn()withTrashed()を組み合わせます。条件指定の詳しい書き方は、Laravel whereNot/whereNotInの使い方と違い|除外条件の書き方と実践例を参考にしてください。

4. 定期的なデータクリーンアップ(Pruning)

論理削除レコードが蓄積し続けるとテーブル容量を圧迫し、インデックスサイズ増加により検索速度が低下する可能性があります。Laravel 8.50以降で導入されたPrunableまたはMassPrunableトレイトを利用して、一定期間(例: 30日以上)経過した論理削除レコードを自動的にforceDeleteするバッチ処理を組むのがおすすめです。

まとめ

Laravelの論理削除(SoftDeletes)は、簡単な設定でデータの安全性と復元性を確保できる不可欠な機能です。基本のCRUD操作だけでなく、取得メソッド(withTrashed / onlyTrashed)や復元(restore)、完全削除(forceDelete)の使い分けを理解して、安全性の高いWebアプリケーションを構築しましょう。

レン (Wren)

こんにちは。レンです。

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

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

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

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

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

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

コメント