Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

I layer di astrazione dal database

Write once and run everywhere: come aggiungere alle proprie applicazioni PHP il supporto a database diversi
Write once and run everywhere: come aggiungere alle proprie applicazioni PHP il supporto a database diversi
Link copiato negli appunti

Una delle caratteristiche più apprezzate di PHP è il supporto nativo per un notevole numero di database relazionali, non solo quindi RDBMS (Relational Database Management System) open source come MySQL e PostgreSQL ma anche Oracle, DB2 e Informix tanto per fare il nome di alcuni "pezzi grossi". Tuttavia questo comporta che quasi per ogni database esiste un'estensione e un'API client con un gran numero di funzioni specifiche, caratteristica che può rivelarsi piuttosto scomoda qualora si debbano realizzare applicazioni in grado di appoggiarsi indifferentemente a più basi di dati senza subire ogni volta modifiche importanti.

In questi casi diventa necessario creare un "livello di astrazione" (abstraction layer), cioè un filtro unico che si interponga tra il database e il linguaggio svolgendo tre funzioni:

  1. annullare le differenze tra le varie API client
  2. attenuare le incompatibilità tra i diversi dialetti SQL
  3. limitare le difformitàtra i tipi di dato riconosciuti dal database

Purtroppo PHP non ha a disposizione nulla di paragonabile a DBI (Database Independent Interface) la completissima interfaccia unica di Perl e, almeno inizialmente, il rapporto privilegiato con MySQL ha ritardato lo sviluppo di qualcosa di analogo: oggi esiste una manciata di "layer di astrazione" (più o meno maturi e in continua evoluzione) che si prestano a realizzare soprattutto il primo dei due obiettivi appena indicati. Se escludiamo DBX, ODBC e PDO nella maggior parte dei casi si tratta di classi o gruppi di classi PHP: non richiedono l'installazione di ulteriori librerie anche se rimangono in ogni caso necessarie le estensioni PHP specifiche per il database che si intende supportare.

Nella prima parte dell'articolo effettueremo una panoramica sui progetti più maturi o promettenti, delineandone pregi e difetti e descrivendone le principali caratteristiche, mentre in quella che seguirà porteremo degli esempi concreti di utilizzo delle varie interfacce. Partiamo subito con i progetti più promettenti.

PEAR::DB

Riferimenti: http://pear.php.net/package/DB, http://pear.php.net/manual/en/package.database.php

È il layer di astrazione storico e "ufficiale" di PHP, il più conosciuto e documentato: al momento supporta 13 database. Richiede PEAR: una montagna di codice PHP di base. Non deve essere considerato necessariamente il migliore e più completo, ma è una possibilità che non si può non prendere in considerazione al momento di scegliere lo strumento più adatto.

Da PEAR::DB dipendono molti altri pacchetti PEAR.

ADODB

Riferimenti: http://php.weblogs.com/ADODB, http://php.weblogs.com/ADOdb_manual

Ha i suoi punti di forza nella sintassi familiare per gli sviluppatori che provengono dal mondo ASP (il nome non è un caso) e nell'essere probabilmente il più performante tra i layer in codice PHP (vedi paragrafo sui benchmarks). Rifiuta alcune caratteristiche object oriented ortodosse in nome della praticità e delle prestazioni, ma la cosa non va a scapito della compeletezza delle sue funzionalità. È davvero un ottimo progetto che ha raccolto rapidamente un ampia comunità di estimatori, è persino in grado di "travestirsi" con la sintassi PEAR::DB in modo da rendere indolore il passaggio da una libreria all'altra.Ne esiste una versione sperimentale come estensione PHP (in codice C) e di recente John Lim, che ne è l'ideatore, sta lavorando anche a una versione di ADODB per il linguaggio Python.

METABASE

Riferimenti: http://www.phpclasses.org/metabase

È la libreria più completa e object oriented: l'autore, Manuel Lemos, è colui che ha dato vita al celebre repository "Phpclasses". Metabase comprende un numero impressionante di funzionalità, dall'astrazione dei tipi di dato alla conversione da e verso XML ma paga la propria completezza con un overhead notevolmente superiore a quello della concorrenza.

PEAR::MDB

Riferimenti: http://pear.php.net/package/MDB, http://pear.php.net/package/MDB2, http://pear.php.net/manual/en/package.database.mdb.php

È un progetto giovane, frutto di una riscrittura di METABASE nello stile di PEAR::DB, prende il meglio da entrambi garantendo sia funzionalità che prestazioni.

Non sono ancora molti i database supportati ma è probabilmente destinato diventare il layer di astrazione di riferimento per PHP: da tenere assolutamente in considerazione.

DBX

