martedì 18 settembre 2007

Joomla 1.5: Funzioni di Accesso al Database

Con il rilascio, avvenuto proprio in questi giorni, della versione RC2, la versione stabile di Joomla 1.5 non dovrebbe farsi attendere ancora per molto.

Anche se si trattasse di aspettare ancora qualche mese, le novità sono molte per cui è consigliabile che chi è interessato a sviluppare applicazioni (componenti, moduli o plugins) per questo cms cominci a fare conoscenza con il nuovo framework.

Io lo sto facendo ed ho pensato (e se questa sia stata una buona idea ho paura che potrete scoprirlo solo continuando a leggere) di iniziare a scrivere qualcosa sull'argomento.

Le funzioni di accesso al database sono un buon punto di partenza.

La classe JDatabase

L'interrogazione e la manipolazione dei dati contenuti nel database di sistema avviene attraverso metodi della classe JDatabase. Molti metodi che esamineremo sono metodi astratti, sono cioè soltanto definiti in JDatabase, ma implementati in classi derivate costruite per accedere ad uno specifico motore di database: nella versione attuale di Joomla queste classi sono JDatabaseMySQL e JDatabaseMySQLi rispettivamente per l'accesso a database MySQL tramite le funzioni delle estensioni PHP mysql o mysqli.

L'oggetto database di sistema

Quando si sviluppano componenti, moduli o plugins per Joomla normalmente non è necessario eseguire istruzioni di connessione a database in quanto è sufficiente ottenere un riferimento all'oggetto database di sistema e utilizzare la connessione già aperta ed inizializzata da Joomla. A questo scopo si utilizza il metodo statico getDBO() della classe JFactory. Così

$database = &JFactory::getDBO();

Tramite l'oggetto database di sistema si possono compiere tutte le normali operazioni di accesso ai dati.

Impostare una query

Prima di poter essere eseguita ogni query deve essere impostata tramite il metodo setQuery().

$database->setQuery('SELECT * FROM #__users');

Il metodo setQuery() sostituisce i caratteri #__ con il prefisso delle tabelle specificato in fase di installazione di Joomla. Se non sono state modificate le impostazioni predefinite la query impostata sopra sarà eseguita sulla tabella jos_users.

E' importante utilizzare sempre questa notazione standard quando si impostano query da eseguire sulle tabelle di sistema. Se, poniamo all'interno del codice di un componente, scrivessimo

$database->setQuery('SELECT * FROM jos_users');

la query fallirebbe in tutti i casi in cui l'utilizzatore ha modificato il prefisso predefinito per le tabelle del database di sistema.

Eseguire una query di selezione

Il framework di Joomla mette a disposizione diversi metodi per eseguire una query di selezione e memorizzare i risultati in una variabile o array.

Per i nostri esempi utilizzeremo un estratto della tabella utenti di un immaginario sito Joomla (molto immaginario!)

loadAssocList

Il metodo loadAssocList() ritorna il risultato di una query in un array associativo.

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__users');
$results = $database->loadAssocList();

$results è un array di array associativi. Per accedere ad un singolo record si usa

$results[numero_record][nome_campo]

Esempio

echo $results[0]['name'];

Produrrà come output il contenuto del campo name del primo record ritornato,

Topolino

Per iterare l'intero insieme dei risultati si può usare

for($i=0, $ct=count($results); $i < $ct; $i++) {
  echo $results[$i]['name'],' | ',$results[$i]['username'],
  ' | ',$results[$i]['email'],'<br />';
}

o, più compatto

foreach($results as $r) {
  echo $r['name'],' | ',$r['username'],' | ', $r['email'],
  '<br />';
}

In entrambi i casi l'output è

Topolino | topolino | topolino@gmail.com
Paperon De' Paperoni | paperone | paperone@hotmail.com
Pietro Gambadilegno | pgamba | pgamba@msn.com
Eta Beta | etabeta | etabeta@yahoo.com
Paolino Paperino | paperino | paperino@gmail.com

A loadAssocList() si può passare come argomento opzionale il nome di un campo i cui valori saranno utilizzati come chiave dell'array dei risultati.

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__users');
$results = $database->loadAssocList('username');

echo $results['paperone']['email'];

Darà come output

paperone@hotmail.com

loadObjectList

Il metodo loadObjectList() ritorna il risultato di una query in un array di oggetti.

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__users');
$results = $database->loadObjectList();

$results è un array di oggetti. Si può accedere ad un singolo record in questo modo

$results[numero_record]->nome-campo

Quindi nel nostro esempio

echo $results[0]->name;

Produrrà come output il contenuto del campo name del primo record ritornato,

Topolino

E ancora un esempio di iterazione con foreach

foreach($results as $r) {
  echo $r->name,' | ',$r->username,' | ', $r->email,'<br />';
}

Output

