Filtraggio
Le Collections sono elenchi di risorse. In un sito decoupled, sono ciò che usi per creare, lato client, elementi come un elenco “Nuovi contenuti” o una sezione “I miei contenuti”.
Tuttavia, quando effettui una richiesta non filtrata a un endpoint di una collection come /jsonapi/node/article
, otterrai semplicemente tutti gli articoli che sei autorizzato a vedere.
Senza filtri, non puoi ottenere solo i tuoi articoli o solo articoli sui lama.
Questa guida ti insegnerà a costruire filtri come un professionista.
Avvio rapido
Il filtro più semplice e comune è un filtro chiave-valore:
?filter[field_name]=value&filter[field_other]=value
Questo corrisponde a tutte le risorse con “field_name” uguale a “value” e “field_other” uguale a “value”.
Per tutto il resto, continua a leggere!
Riepilogo
Il modulo JSON:API dispone di funzionalità di filtraggio tra le più robuste e ricche di funzionalità in circolazione. Tutta questa potenza comporta però una certa curva di apprendimento.
Alla fine di questo articolo, sarai in grado di eseguire query complesse e ragionare sui problemi che potresti incontrare, come “come ottengo un elenco degli articoli di un autore sui lama o sul membro più veloce del regno animale, il falco pellegrino?”
Partiremo dalle basi. Dopo, ti mostreremo alcune scorciatoie che rendono la scrittura dei filtri un po’ più veloce e meno verbosa. Infine, vedremo una serie di esempi di filtri tratti dal mondo reale.
Se non sei nuovo a Drupal, probabilmente hai già usato il modulo Views per questo tipo di cose. A differenza del modulo REST incluso in Drupal Core, JSON:API non esporta i risultati delle Views. Le collections sono la sostituzione “API-First” di JSON:API per le “REST display” esportate in Views.
Creare filtri
Gli elementi fondamentali dei filtri di JSON:API sono le condizioni e i gruppi. Le condizioni affermano che qualcosa è vero e i gruppi ti permettono di comporre tali asserzioni in insiemi logici per creare gruppi di condizioni più grandi. Questi insiemi possono essere annidati per creare query super fini. Puoi pensare a questi insiemi annidati come a un albero:
Rappresentazione convenzionale:
a( b() && c( d() || e() ) )
Rappresentazione ad albero:
a
/ \
b & c
/ \
d | e
In entrambe le rappresentazioni:
“d” ed “e” sono membri di “c” in un gruppo OR.
“b” e “c” sono membri di “a” in un gruppo AND.
Quindi, cosa c’è dentro una condizione?
Andiamo sul logico 🖖. Ricorda, una condizione ti dice una cosa VERA o FALSA su una risorsa e su un’asserzione che fai su di essa, come “questa entità è stata creata da un particolare utente?” Quando la condizione è FALSA per una risorsa, quella risorsa non sarà inclusa nella collection.
Una condizione ha 3 parti principali: un path, un operatore e un valore.
- Un “path” identifica un campo su una risorsa
- Un “operatore” è un metodo di confronto
- Un “valore” è la cosa rispetto a cui confronti
In pseudo-codice, una condizione è una cosa che assomiglia a questo:
($field !== 'space')
Dove:
$field
è il campo della risorsa identificato dal suo “path”- l’“operatore” è
!==
- il “valore” è la stringa
'space'
Nel modulo JSON:API, non possiamo renderlo così elegante perché dobbiamo farlo funzionare all’interno di una query string URL. Per farlo, rappresentiamo ogni condizione con coppie chiave/valore.
Se stessimo filtrando sul nome di battesimo di un utente, una condizione potrebbe assomigliare a questo:
?filter[a-label][condition][path]=field_first_name
&filter[a-label][condition][operator]=%3D <- simbolo "=" codificato
&filter[a-label][condition][value]=Janis
Nota che abbiamo messo un’etichetta all’interno della prima coppia di parentesi quadre. Avremmo potuto facilmente chiamarla b-label
o this_is_my_super_awesome_label
o anche un intero come 666
🤘😅. Il punto è che ogni condizione o gruppo dovrebbe avere un identificatore.
Ma cosa succede se abbiamo un sacco di Janis nel sistema?
Aggiungiamo un altro filtro in modo da ottenere solo le Janis con un cognome che inizia con “J”:
?filter[first-name-filter][condition][path]=field_first_name
&filter[first-name-filter][condition][operator]=%3D <- "=" codificato
&filter[first-name-filter][condition][value]=Janis
&filter[last-name-filter][condition][path]=field_last_name
&filter[last-name-filter][condition][operator]=STARTS_WITH
&filter[last-name-filter][condition][value]=J
Forse il plurale di Janis è “Janii” 🤔...
Ci sono molti più operatori di filtro oltre a =
e STARTS_WITH
. Ecco l’elenco completo preso direttamente dal codebase di JSON:API:
\Drupal\jsonapi\Query\EntityCondition::$allowedOperators = [
'=', '<>',
'>', '>=', '<', '<=',
'STARTS_WITH', 'CONTAINS', 'ENDS_WITH',
'IN', 'NOT IN',
'BETWEEN', 'NOT BETWEEN',
'IS NULL', 'IS NOT NULL',
];
Gli operatori simbolici devono essere codificati nell’URL. Puoi ottenere la codifica corretta con la funzione urlencode()
di PHP.
Gruppi di condizioni
Ora sappiamo come costruire condizioni, ma non ancora come costruire gruppi di condizioni. Come costruiamo un albero come quello visto sopra?
Per farlo, abbiamo bisogno di un “gruppo”. Un gruppo è un insieme di condizioni unite da una “congiunzione”. Tutti i gruppi hanno congiunzioni e una congiunzione è AND oppure OR.
Ora il nostro filtro è un po’ troppo specifico! Diciamo che vogliamo trovare tutti gli utenti con un cognome che inizia con “J” e che abbiano o il nome “Janis” o il nome “Joan”.
Per farlo, aggiungiamo un gruppo:
?filter[rock-group][group][conjunction]=OR
Poi dobbiamo assegnare i nostri filtri a quel nuovo gruppo.
Per farlo, aggiungiamo una chiave memberOf
. Ogni condizione e gruppo può avere una chiave memberOf
.
Suggerimento: i gruppi possono avere una chiave memberOf
proprio come le condizioni, il che significa che possiamo avere gruppi di gruppi 🤯!
Nota: ogni filtro senza una chiave memberOf
si considera parte di un “gruppo radice” con congiunzione AND.
Tutto insieme ora:
?filter[rock-group][group][conjunction]=OR
&filter[janis-filter][condition][path]=field_first_name
&filter[janis-filter][condition][operator]=%3D
&filter[janis-filter][condition][value]=Janis
&filter[janis-filter][condition][memberOf]=rock-group
&filter[joan-filter][condition][path]=field_first_name
&filter[joan-filter][condition][operator]=%3D
&filter[joan-filter][condition][value]=Joan
&filter[joan-filter][condition][memberOf]=rock-group
&filter[last-name-filter][condition][path]=field_last_name
&filter[last-name-filter][condition][operator]=STARTS_WITH
&filter[last-name-filter][condition][value]=J
Ti sembra familiare?
Dovrebbe: lo abbiamo visto sopra come albero:
a a = root-and-group
/ \
/ \ b = last-name-filter
b c c = rock-group
/ \
/ \ d = janis-filter
d e e = joan-filter
Puoi annidare questi gruppi in profondità quanto desideri.
Path
Le condizioni hanno un’ultima caratteristica: i “path”.
I path forniscono un modo per filtrare in base ai valori delle relazioni.
Fino a questo punto, abbiamo filtrato solo per i campi ipotetici field_first_name
e field_last_name
sulla risorsa utente.
Immaginiamo di voler filtrare per il nome della carriera di un utente, dove i tipi di carriera sono memorizzati come risorsa separata. Potremmo aggiungere un filtro come questo:
?filter[career][condition][path]=field_career.name
&filter[career][condition][operator]=%3D
&filter[career][condition][value]=Rockstar
I path usano una “dot notation” per attraversare le relazioni.
Se una risorsa ha una relazione, puoi aggiungere un filtro su di essa concatenando il nome del campo di relazione e il nome del campo della relazione con un .
(punto).
Puoi persino filtrare per relazioni di relazioni (e così via) semplicemente aggiungendo altri nomi di campo e punti.
Suggerimento: puoi filtrare su un indice specifico di una relazione inserendo un intero non negativo nel path. Quindi il path some_relationship.1.some_attribute
filtrerà solo per la seconda risorsa correlata.
Suggerimento: puoi filtrare per sotto-proprietà di un campo. Ad esempio, un path come field_phone.country_code
funzionerà anche se field_phone
non è una relazione.
Suggerimento: quando filtri contro proprietà di configurazione, puoi usare un asterisco (*) per sostituire qualsiasi porzione di un path. Ad esempio, /jsonapi/field_config/field_config?filter[dependencies.config.*]=comment.type.comment
corrisponderà a tutte le configurazioni di campo in cui ["attributes"]["dependencies"]["config"]
(un array indicizzato) contiene il valore “comment.type.comment”.
Scorciatoie
È un sacco di caratteri da digitare. La maggior parte delle volte non hai bisogno di filtri così complicati e, per questi casi, il modulo JSON:API offre alcune “scorciatoie” per aiutarti a scrivere i filtri più velocemente.
Quando l’operatore è =
, non devi includerlo. È semplicemente sottinteso. Quindi:
?filter[a-label][condition][path]=field_first_name
&filter[a-label][condition][operator]=%3D <- simbolo "=" codificato
&filter[a-label][condition][value]=Janis
diventa
?filter[janis-filter][condition][path]=field_first_name
&filter[janis-filter][condition][value]=Janis
È anche raro che tu debba filtrare due volte per lo stesso campo (anche se è possibile). Quindi, quando l’operatore è =
e non hai bisogno di filtrare due volte per lo stesso campo, il path può essere l’identificatore. Quindi:
?filter[janis-filter][condition][path]=field_first_name
&filter[janis-filter][condition][value]=Janis
diventa
?filter[field_first_name][value]=Janis
Quel value
extra è fastidioso. Ecco perché puoi ridurre i controlli di uguaglianza più semplici a una forma chiave-valore:
?filter[field_first_name]=Janis
Filtri e controllo degli accessi
Prima, un avvertimento: non confondere i filtri con il controllo degli accessi. Solo perché hai scritto un filtro per rimuovere qualcosa che un utente non dovrebbe poter vedere, non significa che non sia accessibile. Esegui sempre i controlli di accesso sul backend.
Detto questo, parliamo di come usare i filtri per integrare il controllo degli accessi. Per migliorare le prestazioni, dovresti filtrare ciò che i tuoi utenti non potranno vedere. La richiesta di supporto più frequente nelle issue di JSON:API può essere risolta con questo semplice trucco!
Se sai che i tuoi utenti non possono vedere contenuti non pubblicati, aggiungi il seguente filtro:
?filter[status][value]=1
Usando questo metodo, ridurrai il numero di richieste non necessarie che devi effettuare. Questo perché JSON:API non restituisce dati per risorse a cui un utente non ha accesso. Puoi vedere quali risorse potrebbero essere state interessate ispezionando la sezione meta.errors
del documento JSON:API.
Quindi, fai del tuo meglio per filtrare in anticipo le risorse inaccessibili.
Esempi di filtri
1. Ottenere solo i nodi pubblicati
Uno scenario molto comune è caricare solo i nodi che sono pubblicati. Questo è un filtro molto semplice da aggiungere.
CORTO
filter[status][value]=1
NORMALE
filter[status-filter][condition][path]=status
filter[status-filter][condition][value]=1
2. Ottenere nodi in base al valore di un riferimento a entità
Una strategia comune è filtrare i contenuti tramite un riferimento a entità.
CORTO
filter[uid.id][value]=BB09E2CD-9487-44BC-B219-3DC03D6820CD
NORMALE
filter[author-filter][condition][path]=uid.id
filter[author-filter][condition][value]=BB09E2CD-9487-44BC-B219-3DC03D6820CD
Per conformarsi pienamente alla specifica JSON API, mentre Drupal internamente usa la proprietà uuid
, JSON API usa invece id
.
Da Drupal 9.3 è possibile filtrare anche su target_id
invece di filtrare solo per la proprietà uuid
.
CORTO
filter[field_tags.meta.drupal_internal__target_id]=1
NORMALE
filter[name-filter][condition][path]=field_tags.meta.drupal_internal__target_id
filter[name-filter][condition][value]=1
3. Filtri annidati: ottenere i nodi creati dall’utente admin
È possibile filtrare su campi di entità referenziate come utente, tassonomia o qualsiasi campo di riferimento a entità. Puoi farlo facilmente usando la notazione seguente: reference_field.nested_field. In questo esempio il campo di riferimento è uid
per l’utente e name
è un campo dell’entità utente.
CORTO
filter[uid.name][value]=admin
NORMALE
filter[name-filter][condition][path]=uid.name
filter[name-filter][condition][value]=admin
4. Filtrare con array: ottenere i nodi creati dagli utenti [admin, john]
Puoi fornire più valori a un filtro in cui cercare. Oltre alle chiavi field e value, puoi aggiungere un operatore alla tua condizione. Di solito è “=”, ma puoi anche usare “IN”, “NOT IN”, “>”, “<”, “<>”, “BETWEEN”.
Per questo esempio useremo l’operatore IN. Nota che ho aggiunto due parentesi quadre dietro il value per trasformarlo in un array.
NORMALE
filter[name-filter][condition][path]=uid.name
filter[name-filter][condition][operator]=IN
filter[name-filter][condition][value][1]=admin
filter[name-filter][condition][value][2]=john
Suggerimento: quando utilizzi le parentesi quadre per filtri con valori multipli, non usare solo parentesi vuote per un nuovo valore.
Sebbene funzionino quando digitati nell’URL, Guzzle e altri client HTTP creeranno un solo valore perché la chiave dell’array sarà considerata uguale e sovrascriverà il valore precedente. È meglio usare un indice per creare elementi di array univoci.
5. Raggruppare i filtri: ottenere i nodi che sono pubblicati e creati da admin
Ora combiniamo alcuni degli esempi precedenti e creiamo il seguente scenario.
WHERE user.name = admin AND node.status = 1;
filter[and-group][group][conjunction]=AND
filter[name-filter][condition][path]=uid.name
filter[name-filter][condition][value]=admin
filter[name-filter][condition][memberOf]=and-group
filter[status-filter][condition][path]=status
filter[status-filter][condition][value]=1
filter[status-filter][condition][memberOf]=and-group
Non devi davvero aggiungere l’and-group ma io di solito lo trovo più semplice.
6. Raggruppare gruppi di filtri: ottenere i nodi che sono promossi (promoted) o sticky e creati da admin
Come menzionato nella sezione sui gruppi, puoi inserire gruppi dentro altri gruppi.
WHERE (user.name = admin) AND (node.sticky = 1 OR node.promoted = 1)
Per farlo mettiamo sticky
e promoted
in un gruppo con congiunzione OR. Creiamo un gruppo con congiunzione AND e mettiamo
il filtro “admin” e il gruppo OR “promoted/sticky” al suo interno.
# Crea un gruppo AND e uno OR
filter[and-group][group][conjunction]=AND
filter[or-group][group][conjunction]=OR
# Inserisci il gruppo OR nel gruppo AND
filter[or-group][group][memberOf]=and-group
# Crea il filtro admin e inseriscilo nel gruppo AND
filter[admin-filter][condition][path]=uid.name
filter[admin-filter][condition][value]=admin
filter[admin-filter][condition][memberOf]=and-group
# Crea il filtro sticky e inseriscilo nel gruppo OR
filter[sticky-filter][condition][path]=sticky
filter[sticky-filter][condition][value]=1
filter[sticky-filter][condition][memberOf]=or-group
# Crea il filtro promoted e inseriscilo nel gruppo OR
filter[promote-filter][condition][path]=promote
filter[promote-filter][condition][value]=1
filter[promote-filter][condition][memberOf]=or-group
7. Filtrare i nodi in cui “title” CONTAINS “Foo”
CORTO
filter[title][operator]=CONTAINS&filter[title][value]=Foo
NORMALE
filter[title-filter][condition][path]=title
filter[title-filter][condition][operator]=CONTAINS
filter[title-filter][condition][value]=Foo
8. Filtrare per campi complessi non standard (ad es. addressfield)
FILTRARE PER LOCALITÀ
filter[field_address][condition][path]=field_address.locality
filter[field_address][condition][value]=Mordor
FILTRARE PER RIGA INDIRIZZO
filter[address][condition][path]=field_address.address_line1
filter[address][condition][value]=Rings Street
9. Filtrare sui valori dei termini di tassonomia (ad es. tag)
Per filtrare dovrai usare il machine name del vocabolario e il campo presente sul tuo nodo.
filter[taxonomy_term--tags][condition][path]=field_tags.name
filter[taxonomy_term--tags][condition][operator]=IN
filter[taxonomy_term--tags][condition][value][]=tagname
10. Filtrare per data (solo data, senza ora)
Le date sono filtrabili. Passa una stringa temporale conforme al formato ISO-8601.
Questo esempio è per un campo Data impostato su “solo data” (senza ora).
filter[datefilter][condition][path]=field_test_date
filter[datefilter][condition][operator]=%3D
filter[datefilter][condition][value]=2019-06-27
Questo esempio è per un campo Data che supporta data e ora.
filter[datefilter][condition][path]=field_test_date
filter[datefilter][condition][operator]=%3D
filter[datefilter][condition][value]=2019-06-27T16%3A00%3A00
Nota che i campi timestamp (come created
o changed
) attualmente devono usare un timestamp per il filtraggio:
filter[recent][condition][path]=created
filter[recent][condition][operator]=%3D
filter[recent][condition][value]=1591627496
11. Filtrare su campi array vuoti
Questo esempio riguarda un campo Checkbox/Radio senza alcun valore selezionato. Supponiamo che tu abbia un campo checkbox. Vorresti ottenere tutti i nodi che non hanno quel valore selezionato. Quando selezionato, JSON API restituisce un array:
"my_field":["checked"]
Quando non selezionato, JSON API restituisce un array vuoto:
"my_field": []
Se desideri ottenere tutti i campi non selezionati, devi usare IS NULL sull’array come segue (senza un valore):
filter[my-filter][condition][path]=my_field
filter[my-filter][condition][operator]=IS NULL
Articolo tratto dalla Documentazione di Drupal.