Docs›Banco de dados›Filtros de query
Banco de dados

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 outro Builder — 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]],
    ]" />
OperadorComportamento
= (default se omitido)where($campo, $valor)
!=, >, <, >=, <=, likewhere($campo, $op, $valor)
inwhereIn($campo, (array) $valor)
not inwhereNotIn($campo, (array) $valor)
is nullwhereNull($campo)
is not nullwhereNotNull($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 o whereRaw. 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 com depends-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 um Builder vivo (PDO + closures) não serializa. Com depends-on, use model + :filters (array-DSL, 100% serializável) — o componente lança erro explícito se você tentar combinar :query com depends-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árioAbordagem
Filtro simples em código PHPModel::where($campo, $op, $valor)
IN com subquerywhereIn($campo, function ($q) {...}) ou outro Builder
SQL específico do banco, sem equivalente fluentewhereRaw($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