Riferimenti: http://www.php.net/manual/it/ref.dbx.php

Si tratta di un'estensione PHP e garantisce le migliori prestazioni in assoluto. Non sono molti i database supportati (anche SQLite a partire dalla verisone per PHP5) ma fa della semplicità e delle performance le sue qualità migliori.

ODBC (Open Database Connectivity)

Riferimenti: http://www.php.net/manual/it/ref.odbc.php

È probabilmente il livello di astrazione più antico e supportato, questa architettura deve le sue origini ad aziende come IBM e Microsoft e praticamente non esiste database relazionale che non sia compatibile con ODBC. Purtroppo la necessità di veri e propri driver, specifici per il database in questione, penalizza prestazioni rispetto ai layer che si servono del supporto nativo.

Da tenere presente che le funzioni ODBC di PHP non solo si interfacciano ai driver ODBC, ma rappresentano anche una semplice API comune (stile DBX) per il supporto nativo di alcuni database (tra cui il celebre DB2) per i quali quindi PHP non utilizza realmente l'architettura ODBC.

La cosa viene specificata chiaramente nel capitolo del manuale PHP dedicato all'argomento

"Nella connessione ai database sopra elencati non vengono coinvolte funzioni ODBC. Le funzioni che vengono utilizzate per collegarsi nativamente con essi condividono solamente lo stesso nome e sintassi delle funzioni ODBC. L'eccezione a questo è iODBC. Compilando il PHP con il supporto di iODBC, si può utilizzare qualsiasi driver compatibile ODBC nelle applicazioni PHP. iODBC è gestito da OpenLink Software..."

PDO (PHP Data Objects)

Riferimenti: http://www.edwardbear.org/blog/archives/000203.html

Un'estensione per PHP5 ideata da uno degli sviluppatori PHP più attivi in questo periodo, Sterling Hughes. Si intravvedono grandi possibilità ma lo sviluppo è ancora agli inizi.

DB dataobject

Riferimenti: http://pear.php.net/package/DB_DataObject

È qualcosa di più di un layer di astrazione, vuole rappresentare per PHP ciò che ADO (ActiveX Data Objects) è per ASP: una mezzo per interfacciarsi ai database relazionali senza utilizzare direttamente SQL ma lavorando esclusivamente con metodi e proprietà di appositi oggetti.

Svolge due funzioni:

  1. Costruisce automaticamente espressioni SQL dai metodi degli oggetti
  2. Utilizza sempre gli oggetti per immagazzinare i dati di una tabella o il risultato di una query

Benchmarks

La complessità si paga e qualsiasi livello di astrazione determina un overhead nell'esecuzione degli script, non fosse altro perchè l'utilizzo di queste classi comporta numerose inclusioni: come sempre la scelta sullo strumento da utilizzare è legata al rapporto "obiettivi/prestazioni" e in definitiva dalle nostre esigenze concrete.

Ai link seguenti è possibile esaminare alcuni benchmark considerati affidabili http://freshmeat.net/screenshots/30313/

http://phplens.com/lens/adodb/

Nella prima parte dell'articolo abbiamo introdotto i layer di astrazione attraverso una panoramica sulle principali librerie disponibili per PHP; invece nei prossimi paragrafi, attraverso alcuni esempi pratici, vedremo quali problemi riescano concretamente a risolvere.

Analizzeremo cinque problemi pratici che riguarderanno:

  • la connessione al database
  • la query di selezione dei dati
  • la query di tipo LIMIT
  • l'escape dei caratteri pericolosi
  • l'astrazione dal tipo di dati.

Per ognuno verrà fornito un pratico esempio di utilizzo.

Per gli esempi ci serviremo di PEAR::MDB e ADODB, ricordiamo nuovamente che ADODB è più matura e supporta un maggior numero di database, mentre PEAR::MDB è forse più ricca di funzionalità specifiche per ottenere la compatibilità tra i diversi RDBMS, inoltre fa parte del PEAR: una montagna di codice PHP e ne è dipendente per quanto riguarda la gestione degli errori.

La connessione al database

ADODB


<?php

/*

include i file principali di ADODB

*/


error_reporting(E_ALL);

include('adodb-errorhandler.inc.php');

include_once('adodb.inc.php');


/*

specifica il RDBMS (MySQL, PostgreSQL etc.etc.) e restituisce un oggetto che incapsula la connessione $tipoDB può essere ad esempio "mysql"

*/


$conn = &ADONewConnection($tipoDB);


/*

la connessione vera a propria

*/


$conn->Connect($host, $utente,$password, $nome_db);


//altro codice


.

.

.


//solitamente facoltativo, chiude la connessione


$conn->close() ;

?>

