株式会社WR

株式会社WR

WEB TOTAL CONSULTING

LaravelのSoftDelete——データを削除せずに無効化する
ブログ一覧へ
技術ブログ

LaravelのSoftDelete——データを削除せずに無効化する

顧客管理・予約システムでデータを物理削除すると、履歴が失われます。LaravelのSoftDeleteで論理削除を実装し、データを安全に管理する方法を解説します。

SoftDeleteとは——削除しない削除

Laravelの SoftDelete(ソフトデリート)は、レコードをDBから物理的に削除せず、deleted_at カラムに削除日時を記録することで「論理削除」を実現する仕組みです。

なぜソフトデリートを使うか

  • 間違いの取り消し:管理者が誤って削除したデータをすぐに復元できる
  • 監査ログ:誰がいつ削除したかの記録が残る
  • 参照整合性:他テーブルから参照されているレコードを安全に「隠す」
  • 法的要件:取引記録など、一定期間保持義務があるデータの管理

セットアップ

マイグレーション

Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->string('order_number')->unique();
    $table->foreignId('customer_id')->constrained();
    $table->decimal('total', 10, 2);
    $table->string('status');
    $table->timestamps();
    $table->softDeletes(); // → deleted_at TIMESTAMP NULL を追加
});

既存テーブルに追加する場合:

Schema::table('orders', function (Blueprint $table) {
    $table->softDeletes();
});

モデル

<?php

namespace App\Models;

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

class Order extends Model
{
    use SoftDeletes;

    protected $casts = [
        'deleted_at' => 'datetime',
    ];
}

基本操作

// 通常のDeleteはsoftDelete(deleted_atに日時を記録)
$order = Order::find(1);
$order->delete();
// → deleted_at = '2024-01-15 12:34:56' にUPDATE

// 通常のクエリにはsoft-deletedが含まれない
$orders = Order::all(); // deleted_at IS NULL のみ返る

// 削除済みを含めて取得
$allOrders = Order::withTrashed()->get();

// 削除済みのみ取得
$deletedOrders = Order::onlyTrashed()->get();

// 特定レコードが削除済みか確認
$order->trashed(); // true/false

// 復元(deleted_at を NULL にリセット)
$order->restore();

// 物理削除(本当に削除)
$order->forceDelete();

Observerと組み合わせる

class OrderObserver
{
    public function deleted(Order $order): void
    {
        // ソフトデリート時に関連データも非表示にする
        $order->items()->each(fn($item) => $item->delete());

        // 管理者に通知
        Log::info("注文 #{$order->order_number} が削除されました", [
            'deleted_by' => auth()->id(),
            'deleted_at' => now()->toIso8601String(),
        ]);
    }

    public function restored(Order $order): void
    {
        // 復元時に関連データも復元
        $order->items()->withTrashed()->restore();
    }
}

削除者を記録する——deleted_by カラム

誰が削除したかを記録したい場合は deleted_by カラムを追加します。

// マイグレーション
$table->foreignId('deleted_by')->nullable()->constrained('users');

// モデルのbooted
protected static function booted(): void
{
    static::deleting(function (Order $order) {
        $order->deleted_by = auth()->id();
        $order->saveQuietly();
    });
}

リレーション先もソフトデリートする

class Customer extends Model
{
    use SoftDeletes;

    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }
}

// 顧客を削除すると関連注文も削除(CascadeSoftDeletes)
class CustomerObserver
{
    public function deleting(Customer $customer): void
    {
        // 全注文をソフトデリート
        $customer->orders()->each->delete();
    }

    public function restored(Customer $customer): void
    {
        // 全注文を復元
        $customer->orders()->withTrashed()->restore();
    }
}

定期的な物理削除——古い削除済みレコードを消す

ソフトデリートされたレコードが溜まり続けるのを防ぐため、一定期間後に物理削除するバッチを設定します。

// コマンドを作成
class PruneDeletedOrders extends Command
{
    protected $signature = 'orders:prune {--days=90}';

    public function handle(): void
    {
        $days = (int) $this->option('days');
        $count = Order::onlyTrashed()
                      ->where('deleted_at', '<', now()->subDays($days))
                      ->forceDelete();

        $this->info("物理削除: {$count}件({$days}日以上前に削除されたもの)");
    }
}

// スケジューラーで毎月1日の午前3時に実行
Schedule::command('orders:prune --days=365')->monthlyOn(1, '03:00');

テスト

class SoftDeleteTest extends TestCase
{
    use RefreshDatabase;

    public function test_注文を削除するとsoftDeleteされる(): void
    {
        $order = Order::factory()->create();
        $order->delete();

        $this->assertSoftDeleted('orders', ['id' => $order->id]);
        $this->assertNull(Order::find($order->id)); // 通常クエリでは見えない
    }

    public function test_削除した注文を復元できる(): void
    {
        $order = Order::factory()->create();
        $order->delete();
        $order->restore();

        $this->assertNotSoftDeleted('orders', ['id' => $order->id]);
        $this->assertNotNull(Order::find($order->id));
    }
}

まとめ

SoftDeleteはデータの安全性と運用性を両立する重要なパターンです。管理画面での誤削除対策・監査ログ・段階的削除など、業務システムでは必須の機能といえます。弊社では予約システム・受注管理システムでSoftDeleteを標準採用しています。

LaravelによるWebシステム開発のご相談はお気軽にどうぞ。

Category 技術ブログ

Related Posts

関連記事

開発・技術のご相談はお気軽に

お見積りは無料です。まずはお気軽にご相談ください。

お問い合わせ →