notiziephp.it

Ricerca su più colonne con un singolo parametro URL in laravel-query-builder

Traduzione da Spatie
4 Maggio 2026 Tempo di lettura: 4 minuti
Ricerca su più colonne con un singolo parametro URL in laravel-query-builder

Abbiamo appena rilasciato la v7.3.0 di laravel-query-builder, che aggiunge un nuovo modo per raggruppare più filtri sotto un singolo parametro URL. Prima di addentrarci nella nuova funzionalità, lascia che ti mostri come funzionano le basi, così la nuova parte ha senso nel contesto.

Le basi

Ecco una configurazione tipica in un controller:

use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;

$users = QueryBuilder::for(User::class)
    ->allowedFilters(
        AllowedFilter::partial('name'),
        AllowedFilter::exact('status'),
    )
    ->get();

Con questo, il pacchetto configura due filtri che i client possono utilizzare tramite la query string.

Una richiesta a /users?filter[name]=john esegue:

SELECT * FROM users WHERE LOWER(name) LIKE '%john%'

Una richiesta a /users?filter[status]=active esegue:

SELECT * FROM users WHERE status = 'active'

E una richiesta a /users?filter[name]=john&filter[status]=active esegue:

SELECT * FROM users
WHERE LOWER(name) LIKE '%john%'
  AND status = 'active'

Più filtri nell'URL vengono uniti con AND. Questo è il comportamento predefinito ed è ciò che desideri nella maggior parte dei casi.

La parola "allowed" in allowedFilters fa un lavoro reale. Se un client cerca di filtrare per qualcosa che non hai inserito nella whitelist, il pacchetto lancia un'eccezione InvalidFilterQuery invece di ignorarla silenziosamente o, peggio, lasciarla passare. Quindi una richiesta a /users?filter[email]=acme.com contro il controller sopra risulta in:

Spatie\QueryBuilder\Exceptions\InvalidFilterQuery
Requested filter(s) `email` are not allowed.
Allowed filter(s) are `name, status`.

L'eccezione ha un codice di stato 400 Bad Request, quindi per impostazione predefinita Laravel la rende come una risposta di errore pulita al client. Non devi fare nulla di speciale per ottenere questo comportamento.

La novità: raggruppamento dei filtri

Ora immagina di volere un'unica casella di ricerca che corrisponda a più colonne. L'utente digita "john" e vuoi trovare le righe in cui name o email contiene quel valore.

Prima della v7.3.0, avresti usato un filtro di callback personalizzato:

$users = QueryBuilder::for(User::class)
    ->allowedFilters(
        AllowedFilter::exact('status'),
        AllowedFilter::callback('q', function ($query, $value) {
            $query->where('name', 'LIKE', "%{$value}%")
                ->orWhere('email', 'LIKE', "%{$value}%");
        }),
    )
    ->get();

Sembra a posto a prima vista. Ma una richiesta a /users?filter[status]=active&filter[q]=john esegue:

SELECT * FROM users
WHERE status = 'active'
  AND name LIKE '%john%'
   OR email LIKE '%john%'

Individua il bug. L'OR si lega più debolmente dell'AND, quindi questo corrisponde a ogni riga il cui email contiene "john", inclusi gli utenti inattivi. Per risolverlo, devi avvolgere i where del callback nella loro chiusura annidata. È facile dimenticarlo, e quando lo fai, restituisce silenziosamente dati errati.

Nella v7.3.0, questo diventa una riga. Ecco come registri un filtro di gruppo:

$users = QueryBuilder::for(User::class)
    ->allowedFilters(
        AllowedFilter::groupOr('q', [
            AllowedFilter::partial('name'),
            AllowedFilter::partial('email'),
        ]),
    )
    ->get();

Una richiesta a /users?filter[q]=john ora esegue:

SELECT * FROM users
WHERE (LOWER(name) LIKE '%john%' OR LOWER(email) LIKE '%john%')

Il valore john viene passato a ogni membro del gruppo, e la congiunzione li unisce. C'è anche AllowedFilter::groupAnd() se vuoi che tutti i membri corrispondano invece.

Combinare filtri di gruppo con filtri regolari

I gruppi si compongono pulitamente con il resto dei tuoi filtri. Ecco una configurazione che ha sia un filtro status a livello superiore sia un gruppo:

$users = QueryBuilder::for(User::class)
    ->allowedFilters(
        AllowedFilter::exact('status'),
        AllowedFilter::groupOr('q', [
            AllowedFilter::partial('name'),
            AllowedFilter::partial('email'),
        ]),
    )
    ->get();

Una richiesta a /users?filter[status]=active&filter[q]=john esegue:

SELECT * FROM users
WHERE status = 'active'
  AND (LOWER(name) LIKE '%john%' OR LOWER(email) LIKE '%john%')

L'intero gruppo è avvolto nelle proprie parentesi, quindi l'OR non può mai fuoriuscire nella clausola status. Questo è il modello di bug che colpiva chi scriveva il proprio filtro di callback per la ricerca su più colonne.

Puoi anche registrare più gruppi indipendenti. Ognuno diventa il proprio ambito avvolto, e sono uniti con AND tra loro. I membri possono essere qualsiasi tipo di AllowedFilter, quindi puoi mescolare partial, exact, scope e altri filtri nello stesso gruppo.

In chiusura

L'aggiornamento è completamente non-breaking. Tutti i comportamenti esistenti dei filtri sono invariati. La nuova funzionalità è documentata nella documentazione sul filtering, e la pull request di Birtan Taşkın contiene tutte le motivazioni dietro il design.

Questo è uno dei tanti pacchetti che abbiamo creato presso Spatie. Se vuoi supportare il nostro lavoro open source, considera di acquistare uno dei nostri prodotti a pagamento.

Condividi su: LinkedIn

Altri articoli recenti