Fetching latest headlines…
Soft Deletes no Laravel: apague sem apagar de verdade
NORTH AMERICA
🇺🇸 United StatesJuly 6, 2026

Soft Deletes no Laravel: apague sem apagar de verdade

2 views0 likes0 comments
Originally published byDev.to

"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. 💀

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)
});

Depois, usa a trait SoftDeletes na model:

use Illuminate\Database\Eloquent\SoftDeletes;

class Pedido extends Model
{
    use SoftDeletes;
}

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

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"

Pra incluir os apagados numa consulta, withTrashed:

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

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

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

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

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

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
}

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');

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

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();
}

Ou pontualmente, sem mexer na model:

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

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. 😅

Comments (0)

Sign in to join the discussion

Be the first to comment!