Le funzioni DBA e i file DBM
Quelli che comunemente vengono definiti "file DBM" costituiscono una modalità di memorizzazione dei dati alternativa sia ai database relazionali (es. Mysql), sia ai comuni file di testo: si tratta di file binari nei quali le informazioni vengono salvate in coppie chiave=>valore.
Possiamo considerare un file di questo tipo come una sorta di array associativo fisico: la quantità di dati che può contenere è enorme, ma l'inserimento e il recupero dei record avviene in modo semplice attraverso le chiavi.
È un po' come avere accesso diretto ad una singola tabella di database, senza usare SQL e passare attraverso il DBMS ("Database Management System")
A cosa servono?
I file di tipo DBM possono tornare utili in numerose occasioni: i tempi di accesso (e le risorse necessarie ad ottenerlo) sono nettamente inferiori a quelli richiesti da un database relazionale, mentre la capacità di memorizzazione è molto maggiore rispetto a quella dei normali file di testo; rispetto a quest'ultimi anche il recupero dei dati è immediato, non essendo necessario il "parsing" dell'intero file.
Persino gli sviluppatori del Webserver Apache hanno previsto la memorizzazione in file DBM delle coppie utente/password per l'autenticazione HTTP, opzione da prendere seriamente in considerazione quando ci si avvicina al centinaio di record.
Spesso un unico file di questo tipo viene impiegato, nell'ambito della programmazione WEB, per creare la cache del database relazionale, ed evitare dispendiose connessioni quando non vi siano stati aggiornamenti nei dati immagazzinati.
Ovviamente i file DBM, operando ad un livello più basso, non possono competere con SQL e i database relazionali quanto alla facilità di manipolazione dei dati.
Attivare il supporto per i file DBM
Nei sistemi Unix-like il sorgente Php va compilato con le seguenti opzioni di configurazione --enable-dba=shared e --with-tipo_gestore[DIR=], dove tipo_gestore deve essere la libreria relativa ad uno degli handlers elencati nel manuale (vedi paragrafo successivo).
Gli utenti Windows devono verificare, nella directory "extensions" della propria distribuzione PHP, la presenza della .dll relativa (solitamente php_dba.dll) e decommentare nel php.ini il richiamo "extension=php_dba.dll".
Se il supporto per i file DBM è attivato eseguendo un phpinfo() dovrebbe essere visibile la tabella DBA, e alla voce "Supported handlers" saranno elencati i gestori disponibili.
I diversi handlers
Fino ad ora ho sempre parlato di "file di tipo DBM" perchè questa definizione accomuna differenti tipologie di file binari: è possibile reperire una lista esaustiva dei diversi gestori ("handlers") nel capitolo del manuale PHP dedicato alle "Database (dbm-style) abstraction layer functions".
PHP supporta tutte le tipologie elencate tramite un'unica interfaccia, le funzioni dba_* (l'interfaccia precedente, dbm_*, ora è deprecata).
Il gestore più conosciuto ed efficiente è la libreria opensource fornita da "Sleepycat Software", provvista di numerose caratteristiche avanzate tanto da essere alla base delle tabelle Berkley DB di Mysql, che supportano le transazioni.
La sigla di identificazione per la versione attuale è "db3" (Sleepycat Software's db3), "db4" verrà supportata soltanto con PHP 5.
L'handler da utilizzare da parte delle funzioni dba_* deve essere sempre specificato come terzo argomento di dba_open()
<?php
/*
Crea il file e/o lo apre in modalità lettura/scrittura
*/
$dbh=dba_open($percorso_e_nome_file,"c","gdbm") ;
//Altro codice...
?>
Se il gestore gdbm (GNU database manager) non è tra quelli supportati riceveremo un errore.
Creazione e apertura di un file
<?php
/*
Nome del file, l'estensione è arbitraria
*/
$filename="test.db" ;
/*
È indispensabile indicare il percorso assoluto fino al file, "percorso" è una directory ipotetica in cui creare il file
*/
$path=$_SERVER["DOCUMENT_ROOT"]."/percorso/" ;
/*
Crea il file se non esiste, altrimenti lo apre in modalità lettura/scrittura ("c")
*/
$dbh=dba_open($path.$filename,"c","db3") ;
if (!$dbh)) {
exit("Impossibile accedere al file ".$path.$filename) ;
}
/*
Inserisce un record o sostituisce il valore presente se il record esiste già
*/
$valore="Questo è solo un valore d'esempio, potremmo inserire un'intera pagina HTML o dei dati binari" ;
dba_replace("chiave_esempio", $valore , $dbh);
dba_close($dbh) ;
?>
dba_open() ha creato un file di tipo Berkley DB (Sleepycat Software's db3), l'impostazione è molto simile a quella per l'accesso ai file di testo con fopen(), di solito si utilizza la directory /tmp (su Linux) o comunque una directory in cui PHP abbia i permessi di scrittura.
dba_replace() ha inserito un nuovo record o sostituito il precedente valore per la chiave già esistente.
N.B. Le modalità di apertura possibili sono descritte adeguatamente nel manuale, ci soffermiamo soltanto sulla modalità "n" per precisare che comporta ogni volta la completa cancellazione dei dati già immagazzinati.
Le altre funzioni DBM-style
dba_fetch(), dba_insert(), dba_replace() e dba_delete() consentono di prelevare, inserire, aggiornare e cancellare i record nel file DBM: sono ben documentate e non mi dilungherò nel descriverle.
È importante tenere presente che se con dba_insert() inavvertitamente si cerca di inserire una chiave che risulta già memorizzata, la funzione restituisce FALSE ma senza stampare alcun errore; stesso risultato se si tenta di leggere un record inesistente:
dba_exists() verifica la presenza di una chiave ed evita questo genere di problemi.
dba_optimize() ricompatta il database e ne migliora l'efficienza dopo che si sono verificate numerose cancellazioni.
dba_sync(): se ad ogni inserimento o aggiornamento dovesse corrispondere il salvataggio dei dati su disco le prestazioni ne risentirebbero; invece normalmente le modifiche vengono mantenute in memoria e applicate soltanto alla chiusura del file con dba_close().
Qualora lo ritenga necessario il programmatore può impedire questo comportamento forzando la scrittura su disco con questa funzione subito dopo aver apportato la modifica.
Attraversare l'intero database
dba_firstkey() restituisce la prima chiave dell'array e azzera la posizione del puntatore interno.
dba_nextkey() restituisce la chiave immediatamente successiva a quella della posizione attuale.
Entrambe le funzioni servono all' iterazione attraverso i record come mostrato in questo esempio.
Poichè il file DBM è una specie di scatola nera che racchiude e cela i dati, l'iterazione rappresenta l'unico sistema per sapere quanti e quali siano le chiavi registrate in un file che vediamo per la prima volta e che pertanto ci è del tutto sconosciuto.
Locking per impedire la corrosione dei dati
Come per le tabelle di database, per evitare problemi di concorrenza ("race condition") tra script (o tra istanze di uno stesso script) che tentano di accedere contemporanemente, spesso diventa necessario applicare un lock all'apertura del file. In teoria l'update di un record (non l'inserimento) dovrebbe essere atomico, ma meglio non correre rischi.
Un blocco in scrittura deve essere esclusivo e impedire qualsiasi altro accesso, mentre un blocco in lettura consente operazioni concorrenti di sola lettura.
PHP 4.3 si preoccupa finalmente di gestire questo tipo di situazioni prevedendo diverse modalità di lock:
- "l" ("L" minuscolo) blocco che utilizza un file esterno (estensione .lck): garantisce accesso esclusivo se l'apertura prevede la scrittura, altrimenti condiviso per le operazioni di lettura.
- "d" come il precedente, soltanto che il blocco viene applicato direttamente sul file DBM.
- "t" va associato ai due flag precedenti, il risultato è che lo script tenta il blocco ma se non ci riesce (perchè è già in atto un altro lock) non si mette in coda: in questo caso rinuncia all'apertura e restituisce FALSE.
- "-" Nessun blocco, opzione da usare soltanto quando non vi sia pericolo di concorrenza.
Quando un file viene aperto per default subisce un lock "d".
Rinvio al manuale per ulteriori informazioni, ribadisco soltanto che si tratta di una possibilità del tutto nuova fornita da PHP 4.3 e che è sempre consigliabile fare qualche prova prima di affidarsi ciecamente ad essa: programmando in PERL (il cui supporto per i DBM è molto completo di quello PHP) si impara che i bloccaggi sullo stesso file ("d" in PHP) non sono sempre affidabili, e che andrebbero preferiti quelli su file esterno ("l" in PHP) specialmente quando c'è la possibilità di apertura in modalità "n" (vedi pagine precedenti).
In PHP il bloccaggio avviene prima dell'apertura del file, quindi teoricamente non si dovrebbero temere accessi "distruttivi"; tuttavia la possibilità di scegliere anche il lock su file esterno mi fa dubitare sull'affidabilità della modalità "d".
Lock prima di PHP 4.3
Ecco come è possibile mettere ordine negli accessi al file DBM nelle versioni di PHP meno recenti
<?php
/*
Creiamo due funzioni per applicare il lock su di un file di testo esterno
*/
function myLock($file,$lockType){
$fp=fopen($file.".lck","w") ;
flock($fp,$lockType) ;
return($fp) ;
}
function myUnLock($h){
flock($h,LOCK_UN) ;
fclose($h) ;
}
/*
Il codice vero e proprio
*/
$filename="xtest.db" ;
$path=$_SERVER["DOCUMENT_ROOT"]."/tuo_percorso/" ;
/*
Blocco file esterno
*/
$fp=myLock($path.$filename,LOCK_EX) ;
/*
Opero sul file DBM
*/
$dbh=dba_open($path.$filename,"c","db3") ;
if (!$dbh) {
exit("Impossibile accedere al file ".$path.$filename) ;
}
//
//Altro codice....
//
dba_close($dbh) ;
/*
Sblocco file esterno
*/
myUnLock($fp) ;
?>
Dobbiamo fare in modo che tutti i processi che vogliono accedere al file DBM prima cerchino di eseguire flock() su un comune file di testo esterno, quest'ultimo ha il solo scopo di fare da semaforo: se l'operazione non riesce restano in attesa e non potranno effettuare dba_open().
Anche sull'affidabilità di flock() ci sarebbe molto da dire, comunque applicato come nell'esempio precedente, se escludiamo i filesystem FAT (Win9X/ME), dovrebbe fare il suo dovere.
Il manuale suggerisce un'alternativa più elegante, ma praticabile soltanto su sistemi Unix-like.
Conclusioni
Le tecnologie di cui abbiamo parlato presenterebbero funzionalità molto più avanzate di quelle che la semplice interfaccia PHP/dba ci consente di padroneggiare, pensiamo anche soltanto alla possibilità, che per ora ci è preclusa, di annidare le chiavi in modo da creare una sorta di array multidimensionale.
Tuttavia l'utilità delle funzioni dba_* è innegabile, e avremo modo di illustrare concretamente l'impiego dei file DBM nei futuri articoli.