Da tenere presente che la struttura degli argomenti da passare al metodo Connect può variare leggermente a seconda del tipo di RDBMS, quella appena vista è la più comune.

PEAR::MDB


<?php

/*

include il file principale di PEAR::MDB

*/


include_once("MDB.php");


/*

specifica tutti i dati necessari alla connessione nel formato DSN tipico di PEAR. $tipoDB potrebbe essere ad esempio "mysql"

*/


$dsn = "$tipoDB://$utente:$password@$host/$nome_db";


/*

la connessione vera a propria

*/


$conn = MDB::connect($dsn);

if (MDB::isError($conn)) {

die ($conn->getMessage());

}


//altro codice


.

.

.


//solitamente facoltativo


$conn->disconnect();

?>

Query di selezione

ADODB


<?php

//esiste già la connessione $conn (vedi esempio precedente)

/*

Imposta il modo in cui i record vengono restituiti, in questo caso corrisponde a

mysql_fetch_assoc o pg_fetch_assoc

*/

$conn->SetFetchMode(ADODB_FETCH_ASSOC);


/*

Query e restituzione di un recordset

*/


$rs = $conn->Execute('SELECT nome, cognome FROM tabella_persone');


/*

Preleva i record dal recordset

*/


if (!$rs){

exit($conn->ErrorMsg()) ;

}

else{

while (!$rs->EOF) {

echo $rs->fields['nome'].' '.$rs->fields['cognome'].'
';


//sposta il cursore al record successivo


$rs->MoveNext();

}//END WHILE


//facoltativo


$rs->Close();

}

?>

È possibile utilizzare una sintassi più semplice con il metodo FetchRow dell'oggetto $rs, ma quella appena vista è più tipica di ADODB.

PEAR::MDB


<?php

//esiste già la connessione $conn (vedi esempio precedente)



/*

Imposta il modo in cui i record vengono restituiti, in questo caso corrisponde a

mysql_fetch_assoc o pg_fetch_assoc

*/


$conn->setFetchMode(MDB_FETCHMODE_ASSOC);


/*

Query e restituzione di un recordset

*/


$rs=$conn->query('SELECT nome, cognome FROM tabella_persone');

