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.
Altri articoli recenti
Laravel AI 1.1 e 1.2: Agent Skills, Cohere e structured output più solidi
Le release 1.1.0 e 1.2.0 di laravel/ai portano gli Agent Skills, l'approvazione obbligatoria per i tool built-in e nuovi provider come Cohere e Laya self-hosted. Sul fronte qualità arrivano i fix allo structured output per Bedrock e il supporto alle provider options. Aggiornati anche i modelli di default, con Claude 4.7+ che ignora i sampling params.
Laravel 13.35.0: scheduler più controllabile, code più flessibili e response Markdown
Laravel 13.35.0 porta Schedule::alwaysOnOneServer(), il supporto a schedule:interrupt per i comandi di lunga durata, --memory in percentuale per queue:work e il nuovo metodo markdown() sulla response factory. Tante piccole migliorie anche a test, view compilate in parallelo e dischi local.
BashBox: un interprete Bash in sandbox per agenti AI in PHP
BashBox è un interprete bash scritto in puro PHP che permette di eseguire gli script generati dagli agenti AI senza mai ricorrere a proc_open, exec o shell_exec. Ogni comando è una classe PHP che lavora su un filesystem virtuale, con limiti di esecuzione, quote disco e accesso di rete configurabili. Ideale per chi sviluppa agenti con il Laravel AI SDK o altre librerie simili.