Topolino | topolino | topolino@gmail.com
Paperon De' Paperoni | paperone | paperone@hotmail.com
Pietro Gambadilegno | pgamba | pgamba@msn.com
Eta Beta | etabeta | etabeta@yahoo.com
Paolino Paperino | paperino | paperino@gmail.com

Esattamente come loadAssocList() anche loadObjectList() accetta come argomento opzionale il nome di un campo i cui valori costituiranno le chiavi dell'array dei risultati.

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__users');
$results = $database->loadObjectList('name');
echo $results['Eta Beta']->email;

Output

etabeta@yahoo.com

loadRow

Il metodo loadRow() ritorna il primo record della query come array di campi indicizzato numericamente. Si accede al valore di un campo indicandone la posizione nella lista dei campi dell'istruzione SELECT (o nella tabella qualora si usi SELECT *).

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$result = $database->loadRow();
echo $result[0], ' | ', $result[1], ' | ', $result[2];
Output
Topolino | topolino | topolino@gmail.com

loadRowList

Il metodo loadRowList() ritorna il risultato di una query in un array di array di campi. Analogamente a quanto abbiamo visto per loadRow() si accede al valore di un campo tramite indice numerico.

$results[numero_record][posizione_campo]

Esempio

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$results = $database->loadRowList();
foreach($results as $r) {
  echo $r[0],' | ',$r[1],' | ', $r[2],'<br />';
}

Output

Topolino | topolino | topolino@gmail.com
Paperon De' Paperoni | paperone | paperone@hotmail.com
Pietro Gambadilegno | pgamba | pgamba@msn.com
Eta Beta | etabeta | etabeta@yahoo.com
Paolino Paperino | paperino | paperino@gmail.com

A loadRowList() si può passare la posizione di un campo come argomento opzionale. In questo caso l'array dei risultati viene indicizzato utilizzando come chiavi i valori del campo. Notate la differenza con loadAssocList() e loadObjectList() dove il parametro opzionale è il nome di un campo.

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$results = $database->loadRowList(1);
echo $results['paperino'][0];

Il campo con posizione 1 nella lista dei campi selezionati con SELECT è username, quindi l'istruzione echo del campo 0 (name) del record con chiave paperino visualizzerà

Paolino Paperino

loadAssoc

Il metodo loadAssoc() ritorna il primo record della query come array associativo le cui chiavi sono costituite dai nomi dei campi.

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$result = $database->loadAssoc();
echo $result['name'], ' | ', $result['username'], 
  ' | ', $result['email'];

Output

Topolino | topolino | topolino@gmail.com

loadResult

Il metodo loadResult() ritorna il valore del primo campo del primo record di una query.

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
echo $database->loadResult();

Output

Topolino

Di solito si usa con funzioni SQL che ritornano un solo record. Esempio.