if(!MDB::isError($rs)){


/*

Notare che fetchRow qui è un metodo di $conn, non di $rs

*/


while ($row = $conn->fetchRow($rs)) {

echo($row['nome'].' '.$row['cognome'].'
') ;

}

}

else{

die ($rs->getMessage());

}

?>

Query di tipo limit

La sintassi necessaria a prelevare solo un numero preciso di record da un insieme più grande, varia da database a database:

  • PostgreSQL e MySQL: "SELECT * FROM tabella LIMIT 5"
  • MS Access e SQLServer: "SELECT TOP 5 * FROM tabella"
  • DB2: "SELECT * FROM tabella fetch first 5 rows only"

Ecco come risolvono le differenze i due layer di astrazione che stiamo esaminando

ADODB

Riferimento:
http://phplens.com/lens/adodb/docs-adodb.htm#SelectLimit.

$conn->SelectLimit("SELECT * FROM tabella", 5) ;

PEAR::MDB

Riferimento: http://pear.php.net/manual/en/package.database.mdb.mdb-common.limitquery.php

$conn->limitQuery("SELECT * FROM tabella", array(), 0, 5)

Da notare che con PEAR::MDB è possibile specificare un array di tipi (nel nostro caso vuoto) da applicare al volo ai campi del risultato restituito.

Entrambe le librerie barano un po', nel senso che effettuano una normale query e poi "unsettano" al più presto i record in eccesso.

Escape dei caratteri pericolosi

Quando si costruiscono delle query dinamicamente è importante controllare l'input del navigatore ed evitare "iniziezioni" di dati pericolosi, tuttavia ogni RDBMS utilizza diversi caratteri di "escape" e quindi anche in questo caso l'astrazione diventa importante.

ADODB

$query='SELECT * FROM tabella_persone WHERE cognome='.$conn->qstr($_GET['cognome']) ;

PEAR::MDB

$query= 'SELECT * FROM tabella_persone WHERE cognome='.$conn->getTextValue($_GET['cognome'])

Se la variabile proveniente dalla queryString $_GET['cognome'] contenesse "Dall'Acqua", nel caso di MySQL la stringa diverrebbe:

SELECT * FROM tabella_persone WHERE cognome='Dall'Acqua' ;

mentre nel caso di MS SQLserver

SELECT * FROM tabella_persone WHERE cognome='Dall''Acqua' ;

Se si usano queste funzioni di escape è importante disabilitare nel php.ini le direttive:

magic_quotes_runtime = Off
magic_quotes_sybase = Off
magic_quotes_gpc= Off

altrimenti si otterrebbe una doppia "bonifica" dei dati.

Per una trattazione più approfondita sul tema delle "SQL Injections" è possibile fare riferimento all'articolo Proteggersi dalla SQL Injection.

Astrazione dal tipo di dati

Spesso esistono grandi differenze tra i tipi di dati riconosciuti dai vari database, i layer di astrazione risolvono la cosa utilizzando dei tipi proprietari che poi vengono mappati internamente con quelli specifici per il RDBMS che si sta utilizzando.

Campi tipo "autoincrement"

Solo alcuni RDBMS (tra i quali MySQL e SQL Server) supportano i campi di tipo autoincrement, utili per generare autmaticamente delle chiavi univoche nella tabella. Le ragioni della portabilità possono spingere lo sviluppatore a rinunciare alle funzionalità native del database per servirsi invece di un metodo apposito del layer di astrazione per la generazione delle cosiddette "sequences" (sequenze)

Ecco come fare con ADODB

Riferimento: http://phplens.com/lens/adodb/docs-adodb.htm#createseq


/*

Genera la sequenza alla prima chiamata e restituisce sempre un id

*/


$id = $conn->GenID('mia_sequenza');

$conn->Execute("insert into tabella_persone (id, nome, cognome) values ($id, $nome, $cognome)");

con PEAR::MDB

Riferimento: http://pear.php.net/manual/en/package.database.mdb.intro-sequences.php.


/*

Genera la sequenza alla prima chiamata e restituisce sempre un id

*/


$id = $conn->nextId('mia_sequenza');

$conn->query("insert into tabella_persone (id, nome, cognome) values ($id, $nome, $cognome)");

Campi tipo "date"

I database utilizzano diversi formati per immagazzinare le date, ma il layer si fa carico anche di questo.

Inserire una data con ADODB:

Riferimento: http://phplens.com/lens/adodb/docs-adodb.htm#dbdate

$data_da_inserire = $conn->DBDate($date) ;

$date può essere un timestamp Unix oppure una data nel formato ISO Y-m-d

Inserire un data con PEAR::MDB:

Riferimento: http://www.backendmedia.com/MDB/docs/class.MDB_Date.html.

$data_da_inserire = $conn->unix2Mdbstamp($timestamp);

Campi tipo "LOB" (large objects)

Sigla unica per indicare i campi BLOB (binari) e CLOB (caratteri, es. il campo text in MySQL)

Entrambe le librerie presentano metodi per gestire l'inserimento di file nel database e il loro recupero.

PEAR::MDB si spinge oltre arrivando a prelevare automaticamente i dati un po' alla volta (ricomponendoli successivamente) quando il file è di dimensioni davvero enormi e può consumare eccessiva memoria. L'argomento richiederebbe da solo un intero articolo quindi vi invitiamo ad esaminare la documentazione ai link proposti di seguito:

Per ADODB: http://phplens.com/lens/adodb/docs-adodb.htm#updateblob

Per PEAR::MDB: http://www.backendmedia.com/MDB/docs/.

Fino a che punto può spingersi l'astrazione?

È impossibile trattare in un paio di articoli tutte le caratteristiche racchiuse in un layer di astrazione che aspiri ad essere completo, abbiamo quindi preferito fornire una panoramica sull'argomento e alcuni esempi pratici che aiutino ainquadrare il problema della portabilità.

Ci siamo erviti di ADODB e PEAR::MDB proprio perchè ricchissimi di funzionalità, tra le quali (qui non trattata) la capacità di creare e aggiornare database e tabelle attraverso l'interpretazione di file XML: tuttavia esistono differenze che nessun layer di astrazione potrà mai eliminare completamente, e cioè le carenze rispetto all'SQL standard.

Pensiamo in particolare a MySQL e all'assenza di caratteristiche come le subquery, le views, le transazioni (almeno in parte) e le stored procedures.

Esistono casi in cui per ottenere un risultato con MySQL sono necessari metodi non ortodossi come l'utilizzo di tabelle temporanee e l'invio di più query, mentre ad esempio in PostgreSQL lo stesso obiettivo magari si ottiene con un'unica espressione SQL: ebbene raggiungere la portabilità significa molto spesso rinunciare a una parte delle funzionalità a disposizione, appiattendo le capacità dei database più dotati per conformarli alle carenze di quelli che lo sono meno. L'alternativa è rassegnarsi a scrivere righe di codice apposito per colmare le lacune del database che devia dallo standard: in conclusione i layer di astrazione sono strumenti utilissimi, che aiutano a programmare più velocemente e seguando uno standard, ma non possono sostituirsi ad una programmazione accorta delle proprie applicazioni.

Ti consigliamo anche