Query Builder fluente
where().orderBy().limit().get() encadeado — Eloquent Builder nativo.
O Query Builder do MAD é o Eloquent Builder nativo do Laravel —
encadeamento de métodos (where, orderBy, limit,
get) chamados direto na classe do model. Não existe camada própria por
cima: tudo aqui é Eloquent puro.
Encadeamento básico
$produtos = Produto::where('ativo', true)
->where('valor', '>=', 100)
->orderBy('nome', 'asc')
->limit(50)
->get();
Operadores
| Operador | Uso |
|---|---|
= | where('campo', $valor) — = é o default, pode omitir |
!=, <> | where('campo', '!=', $valor) |
>, <, >=, <= | Comparação numérica/data |
like | where('campo', 'like', "%{$busca}%") |
in / not in | whereIn('campo', $array) / whereNotIn(...) |
is null | whereNull('campo') / whereNotNull('campo') |
between | whereBetween('campo', [$ini, $fim]) |
OR e agrupamento
// AND (default)
Produto::where('ativo', true)->where('tipo', 'A');
// → WHERE ativo = 1 AND tipo = 'A'
// OR
Produto::where('tipo', 'A')->orWhere('tipo', 'B');
// → WHERE tipo = 'A' OR tipo = 'B'
// Agrupamento — closure isola os parênteses
Produto::where('ativo', true)
->where(function ($q) {
$q->where('tipo', 'A')->orWhere('tipo', 'B');
})->get();
// → WHERE ativo = 1 AND (tipo = 'A' OR tipo = 'B')
Exemplo real do projeto (App\Models\Sys\ImportTemplate) combinando agrupamento e subquery:
return self::where('active', 'Y')
->where(function ($q) use ($groupIds, $userId) {
if (!empty($groupIds)) {
$q->whereIn('id', ImportTemplateGroup::select('template_id')
->whereIn('group_id', $groupIds));
}
$q->orWhereIn('id', ImportTemplateUser::select('template_id')
->where('user_id', $userId));
})->get()->all();
Métodos terminais (executam a query)
| Método | Retorno | Descrição |
|---|---|---|
get() | Collection | Coleção de models |
first() | Model|null | Primeiro registro do filtro |
firstOrFail() | Model | Primeiro registro; lança ModelNotFoundException |
count() | int | Total que casa o filtro |
exists() / doesntExist() | bool | Existência sem carregar registros |
sum($col), avg($col), min($col), max($col) | mixed | Agregações |
pluck($value, $key?) | Collection | Mapa chave => valor para combos |
Métodos intermediários (encadeáveis)
| Método | Descrição |
|---|---|
where($col, $op, $val) | Filtro AND (operador omitido = =) |
orWhere(...) | Filtro OR |
whereIn($col, $values) / whereNotIn(...) | IN / NOT IN — aceita array ou outro Builder (subquery) |
whereBetween($col, [$a, $b]) | BETWEEN |
whereNull($col) / whereNotNull($col) | IS NULL / IS NOT NULL |
orderBy($col, $dir = 'asc') | ORDER BY |
limit($n) / take($n) | LIMIT (sinônimos) |
offset($n) / skip($n) | OFFSET (sinônimos) |
select($cols) | SELECT col1, col2 — aceita array ou args variádicos |
groupBy($col) / having(...) | GROUP BY / HAVING |
with($relation) | Eager load — ver Relacionamentos |
Casos comuns
Paginação
// Paginação manual (limit/offset)
$page = 1;
$perPage = 20;
$itens = Produto::where('ativo', true)
->orderBy('nome')
->forPage($page, $perPage)
->get();
$total = Produto::where('ativo', true)->count();
$pages = (int) ceil($total / $perPage);
// Paginação automática do Laravel (gera meta + links)
$itens = Produto::where('ativo', true)->orderBy('nome')->paginate(20);
Busca multi-coluna
$q = trim(request('q', ''));
$itens = Produto::where('ativo', true)
->when($q !== '', function ($query) use ($q) {
$query->where(function ($w) use ($q) {
$w->where('nome', 'like', "%{$q}%")
->orWhere('codigo', 'like', "%{$q}%")
->orWhere('cod_barras', 'like', "%{$q}%");
});
})
->orderBy('nome')
->get();
when($condicao, $callback)aplica a closure só se a condição for verdadeira — evitaifespalhado em cima da query.
Agregação
// Total faturado no mês
$total = PedidoVenda::where('mes', date('Y-m'))
->where('status', 'pago')
->sum('valor_total');
// Top 10 produtos mais vendidos
$top = PedidoVendaItem::selectRaw('produto_id, SUM(quantidade) as total_qtd')
->groupBy('produto_id')
->orderByDesc('total_qtd')
->limit(10)
->get();
ORDER BY vindo do usuário — OrderGuard
Valores viram bind PDO automaticamente, mas identificadores não: coluna de
ordenação, groupBy e expressões selectRaw/groupByRaw
entram na SQL como texto. Quando esse texto vem da UI (clique no cabeçalho da grid,
configuração de chart), passe por Mad\Database\OrderGuard — a fonte única de
validação usada pela própria grid e pelos charts do framework:
use Mad\Database\OrderGuard;
// Fail-closed: lança exceção e loga [SQL-INJECTION-GUARD] se a cláusula não passar
OrderGuard::validate($ordem, 'ProdutoListagem::query');
$itens = Produto::orderByRaw($ordem)->get();
// Ou checando sem lançar
if (OrderGuard::isSafeOrderBy($ordem)) { /* ... */ }
| Método | Descrição |
|---|---|
isSafeOrderBy($clause) | Bool — cláusula ORDER BY / GROUP BY só com identificadores |
validate($clause, $context) | Igual, mas fail-closed: lança exceção e loga o contexto |
isSafeExpression($expr) | Bool — expressão de SELECT/GROUP BY raw (coluna ou função) |
validateExpression($expr, $context) | Versão fail-closed da anterior |
A allowlist aceita, por termo separado por vírgula: inteiro (posição de GROUP BY), identificador (
col), qualificado (tabela.col), alias entre aspas duplas, agregado de um argumento (SUM(col),COUNT(*),MIN(t.c)...) e direção opcionalASC/DESC. Qualquer outra coisa é recusada — é allowlist, não blacklist.
Próximos
- Models Eloquent — definição, traits, CRUD.
- Operações em massa — update/delete em lote sem carregar models.
- Filtros de query — WHERE seguro com placeholders.
- Soft delete — withTrashed, restore.