$database = &JFactory::getDBO();
$database->setQuery("SELECT COUNT(*) FROM #__users 
  WHERE username LIKE 'p%'");
echo $database->loadResult();

Ritorna il numero dei record il cui campo username inizia con la lettera p. Nel caso della nostra tabella di esempio l'output è quindi

3

loadResultArray

Il metodo loadResultArray() riceve come argomento la posizione di un singolo campo nella lista dei campi specificati con l'istruzione SELECT e restituisce un array i cui elementi sono costituiti dai valori del campo per tutti i record estratti dalla query.

$database = &JFactory::getDBO();
$database->setQuery('SELECT name, username, email 
  FROM #__users');
$results = $database->loadResultArray(0);
print_r($results);

Output

Array
(
    [0] => Topolino
    [1] => Paperon De' Paperoni
    [2] => Pietro Gambadilegno
    [3] => Eta Beta
    [4] => Paolino Paperino
)

Eseguire query di aggiornamento, inserimento o cancellazione

Si è visto che le query di selezione (SELECT) si eseguono utilizzando metodi diversi a seconda del tipo di array / variabile in cui vogliamo ricevere i risultati.

La situazione è fortunatamente molto più semplice nel caso di query di inserimento (INSERT), aggiornamento (UPDATE) o cancellazione (DELETE), che sono tutte eseguite con lo stesso metodo (query()) dell'oggetto database di sistema.

Quindi è sufficiente un solo esempio. Topolino cambia indirizzo email!

$database = &JFactory::getDBO();
$database->setQuery("UPDATE #__users 
  SET email='topolino@hotmail.com' 
  WHERE username='topolino'");
$database->query();
echo 'Record modificati: ', $database->getAffectedRows();

Il metodo getAffectedRows() restituisce il numero dei record modificati dalla query. Nel nostro caso l'output sarà

Record modificati: 1

Gestione degli errori

In caso di errore tutti i metodi che ritornano un insieme di record estratti in base ad una istruzione SELECT (i vari loadxxx() per intendersi) restituiscono null, il metodo query() invece restituisce il valore false.

Negli esempi i valori di ritorno dei metodi di accesso al database non sono mai controllati per non appesantire il codice, ma in una applicazione reale questa non sarebbe certo una buona pratica di programmazione.

Di solito oltre a sapere che si è verificato un errore in una query, torna comodo sapere di quale errore si tratti. Per far questo si utilizzano i metodi getErrorNum() e getErrorMsg() della classe JDatabase che restituiscono il codice numerico e il messaggio esteso relativo all'errore generato dalla query più recente.

Esempio (notare il nome errato della tabella)

$database = &JFactory::getDBO();
$database->setQuery('SELECT * FROM #__usrs');
$results = $database->loadObjectList();
if(!$results) {
 echo 'Errore: ', $database->getErrorNum(), '<br />';
 echo $database->getErrorMsg();
}

Output

Errore: 1146
Table 'xxx.jos_usrs' doesn't exist SQL=SELECT * FROM jos_usrs

La documentazione di MySQL contiene una lista dei codici di errore

Comporre una query con dati ricevuti dall'utente

Capita spesso di dover creare una query in base a parametri di ricerca inseriti dall'utente. In questi casi è essenziale utilizzare il metodo getEscaped() per filtrare la stringa SQL in modo che tutti i caratteri che hanno un significato particolare per il motore del database (ad esempio gli apici) siano opportunamente mascherati.

Questo per prevenire errori o peggio tentativi di effettuare delle sql injections.

Esempio (sbagliato)

$name = "Paperon De' Paperoni";
$sql = "SELECT email FROM #__users WHERE name = '$name'";
$database = &JFactory::getDBO();
$database->setQuery($sql);
$result = $database->loadResult();
if(!$result) {
  echo $database->getErrorMsg();
} else {
  echo $result;
}

L'apice in $name non può essere inserito come tale all'interno della stringa SQL perché è utilizzato come carattere delimitatore.

Infatti questo codice produce un errore

You have an error in your SQL syntax; check the manual that 
corresponds to your MySQL server version for the right syntax
to use near 'Paperoni'' at line 1 
SQL=SELECT email FROM jos_users WHERE name = 'Paperon De' 
Paperoni'

Questa è la versione corretta

$database = &JFactory::getDBO();
$name= "Paperon De' Paperoni";
$sql = "SELECT email FROM #__users WHERE
  name = '" . $database->getEscaped($name) . "'";
$database->setQuery($sql);
$result = $database->loadResult();
if(!$result) {
  echo $database->getErrorMsg();
} else {
  echo $result;
}

L'output questa volta è

paperone@hotmail.com

Paginazione dei risultati di una query

Per concludere vediamo un esempio di come il risultato di una query SQL possa essere suddiviso in pagine contenenti un numero prefissato di record.

//Record per pagina
$rpp = 3;
$database = &JFactory::getDBO();
//Calcola il numero totale dei record
$sql = 'SELECT count(*) FROM #__users';
$database->setQuery($sql);
$tot = $database->loadResult();
if($tot) {
  //Numero delle pagine
  $np = ceil($tot / $rpp);
  $sql =  'SELECT * FROM #__users';
  
  for($pag=0; $pag < $np; $pag++) {
    $database->setQuery($sql, $pag*$rpp, $rpp);
    $results = $database->loadObjectList();
    if(!$results) {
      echo $database->getErrorMsg();
      break;
    }
    echo '<br />Pagina ',$pag+1,'<br />', 
      '----------','<br />';
    foreach($results as $r) {
      echo $r->name,' | ',$r->username,' | ', $r->email,
      '<br />';
    }
  }
}
Che genera il seguente output
Pagina 1
----------
Topolino | topolino | topolino@gmail.com
Paperon De' Paperoni | paperone | paperone@hotmail.com
Pietro Gambadilegno | pgamba | pgamba@msn.com

Pagina 2
----------
Eta Beta | etabeta | etabeta@yahoo.com
Paolino Paperino | paperino | paperino@gmail.com

Il codice dovrebbe essere autoesplicativo (ottimista?).

Da notare che il metodo setQuery() è usato con due argomenti opzionali che non abbiamo visto fino ad ora e che servono ad includere nell'array dei risultati (nell'esempio $results) un sottoinsieme del totale dei record estratti in base alla query SQL.

Il primo argomento (offset) indica la posizione a partire dalla quale i record estratti dalla query devono essere ritornati nell'array dei risultati. Da tenere presente che il primo record estratto dalla query ha posizione zero.

Il secondo argomento indica il numero dei record da ritornare nell'array dei risultati.

Calcolato il totale dei record da visualizzare e conoscendo il numero dei record per pagina (3 nell'esempio), determinare il valore dei due argomenti di setQuery() per ciascuna pagina è solo matematica!

Fortunatamente siamo alla fine perché cominciavo ad averne più che abbastanza di personaggi Disney!

Quelle esaminate non sono tutte, ma sono sicuramente le più utili e frequentemente usate funzioni per l'accesso al database che il framework di Joomla mette a disposizione degli sviluppatori.

Alla prossima.

16 commenti:

Grisus ha detto...

Grazie mille per il tuo articolo, mi hai aiutato con un problema che avevo :-)) Quello che scrivi l'ho trovato abbastanza chiaro, dopo tante ricerche dove nn capivo nulla ehehe Mi sono segnato il tuo blog cosi' ci tornerò se avro' altri dubbi, ciao ciao

Massimo ha detto...

Ti ringrazio per i complimenti. Torna pure quando vuoi :)

