Soft Deletes no Laravel: apague sem apagar de verdade

작성자

카테고리:

← 피드로
DEV Community · Denis Augusto · 2026-07-07 개발(SW)

“Some do banco” nem sempre é o que você quer

Imagina a cena: o cliente liga desesperado porque um funcionário apagou um pedido importante. Você abre o banco, e… não tem. Sumiu. DELETE é DELETE. Agora é backup ou choro.

Agora imagina a mesma cena, só que você digita $pedido->restore() e o registro volta como se nada tivesse acontecido. Herói do dia.

Essa é a proposta do Soft Delete: em vez de apagar a linha de verdade, o Laravel marca ela como apagada e finge que ela não existe mais nas consultas. Ela continua lá, só invisível. E dá pra trazer de volta quando quiser.

O problema: delete() é definitivo

Sem soft delete, isso aqui é irreversível:

$pedido->delete(); // tchau. pra sempre. 💀

Enter fullscreen mode Exit fullscreen mode

Some do banco, some dos relatórios, some da vida. Se alguém apagou por engano, ou se você precisa de histórico pra auditoria, azar. E é impressionante como “apagou por engano” acontece com frequência em produção.

A solução: três passos e pronto

Primeiro, adiciona a coluna deleted_at na migration. O Laravel tem um helper só pra isso:

Schema::table('pedidos', function (Blueprint $table) {
    $table->softDeletes(); // cria a coluna deleted_at (nullable)
});

Enter fullscreen mode Exit fullscreen mode

Depois, usa a trait SoftDeletes na model:

use Illuminate\Database\Eloquent\SoftDeletes;

class Pedido extends Model
{
    use SoftDeletes;
}

Enter fullscreen mode Exit fullscreen mode

Pronto. Agora o delete() mudou de comportamento sem você tocar em mais nada:

$pedido->delete();
// não apaga a linha — só preenche deleted_at com a data/hora de agora

Enter fullscreen mode Exit fullscreen mode

E o melhor: as consultas normais ignoram automaticamente os registros apagados. Pedido::all() não traz nada que foi soft-deletado. Você não precisa filtrar nada na mão.

Trazendo de volta e vendo os apagados

Restaurar é uma linha:

$pedido->restore(); // deleted_at volta pra null, registro "revive"

Enter fullscreen mode Exit fullscreen mode

Pra incluir os apagados numa consulta, withTrashed:

Pedido::withTrashed()->get(); // traz tudo, apagados junto

Enter fullscreen mode Exit fullscreen mode

Pra ver os apagados (útil pra uma tela de “lixeira”):

Pedido::onlyTrashed()->get();

Enter fullscreen mode Exit fullscreen mode

E se precisar apagar de verdade, aí sim, sem volta:

$pedido->forceDelete(); // agora foi pro além de verdade

Enter fullscreen mode Exit fullscreen mode

Como usar na prática

Lixeira pro usuário. Uma tela onde ele apaga itens, mas tem 30 dias pra restaurar. onlyTrashed() lista, restore() recupera.

Auditoria e histórico. Setores como financeiro e jurídico costumam exigir que nada suma de verdade. Soft delete mantém o rastro sem poluir as telas do dia a dia.

Checar o estado no código. Dá pra saber se um registro tá apagado:

if ($pedido->trashed()) {
    // esse aqui tá na lixeira
}

Enter fullscreen mode Exit fullscreen mode

Pegadinha 1: unique não sabe de soft delete

Essa pega muita gente. Você tem uma coluna email com índice unique. O usuário [email protected] é soft-deletado. Ele tenta se cadastrar de novo… e o banco recusa, porque a linha antiga ainda está lá, ocupando o email.

O unique do banco não liga pro deleted_at — pra ele, a linha existe. Na validação, você precisa dizer isso explicitamente. Na Rule unique, ignore os apagados:

use Illuminate\Validation\Rule;

Rule::unique('usuarios', 'email')->whereNull('deleted_at');

Enter fullscreen mode Exit fullscreen mode

Assim a validação só considera os registros ativos, e o email “liberado” pelo soft delete volta a funcionar.

Pegadinha 2: relacionamento com registro apagado

Outra que confunde. O pedido foi soft-deletado, mas você carrega ele por um relacionamento e o item some:

$cliente->pedidos; // não traz os pedidos apagados

Enter fullscreen mode Exit fullscreen mode

Isso normalmente é o que você quer. Mas quando não for — tipo montar um histórico completo do cliente — o relacionamento também aceita withTrashed:

public function pedidos()
{
    return $this->hasMany(Pedido::class)->withTrashed();
}

Enter fullscreen mode Exit fullscreen mode

Ou pontualmente, sem mexer na model:

$cliente->pedidos()->withTrashed()->get();

Enter fullscreen mode Exit fullscreen mode

Bônus: e agora?

Olha as tabelas do seu projeto e pergunta pra cada uma: “se alguém apagar isso por engano, dói?” Pedido, fatura, contrato, usuário — provavelmente sim. Categoria de teste que você cria e joga fora — provavelmente não. Soft delete é ótimo, mas não precisa estar em tudo; coluna a mais e query um pouco mais complexa têm um custo.

E cuidado com a ideia de “guardar tudo pra sempre”. Dependendo do dado (LGPD, por exemplo), você pode ser obrigado a apagar de verdade depois de um tempo. Aí um job periódico com forceDelete() nos registros antigos resolve.

Antes de você fechar a aba

Já perdeu algum registro importante em produção por causa de um delete() sem volta? Ou já foi salvo por um soft delete que alguém teve a sabedoria de colocar antes?

Me conta aí embaixo. E se esse post pode evitar um choro futuro no seu projeto, salva ele e manda pro colega que ainda apaga tudo direto no banco. 😅

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다