Callbacks de ciclo
Eventos Eloquent: creating, saving, deleting, observers.
Models Eloquent disparam eventos em cada etapa do ciclo de vida — antes/depois de salvar, criar, atualizar, deletar. Use para auditoria, validações cruzadas, side effects e normalização de dados. O MAD não acrescenta um sistema de callbacks próprio aqui: é o mecanismo de eventos nativo do Eloquent.
Eventos disponíveis
| Evento | Quando dispara |
|---|---|
retrieved | Depois de carregar um model existente do banco |
creating / created | Antes/depois de INSERT |
updating / updated | Antes/depois de UPDATE |
saving / saved | Antes/depois de INSERT ou UPDATE (ambos os casos) |
deleting / deleted | Antes/depois de DELETE (ou soft delete, se o model usa HasMadSoftDeletes) |
Não existem eventos
restoring/restoredaqui. Eles vêm do trait nativoIlluminate\Database\Eloquent\SoftDeletes, que o MAD não usa:Mad\Database\Concerns\HasMadSoftDeletes::restore()faz umUPDATEdireto na coluna de deleção (setKeysForSaveQuery(...)->update([$col => null])) e não dispara evento nenhum. Se precisa reagir a uma restauração, chame o side effect explicitamente depois dorestore().
Registre via static::booted() — chamado uma vez quando a classe do model
é inicializada:
class Produto extends Model
{
protected static function booted(): void
{
static::creating(function (self $produto) {
// ...
});
static::updating(function (self $produto) {
// ...
});
}
}
Exemplo real do projeto — cascata de delete
App\Models\Iam\User::booted() — limpa vínculos antes de deletar o usuário:
protected static function booted(): void
{
static::deleting(function (self $user) {
UserGroup::where('user_id', $user->id)->delete();
UserUnit::where('user_id', $user->id)->delete();
UserProgram::where('user_id', $user->id)->delete();
UserRole::where('user_id', $user->id)->delete();
});
}
Exemplo: auditoria automática
É exatamente assim que Mad\Database\Concerns\HasMadAudit funciona por dentro
— sem reinventar nada, dois closures simples:
public static function bootHasMadAudit(): void
{
static::creating(fn ($model) => $model->madStampAudit(true));
static::updating(fn ($model) => $model->madStampAudit(false));
}
Exemplo: normalização
class Cliente extends Model
{
protected static function booted(): void
{
static::saving(function (self $cliente) {
// Normaliza CPF/CNPJ — remove pontos/traços
if (!empty($cliente->documento)) {
$cliente->documento = preg_replace('/\D/', '', $cliente->documento);
}
// Email lowercase
if (!empty($cliente->email)) {
$cliente->email = mb_strtolower(trim($cliente->email));
}
});
}
}
Exemplo: validação cruzada / bloqueio de save
Lance exceção dentro do evento pra abortar a operação:
class PedidoVenda extends Model
{
protected static function booted(): void
{
static::saving(function (self $pedido) {
if ($pedido->exists) {
$total = $pedido->itens()->sum('valor_total');
if ($total <= 0) {
throw new \Exception('Pedido sem itens não pode ser salvo.');
}
$pedido->valor_total = $total;
}
});
}
}
// No controller:
try {
DB::connection('business')->transaction(function () use ($pedido) {
$pedido->save(); // dispara exceção se sem itens
});
return MadToast::success('Salvo!');
} catch (\Throwable $e) {
return MadMessage::error('Erro', $e->getMessage());
}
Exemplo: bloqueio de delete
class Categoria extends Model
{
protected static function booted(): void
{
static::deleting(function (self $categoria) {
$count = Produto::where('categoria_id', $categoria->id)->count();
if ($count > 0) {
throw new \Exception(
"Não é possível excluir: há {$count} produto(s) usando esta categoria."
);
}
});
}
}
Observers — quando os closures crescem demais
Pra models com muitos eventos, extraia uma classe Observer em vez de
empilhar closures em booted():
class ProdutoObserver
{
public function creating(Produto $produto): void { /* ... */ }
public function updating(Produto $produto): void { /* ... */ }
public function deleting(Produto $produto): void { /* ... */ }
}
// AppServiceProvider::boot()
Produto::observe(ProdutoObserver::class);
Eventos NÃO rodam em operações em massa
Model::where(...)->update()eModel::where(...)->delete()operam direto via Query Builder — nenhuma instância de model é criada, então nenhum evento dispara. Comportamento padrão do Eloquent, não uma limitação do MAD. Ver Operações em massa.
// ❌ creating/updating NÃO rodam — mass update via query builder
Produto::where('categoria_id', 5)->update(['ativo' => false]);
// ✅ Se você precisa dos eventos, itere e salve cada instância
$produtos = Produto::where('categoria_id', 5)->get();
foreach ($produtos as $p) {
$p->ativo = false;
$p->save(); // eventos rodam
}
Boas práticas
- Mantenha eventos rápidos — side effects pesados (email, webhook) devem ir pra uma queue, não rodar inline no save.
- Não chame
save()recursivo — modificar e re-salvar dentro desavedcausa loop infinito. - Validação pertence ao controller/form request — o evento é uma defesa adicional, não o lugar primário pra regras de negócio.
Próximos
- Models Eloquent — API base do model.
- Soft delete —
deleting+ soft-delete condicional. - Global scopes — filtros automáticos em queries.
- Validação — rules + exceção no form.