Penny ha detto...

Mi sto buttando su J15 solo da poche settimane e lo sto odiando... i tuoi articoli molto semplici e molto efficaci. Mi pare che a volte sia più semplice farsi un component da solo che modificarne uno altrui che cmq utilizza programmazione troppo di alto livello.

Massimo ha detto...

Sono d'accordo. Non è una cattiva idea farsi un componente partendo da zero. Così te lo costruisci secondo lo standard.
La nuova struttura dei componenti di Joomla 1.5 è complessa e ci vuole un po' a prenderci la mano, però col tempo ne capisci anche i vantaggi e lo odi meno :)

Massimo ha detto...

Ciao, ho seguito il tuo "tutorial" e ho ottenuto la lista degli articoli che volevo:

prova php
mosmodule prova
Corno modificato
A sud ovest di Porretta

Lo scopo è ottenere una lista degli articoli di uno stesso autore.
Ora però ho due problemi:

1) come rendo cliccabili i titoli della lista che ho ottenuto, in modo che vengano reindirizzati agli articoli relativi (ci ho provato ma non riesco...);
2) come si può conoscere l'id ed il nome dell'autore dell'articolo attualmente visualizzato?

Capisco che questo non è un forum dove chiedere aiuto su queste questioni ma il tuo articolo è chiaro e fatto bene, quindi chiedo prima a te sperando in una risposta.
Ciao e grazie.

Massimo ha detto...

Tu per avere la lista degli articoli avrai fatto una query su jos_content, e lì trovi tutte le informazioni che ti servono. Il link ad un articolo è fatto così

index.php?option=com_content&view=article&id=xx

Dove xx è l'ID dell'articolo che avrai nel risultato della query. Avrai anche il campo created_by che contiene l'ID utente dell'autore dell'articolo, il nome lo devi cercare in jos_users.

Se hai dubbi penso ti convenga guardare anche come sono fatti i moduli latest_news e most_read standard.

Sergio ha detto...

Ciao, ho un problema. Devo creare un componente e mi sto accingendo a progettarlo. Ho la necessità di usare diversi database dove lavorare. Come faccio a creare la connessione al secondo database?
all'inizio dell'articolo parli di getDBO() per prendere la conn già inizializzata da joomla ma se ne volessi creare un altra ad un altro db per lavorare su entrambi?
stavo cercando qualcosa qui http://api.joomla.org/Joomla-Framework/Database/JDatabase.html ma mi sto perdendo di casa.
Complimenti per tutte le guide e se volessi aiutarmi io te ne sarei molto grato ;)

Massimo ha detto...

Per il problema di aprire connessioni a più database ho fatto un nuovo post, era troppo lungo rispondere con un commento.

Anonimo ha detto...

Complimenti! Ho girato giorni e giorni per trovare una guida che spiegasse come interfacciarsi alle api di joomla.
GRAZIE!

ventus85 ha detto...

Ciao!
Il tuo articolo è ottimo!
Mi è servito per capire come creare dei moduli e componenti di Joomla che possano accedere al database.

Massimo ha detto...

Ciao ventus85 (nick familiare, forum di joomla.it se non sbaglio). Grazie, mi fa piacere che l'articolo ti sia stato utile.

Anonimo ha detto...

Complimenti al tuo articolo, mi ha decisamente chiarito la questione database :-)

Ruben

ventus85 ha detto...

ehhhmmmm...non si può nascondere nulla, Massimo!

Anonimo ha detto...

Grazie,
finalmente qualcosa di comprensibile per un neofita....
Luca

Massimo ha detto...

Grazie te, sono contento che l'articolo ti sia stato utile.

Micaela ha detto...

Ottimo articolo, ci ho messo settimane per trovare qualche cosa del genere e dopo averlo letto.. in 10 minuti.. ho risolto un problema che mi stava facendo perdere il sonno...
Ora devo solo capire come far "aprire" una pagina che riassuma le info che mi interessano, cliccando sul nome dell'autore...

Posta un commento

Nota. Solo i membri di questo blog possono postare un commento.