La classe WP_Query di cui abbiamo parlato nel precedente articolo è molto potente e versatile, ma potrebbe non metterci a disposizione tutti gli strumenti richiesti per un determinato progetto, e può sorgere spontanea l'esigenza di interrogare direttamente il database di WordPress formulando la query in linguaggio SQL. Per rendere più semplice anche questa operazione WordPress possiede uno strumento ad hoc: la classe wpdb.
Richiamare la classe
A differenza di WP_Query non abbiamo la necessità di istanziare la classe, anzi, la documentazione ufficiale ci sconsiglia di farlo, è infatti già istanziato un oggetto con la connessione al database denominato, appunto, $wpdb
. Utilizzandolo potremo richiamare tutti i metodi della classe:
<?php
global $wpdb;
$wpdb->query("SELECT * FROM $wpdb->comments");
?>
In questo primo esempio abbiamo dovuto globalizzare l'oggetto $wpdb
- operazione necessaria prima di ogni altra - per poi applicare uno dei suoi metodi, chiamato semplicemente query
, per eseguire una qualsiasi query SQL sul database utilizzato dal CMS. È anche interessante notare che non abbiamo dovuto indicare per intero il nome della tabella da utilizzare, comprensivo cioè del prefisso utilizzato da quella particolare installazione, ma ci siamo serviti sempre dello stesso oggetto tramite i metodi chiamati come le tavole create da WordPress per farlo generare automaticamente.
I metodi di $wpdb
I metodi più utilizzati per $wpdb
sono get_results
, get_var
, get_row
e get_col
. Il primo metodo restituisce i tutti i risultati trovati dalla query passata come primo parametro, mentre col secondo parametro si può determinare il tipo di output: di default un array indicizzato numericamente di un oggetto per ogni riga.
Per sfrutttare al meglio questo meotodo (e anche i successivi) è meglio conoscere le tabelle che compongoono il database di WordPress: posts, utenti, commenti e opzioni hanno una tabella ciascuno (posts, users, comments, options) mentre le tassonomie e i custom fields sono gestiti con tabelle in relazione con quelle principali. Le tassonome inserite nel sito si trovano elencate dentro la tabella terms, relazionate da un lato con la tabella term_taxonomy che ne marca la tipologia, e dall'altro con la tabella term_relationship che le collega ai post. I valori inseriti tramite custom fields sono invece nella tabella postmeta, nella quale la colonna meta_key ne specifica il nome, e la colonna meta_value il valore. Questa tabella è poi relazionata a quella principale posts tramite la colonna post_id.
Potremo quindi scrivere una query d'esempio che elenca quelle tassonomie del tipo "category" per le quali i post relativi hanno "si" come valore del custom field "inedito":
<?php
$query = "SELECT DISTINCT name, slug
FROM $wpdb->terms wpterms
LEFT JOIN $wpdb->term_taxonomy wptermstaxonomy ON (wpterms.term_id = wptermstaxonomy.term_id)
LEFT JOIN $wpdb->term_relationships wptermrelation ON (wptermrelation.term_taxonomy_id = wpterms.term_id)
LEFT JOIN $wpdb->posts wpposts ON (wptermrelation.object_id = wpposts.ID)
LEFT JOIN $wpdb->postmeta wppostmeta ON (wppostmeta.post_id = wpposts.ID) WHERE wptermstaxonomy.taxonomy = 'category' AND wppostmeta.meta_key = 'inedito' AND wppostmeta.meta_value = 'si'";
$risultati = $wpdb->get_results($query);
foreach ($categorie as $caegoria) {
echo "<p>" . $caegoria->name . "</p>";
}
?>
Tramite i join abbiamo costruito il prodotto cartesiano delle tabelle necessarie per la relazione, ed infine abbiamo posto la condizione con il costrutto where
. Gli altri metodi che abbiamo elencato utilizzano la stessa logica, ma sono già ottimizzati per ottenere in risposta un singolo valore (get_var
) un singolo record (get_row
), o una specifica colonna (get_col
). WordPress implementa anche metodi ottimizzati per la lettura di un singolo valore (get_var
), di una riga (get_row
) o di una colonna (get_col
).
I metodi non sono solo di lettura, esistono anche i metodi per inserire e aggiornare dati, essi hanno una forma molto semplice a tre parametri: la tabella in cui avviene l'operazione, i dati da inseririre o modificare, e infine un array che nel caso dell'inserimento presenta i tipi di dati, mentre nel caso dell'aggiornamento contiene i WHERE della query, sempre in formato "nome (della colonna) - valore":
<?php
//Inserimento dati
$wpdb->insert($wpdb->post, //Tabella
array('post_title' => 'Titolo del post',
'post_content' => 'Contenuto del post'), //Array dei contenuti
array('%s', '%s') //Array del tipo di dati
);
//Aggiornamento dei dati
$wpdb->update($wpdb->post, //Tabella
array('post_title' => 'Titolo modificato',
'post_content' => 'contento modificato'), //Array dei contenuti
array('ID' => 10) //Array delle condizioni WHERE
);
?>
Attenzione alla sicurezza
Il metodo "query", come si è detto, consente l'esecuzione di una qualsiasi query scritta in SQL; nel caso che in quest'ultima vengano collocati valori inseriti dall'utente siamo a rischio di SQL Injection. Abbiamo comunque la possibilità di premunirci tramite il metodo "prepare", al quale passeremo la query con i valori indicati da segnaposto, e un array con i valori effettivi:
<?php
$wpdb->query( $wpdb->prepare(
"INSERT INTO $wpdb->posts (post_title, post_content) VALUES ( %s, %s ) WHERE ID = %d",
array(
'Nuovo titolo',
'Nuovo contenuto',
10
)
));
?>
Le altre funzioni di cui abbiamo parlato comprendono già l'escape di eventuali espressioni pericolose.
Un esempio di query avanzata: restituire i risultati in ordine di distanza.
Per fare un esempio di utilizzo, proponiamo un modo per ordinare una serie di risultati per ordine crescente di distanza. La distanza fra due punti può essere calcolata tramite formula dell'emisenoverso, che ha come parametri la latitudine e la longitudine dei due punti geografici. Se abbiamo a disposizione la posizione dell'utente che sta consultando il sito, potremo costruire una query che ordina i risultati (esercizi commerciali, servizi, ecc.) sulla base della distanza. È necessario avere a disposizione prima le coordinate dei diversi custom post type memorizzati come custom fields, in quanto richiedere ad un servizio come Google Maps il geocoding in tempo reale per ciascun risultato non risulterenbbe pratico nella costruzione della query, e sarebbe fermato dal server dopo un certo numero di richieste provenienti dalla stessa fonte.
Il codice risultante è il seguente:
<?php
$user_lat = 44.5353244; //Esempio di latitudine dell'utente
$user_lng = 11.3505099; //Esempio di longitudine dell'utente
/*lat e lng sono custom fields*/
$query = "SELECT id, post_title, post_content, lat, lng,
6371 * 2 * ASIN(SQRT( POWER(SIN(($user_lat - lat) * pi()/180 / 2), 2) + COS($user_lat * pi()/180) * COS($user_lat * pi()/180) * POWER(SIN(($user_lng - lng) * pi()/180 / 2), 2) )) AS distanza
FROM
( SELECT id, post_title, post_content, lat.meta_value AS lat, lng.meta_value AS lng
FROM $wpdb->posts posts
INNER JOIN $wpdb->postmeta lat ON ( lat.post_id = posts.ID AND lat.meta_key = 'latitudine')
INNER JOIN $wpdb->postmeta lng ON ( lng.post_id = posts.ID AND lng.meta_key = 'longitudine')
)
AS subquery ORDER BY distanza";
$wpdb->get_results($query); //Lettura dei risultati
foreach ( $risultati as $risultato ) { //Stampo l'elenco dei risultati
echo "<h1>" . $risultato->post_title . "</h1>";
echo "<p>" . $risultato->post_content . "</p>";
echo "<p>Distanza: " . $risultato->distanza . "</p>";
}
?>
Per poter utilizzare il valore calcolato "distanza" come parametro per la specifica ORDER BY
abbiamo dovuto costrire una subquery nella quale i valori lat
e lng
fossero espressamente indicati. Un meccanismo di questo tipo è molto comune in portali che raccolgono informazioni su attività o servizi di un certo territorio, e li propongono all'utente sulla base della sua localizzazione dell'indicazione esplicita di una posizione di partenza.
Conclusioni
In questa trattazione è stato ulteriormente approfondito il discorso riguardante a classe WP_Query di WordPress con particolare riferimento ai metodi per interrogare direttamente il database dell'applicazione formulando la query in linguaggio SQL.