Soft delete
HasMadSoftDeletes, withTrashed, restore.
Soft delete marca registros como deletados sem removê-los fisicamente
do banco. No MAD isso é implementado pelo trait Mad\Database\Concerns\HasMadSoftDeletes
— soft delete condicional: só fica ativo quando o model declara a coluna.
Não é o trait
Illuminate\Database\Eloquent\SoftDeletesnativo do Laravel — é uma implementação própria do MAD com a mesma ideia, mas remapeamento de nome de coluna e compatibilidade com constantes legadas (DELETEDAT).
Habilitar
use Mad\Database\Concerns\HasMadSoftDeletes;
class Produto extends Model
{
use HasMadSoftDeletes;
protected $connection = 'business';
protected $table = 'produto';
// Coluna padrão é 'deleted_at' — só precisa declarar se for outro nome
protected ?string $deletedAtColumn = 'deleted_at';
}
Forma recomendada pra remapear (junto de HasMadAudit, mesmo array
$madAudit, false desliga a coluna):
protected array $madAudit = [
'deleted_at' => 'removido_em',
'deleted_by' => 'removido_por',
'deleted_by_user_id' => 'removido_por_user_id',
];
A coluna deve ser
NULLpor padrão. Quando preenchida com timestamp, o registro é considerado deletado.
Comportamento padrão
// delete() agora faz UPDATE deleted_at = NOW() — não DELETE FROM
$produto = Produto::find(42);
$produto->delete();
// Queries automaticamente excluem soft-deleted (global scope 'madSoftDelete')
$ativos = Produto::all();
// → SELECT * FROM produto WHERE deleted_at IS NULL
$total = Produto::count();
// → SELECT COUNT(*) FROM produto WHERE deleted_at IS NULL
Incluir registros deletados
// withTrashed() — remove o global scope, inclui deletados
$todos = Produto::withTrashed()->where('ativo', true)->get();
// onlyTrashed() — APENAS deletados (lixeira)
$lixeira = Produto::onlyTrashed()->orderByDesc('deleted_at')->get();
Restaurar
$produto = Produto::withTrashed()->find($id);
if ($produto && $produto->trashed()) {
$produto->restore();
}
Hard delete (físico)
O trait não expõe um método forceDelete() próprio. A maneira real de
remover fisicamente é ir direto pelo Query Builder — operações em massa não passam
pela instância do model, então não disparam o override de soft delete:
// DELETE FROM produto WHERE id = X — físico, irreversível
Produto::withTrashed()->where('id', $id)->delete();
Por que isso funciona:
HasMadSoftDeletesinterceptaperformDeleteOnModel()(chamado por$model->delete()numa instância única). Um mass delete via Query Builder (Model::where(...)->delete()) nunca instancia o model — vai direto pro SQL, igual qualquer operação em massa do Eloquent. Ver Operações em massa.
Auditoria — quem deletou
Já é automático: basta mapear as colunas em $madAudit (ou declarar as
propriedades $deletedByColumn/$deletedByUserIdColumn) — o
trait preenche sozinho a partir da session a cada delete(), sem precisar
de evento nenhum:
class Produto extends Model
{
use HasMadSoftDeletes;
protected array $madAudit = [
'deleted_at' => 'deleted_at',
'deleted_by' => 'deleted_by', // ← preenchido com session('login')
'deleted_by_user_id' => 'deleted_by_user_id', // ← preenchido com session('userid')
];
}
Não tente setar
$produto->deleted_bymanualmente num eventodeleting—performDeleteOnModel()recalcula esse valor a partir da session e sobrescreve qualquer coisa que você tenha atribuído antes.
Listagens com toggle de deletados
Em MadDataGrid, sobrescreva o hook query(): array (vazio por
padrão, sinaliza pro grid usar a auto-query builder-native) pra montar a busca você
mesmo quando precisa de uma condição como "mostrar excluídos":
class ProdutoListagem extends MadDataGrid
{
protected string $model = 'Produto';
public bool $mostrarDeletados = false;
protected function query(): array
{
$q = Produto::query();
if ($this->mostrarDeletados) {
$q->withTrashed();
}
$q->orderBy('nome');
return [
'items' => $q->forPage($this->page, $this->perPage)->get()->all(),
'total' => (clone $q)->count(),
];
}
public function onToggleDeletados(): void
{
$this->mostrarDeletados = !$this->mostrarDeletados;
$this->page = 1;
$this->loadData();
}
}
API — resumo
| Método | Descrição |
|---|---|
where(...)->get() | Exclui deletados automaticamente (global scope) |
withTrashed() | Inclui deletados |
onlyTrashed() | APENAS deletados |
restore() | Limpa a coluna de deleção (restaura) |
trashed() | Bool — registro está soft-deleted? |
withTrashed()->where(...)->delete() | DELETE físico (mass delete, irreversível) |
Próximos
- Callbacks — eventos
deleting/deleted. - Global scopes — como o filtro automático funciona por baixo.
- Models Eloquent — API completa do model.
- Operações em massa — update/delete em lote.