Operações em massa
update/delete em lote, agregações, insert/upsert, chunk/lazy.
Não existe mais uma classe "Repository" separada do model — toda operação em massa (update/delete em lote, agregações, inserts em bloco) é feita direto no Eloquent Query Builder, encadeado a partir da classe do model. Esta página cobre o que antes vivia numa camada de repository dedicada.
update() — atualizar em massa
// Atualiza TODOS os registros que casam o filtro, numa única query
Produto::where('categoria_id', 5)->update(['ativo' => false]);
Produto::where('categoria_id', 5)->update([
'ativo' => false,
'updated_at' => now(),
]);
Update em massa não dispara eventos do model (
updating/updated) — vai direto pro SQL sem instanciar nenhum model. Se você precisa dos eventos, itere e chamesave()em cada um. Ver Callbacks.
delete() — excluir em massa
Produto::where('ativo', false)
->where('created_at', '<', '2024-01-01')
->delete();
Em model com HasMadSoftDeletes, isso ainda passa pelo global scope normal
(só afeta os não-deletados) mas não vira UPDATE soft-delete — é DELETE
físico, porque mass delete não passa pela instância onde o trait intercepta
performDeleteOnModel(). Ver Soft delete.
Restaurar em massa
restore() é por instância — pra restaurar em lote, faça o update direto na coluna:
Produto::onlyTrashed()
->where('categoria_id', 5)
->update(['deleted_at' => null]);
Use o nome real da coluna:
HasMadSoftDeletespermite remapear ($madAudit['deleted_at']ou$deletedAtColumn), então num model remapeadodeleted_atnão existe. Em código genérico pergunte ao model:(new Produto)->getDeletedAtColumn(). E lembre que o update em massa também não limpadeleted_by/deleted_by_user_id— inclua no array se o model auditar a deleção.
Agregações
// Simples
$total = Produto::where('ativo', true)->count();
$soma = Produto::where('ativo', true)->sum('valor');
$media = Produto::where('ativo', true)->avg('valor');
$max = Produto::where('ativo', true)->max('valor');
$min = Produto::where('ativo', true)->min('valor');
// Com group by — Collection de objetos stdClass
$porCategoria = Produto::where('ativo', true)
->selectRaw('categoria_id, COUNT(*) as total')
->groupBy('categoria_id')
->get();
// [ {categoria_id, total}, ... ]
// Múltiplas agregações na mesma query
$resultado = Produto::where('ativo', true)
->selectRaw('categoria_id, SUM(valor) as total_valor, COUNT(*) as total_itens')
->groupBy('categoria_id')
->get();
// Count distinct
$distintos = Produto::where('ativo', true)->distinct('categoria_id')->count('categoria_id');
pluck() — arrays pra combo/select
// [id => nome, ...]
$options = Produto::pluck('nome', 'id');
// Com filtro
$options = Produto::where('ativo', true)->orderBy('nome')->pluck('nome', 'id');
Inserts em bloco
// insert() — INSERT em lote, sem disparar eventos nem timestamps automáticos
Produto::insert([
['nome' => 'Camiseta', 'valor' => 59.90, 'categoria_id' => 1],
['nome' => 'Calça', 'valor' => 129.90, 'categoria_id' => 1],
]);
// upsert() — INSERT ou UPDATE conforme a(s) coluna(s) única(s) já existir(em)
Produto::upsert(
[
['cod_barras' => '123', 'nome' => 'Camiseta', 'valor' => 59.90],
['cod_barras' => '456', 'nome' => 'Calça', 'valor' => 129.90],
],
uniqueBy: ['cod_barras'],
update: ['nome', 'valor']
);
Processando volumes grandes — chunk() / lazy()
Carregar uma tabela inteira com get() consome memória proporcional ao
total de linhas. Pra processar em lote sem isso, use chunk()/chunkById()
(recomendado quando você atualiza/deleta linhas durante o processamento — evita pular
registros) ou lazy() (cursor, sem carregar tudo de uma vez):
// chunkById — seguro mesmo deletando/atualizando dentro do callback
Produto::where('ativo', true)->chunkById(200, function ($produtos) {
foreach ($produtos as $produto) {
$produto->recalculaEstoque();
$produto->save();
}
});
// lazy() — itera com cursor, sem materializar tudo em memória
foreach (Produto::where('ativo', true)->lazy() as $produto) {
// ...
}
API — resumo
| Método | Descrição |
|---|---|
update(array $values) | UPDATE em massa — não dispara eventos |
delete() | DELETE em massa — físico mesmo com soft delete ativo |
insert(array $rows) | INSERT em lote — não dispara eventos nem timestamps |
upsert($rows, $uniqueBy, $update) | INSERT ou UPDATE conforme chave única |
pluck($value, $key?) | Mapa simples pra combos |
chunk($n, $cb) / chunkById($n, $cb) | Processa em lotes de tamanho fixo |
lazy($chunkSize = 1000) | Cursor lazy sobre todo o resultado |
sum/avg/min/max/count($col) | Agregações de uma coluna |
Quando usar o quê
| Cenário | Abordagem |
|---|---|
| Buscar 1 por ID | Produto::find($id) |
| Buscar 1 por campo | Produto::where('campo', $v)->first() |
| Listar com filtros | Produto::where(...)->orderBy(...)->get() |
| Agregação com group by | selectRaw(...)->groupBy(...)->get() |
| Update/Delete em massa (sem precisar de eventos) | Model::where(...)->update() / ->delete() |
| Update/Delete linha a linha (precisa de eventos) | Iterar + $model->save()/delete() |
| Combos (id => label) | Produto::pluck('nome', 'id') |
| Processar tabela inteira sem esgotar memória | chunkById() / lazy() |
Próximos
- Models Eloquent — Active Record com fluent API.
- Filtros de query — WHERE seguro com placeholders.
- Query Builder fluente — encadeamento where/orderBy/take.
- Callbacks — por que update/delete em massa não dispara eventos.