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システム開発のご相談はお気軽にどうぞ。