Filtros de query
WHERE seguro com bindings: array-DSL :filters, :query, subselects parametrizados.
Filtrar registros com segurança no MAD é, na prática, usar os parâmetros
automaticamente parametrizados (prepared statements) do Eloquent Query
Builder — não existe mais uma classe própria pra montar WHERE. Esta página
cobre como construir filtros com segurança em código PHP e a prop
:filters/:query que os componentes mad-db*
(mad-dbcombo-field, mad-dbradio-field,
mad-dbcheckbox-group-field, mad-db-chart,
mad-db-metric-card, etc.) usam por baixo dos panos pra filtrar a query
automática deles.
Binding automático — nada de placeholders manuais
Todo valor passado para where()/whereIn()/whereBetween()
vira um bind PDO automaticamente — o Eloquent nunca interpola o valor direto na
string SQL. Você não precisa (e não deve) escapar nada manualmente:
// Seguro — $busca vira um bind, nunca é concatenado na SQL
$itens = Produto::where('nome', 'like', "%{$busca}%")->get();
// Seguro — array inteiro vira N binds
$itens = Produto::whereIn('categoria_id', $categoriaIds)->get();
IN / NOT IN
// Array de valores
Produto::whereIn('status_id', [1, 2, 3])->get();
Produto::whereNotIn('tipo', ['X', 'Y'])->get();
// Subquery — outro Builder, NÃO uma string SQL — continua 100% parametrizado
Produto::whereIn('id', function ($q) {
$q->select('produto_id')
->from('pessoa_grupo')
->where('grupo_pessoa_id', GrupoPessoa::VENDEDOR);
})->get();
// Equivalente passando um Builder pronto em vez de closure
$sub = PessoaGrupo::select('produto_id')->where('grupo_pessoa_id', GrupoPessoa::VENDEDOR);
Produto::whereIn('id', $sub)->get();
Esta é a forma moderna do antigo "subselect com bindParams": em vez de montar a string
(SELECT ...)à mão e passar um array de binds posicionais à parte, você só monta outroBuilder— os binds são resolvidos pelo Eloquent automaticamente, sem risco de esquecer um?ou desalinhar a ordem dos parâmetros.
BETWEEN
Produto::whereBetween('dt_pedido', ['2024-01-01', '2024-12-31'])->get();
Produto::whereNotBetween('valor', [0, 10])->get();
Agrupamento (AND/OR entre 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')
Ver Query Builder fluente pra mais exemplos de encadeamento, paginação e busca multi-coluna.
Quando precisar de SQL bruto — sempre com bindings
Pra casos que o Builder fluente não cobre (expressão SQL específica do banco,
subquery complexa demais pra closure), use whereRaw() — mas o valor
sempre entra como bind, nunca concatenado na string:
// CERTO — placeholder posicional + array de binds
Produto::whereRaw('id IN (SELECT produto_id FROM pessoa_grupo WHERE grupo_pessoa_id = ?)', [
GrupoPessoa::VENDEDOR,
])->get();
// CERTO — múltiplos binds, na mesma ordem dos '?'
Produto::whereRaw(
'id IN (SELECT produto_id FROM pessoa_grupo WHERE grupo_pessoa_id = ? AND ativo = ?)',
[GrupoPessoa::VENDEDOR, true]
)->get();
Esse é exatamente o mecanismo por trás de Mad\Database\QuerySource::applyArrayFilters(),
o sink server-side do array-DSL :filters descrito abaixo — quando o
valor do filtro é uma string SELECT ..., o framework gera
whereRaw("{$campo} in ({$sql})", $binds) internamente, nunca interpolando
o valor do usuário direto na SQL.
Array-DSL — prop :filters dos componentes db*
Componentes que buscam options/dados direto do banco (mad-dbcombo-field,
mad-dbselect-field, mad-dbradio-field,
mad-dbcheckbox-group-field, mad-dbmulti-search-field,
mad-dbunique-search-field, mad-dbchecklist-field,
mad-db-chart, mad-db-metric-card, mad-tree-view,
mad-pivot-table, entre outros) aceitam uma prop :filters —
um array de tuplas [campo, operador, valor] aplicado direto no Query
Builder por Mad\Database\QuerySource::applyArrayFilters():
<mad-dbcombo-field name="vendedor_id" label="Vendedor" model="Pessoa" display="nome"
:filters="[['ativo', '=', true]]" />
<mad-dbcombo-field name="fornecedor_id" label="Fornecedor" model="Pessoa" display="nome"
:filters="[
['ativo', '=', true],
['tipo', 'in', ['fornecedor', 'misto']],
]" />
<mad-dbcombo-field name="vendedor_id" label="Vendedor" model="Pessoa" display="nome"
:filters="[
['id', 'in', 'SELECT pessoa_id FROM pessoa_grupo WHERE grupo_pessoa_id = ?', [GrupoPessoa::VENDEDOR]],
]" />
| Operador | Comportamento |
|---|---|
= (default se omitido) | where($campo, $valor) |
!=, >, <, >=, <=, like | where($campo, $op, $valor) |
in | whereIn($campo, (array) $valor) |
not in | whereNotIn($campo, (array) $valor) |
is null | whereNull($campo) |
is not null | whereNotNull($campo) |
in / not in com valor "SELECT ..." | whereRaw("{$campo} [not] in ({$sql})", $binds) — subselect parametrizado |
Nos
:filters, o subselect não leva parênteses na string — o framework já envolve em(...)ao montar owhereRaw. Os?são posicionais e resolvidos pelo array no 4º elemento da tupla, na mesma ordem.
:query — Eloquent/Query Builder direto
Pra filtros que o array-DSL não cobre (joins, orWhere aninhado,
closures), passe um Builder já pronto via :query — tem
prioridade sobre model + filters e dispensa
model/database (vêm do próprio builder). Global scopes
(soft-delete, tenant) são preservados automaticamente:
<mad-dbcombo-field name="vendedor_id" label="Vendedor" display="nome"
:query="$that->vendedoresAtivos()" />
// No MadComponent / MadForm
public function vendedoresAtivos(): \Illuminate\Database\Eloquent\Builder
{
return Pessoa::where('ativo', true)
->whereHas('grupos', fn ($q) => $q->where('grupo_pessoa_id', GrupoPessoa::VENDEDOR));
}
:queryé incompatível comdepends-on(cascata pai→filho): a cascata serializa a configuração da query num token criptografado pra reidratar no servidor a cada mudança do campo pai, e umBuildervivo (PDO + closures) não serializa. Comdepends-on, usemodel+:filters(array-DSL, 100% serializável) — o componente lança erro explícito se você tentar combinar:querycomdepends-on.
NUNCA fazer
// ❌ ERRADO — concatenação direta = SQL injection
$itens = Produto::whereRaw("nome = '{$busca}'")->get();
$itens = Produto::whereRaw("id IN (SELECT produto_id FROM pessoa_grupo WHERE grupo_pessoa_id = {$grupoId})")->get();
// ✅ CERTO — bind, sempre
$itens = Produto::where('nome', $busca)->get();
$itens = Produto::whereRaw('id IN (SELECT produto_id FROM pessoa_grupo WHERE grupo_pessoa_id = ?)', [$grupoId])->get();
<mad-dbcombo-field :filters="[['id', 'in', "SELECT produto_id FROM x WHERE y = {$id}"]]" />
<mad-dbcombo-field :filters="[['id', 'in', 'SELECT produto_id FROM x WHERE y = ?', [$id]]]" />
Resumo — quando usar o quê
| Cenário | Abordagem |
|---|---|
| Filtro simples em código PHP | Model::where($campo, $op, $valor) |
| IN com subquery | whereIn($campo, function ($q) {...}) ou outro Builder |
| SQL específico do banco, sem equivalente fluente | whereRaw($sql, $binds) — nunca concatenar |
Filtro declarado em prop Blade de componente db* | :filters="[[campo, op, valor], ...]" |
Filtro complexo (join, orWhere aninhado) em componente db* | :query="$that->metodo()" |
:filters/:query com depends-on (cascata) | Só model + :filters — :query não serializa |
Próximos
- Query Builder fluente — encadeamento where/orderBy/limit.
- Models Eloquent — API base do model.
- Operações em massa — update/delete em lote.
- mad-dbcombo-field — referência completa de props.