"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 só 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 joao@email.com é 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. 😅
Top comments (0)