Как реализовать soft deletes в Laravel?
Soft deletes реализуются через трейт SoftDeletes в модели и метод softDeletes() в миграции. Удалённые записи помечаются в поле deleted_at и фильтруются автоматически; восстановление — через restore(), включение в запрос — через withTrashed().
Soft Deletes в Laravel
Soft delete (мягкое удаление) — это подход, при котором запись не удаляется из базы данных физически, а помечается временной меткой в поле deleted_at. Все стандартные запросы Eloquent автоматически фильтруют такие записи через WHERE deleted_at IS NULL.
Настройка модели
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Article extends Model
{
use SoftDeletes;
protected $fillable = ['title', 'body', 'user_id'];
}
Добавление колонки в миграции
Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body')->nullable();
$table->timestamps();
$table->softDeletes(); // добавляет deleted_at TIMESTAMP NULL
});
// Для существующей таблицы
Schema::table('articles', function (Blueprint $table) {
$table->softDeletes();
});
Базовые операции
// Мягкое удаление — устанавливает deleted_at = now()
$article = Article::find(1);
$article->delete();
// Проверить, удалена ли запись
$article->trashed(); // true
// Восстановить запись
$article->restore();
// Физически удалить из БД
$article->forceDelete();
Запросы с учётом удалённых записей
// Обычный запрос — удалённые записи НЕ включаются
$articles = Article::all(); // WHERE deleted_at IS NULL
// Включить удалённые
$allArticles = Article::withTrashed()->get();
// Только удалённые
$deletedArticles = Article::onlyTrashed()->get();
// Восстановить пачку по условию
Article::onlyTrashed()
->where('user_id', 42)
->restore();
Soft deletes и связи
По умолчанию Eloquent отношения тоже фильтруют удалённые записи. Чтобы включить их явно:
// В определении отношения
public function comments()
{
return $this->hasMany(Comment::class)->withTrashed();
}
// Каскадное мягкое удаление связей через Observer
class ArticleObserver
{
public function deleted(Article $article): void
{
$article->comments()->delete(); // тоже soft delete
}
public function restored(Article $article): void
{
$article->comments()->onlyTrashed()->restore();
}
}
Уникальные индексы и soft deletes
Если есть уникальный индекс (например, на slug), мягко удалённые записи с таким же slug заблокируют создание новых. Решение — составной уникальный индекс с deleted_at:
$table->unique(['slug', 'deleted_at']);
Или использовать whereNull('deleted_at') в validation rule вручную.
Подводные камни
- Физический внешний ключ FK не знает о soft deletes — запись с deleted_at != null всё равно блокирует удаление родительской таблицы. Используйте
->cascadeOnDelete()аккуратно или управляйте каскадом через Observer. - Индексы по полям фильтрации (WHERE status = 'active') должны включать deleted_at для эффективной работы — иначе БД сканирует все строки включая удалённые.
- withTrashed() в production-запросах нужно использовать осознанно: легко случайно отдать удалённые данные пользователям.
- При использовании Route Model Binding Laravel автоматически применяет WHERE deleted_at IS NULL — мягко удалённая запись вернёт 404. Если нужно показать её, используйте явный запрос вместо биндинга.
- forceDelete() не вызывает событие deleting если модель уже soft-deleted — будьте осторожны с Observers, которые рассчитывают на это событие.
- Таблицы с большим количеством soft-deleted записей требуют периодической очистки (
Article::onlyTrashed()->where('deleted_at', '<', now()->subMonths(6))->forceDelete()), иначе производительность деградирует. - Если используете Scout (full-text search), soft-deleted записи по умолчанию остаются в индексе — нужно явно слушать событие deleted и удалять из индекса.
Common mistakes
- Сводить soft deletes к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: Laravel 13 поверх PHP: request проходит через HTTP kernel, middleware, routing, controller/action и response pipeline.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет soft deletes через конкретную точку lifecycle в Laravel.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.