PDO, Abstraction Layer
Finalmente con la versione 5.1 PHP ha un nuovo livello di astrazione per la connettività con i database veramente valido.
PDO, PHP Data Objects
Ho deciso di studiarlo un po’:
Creo un Database di prova:
DROP TABLE IF EXISTS first_table;
--CREATE TABLE first_table (
--id INT(10) NOT NULL AUTO_INCREMENT,
--name VARCHAR(255) NOT NULL,
--surname VARCHAR(255) NOT NULL,
--PRIMARY KEY (id)
--) TYPE=MyISAM;
CREATE TABLE first_table (
id INT(10) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
surname VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
) TYPE=innoDB;
INSERT INTO first_table (name,surname) VALUES ('matteo','magni');
INSERT INTO first_table (name,surname) VALUES ('john','starks');
INSERT INTO first_table (name,surname) VALUES ('michael','jordan');
INSERT INTO first_table (name,surname) VALUES ('kobe','bryant');
INSERT INTO first_table (name,surname) VALUES ('earvin','johnson');
INSERT INTO first_table (name,surname) VALUES ('larry','bird');
INSERT INTO first_table (name,surname) VALUES ('patrick','ewing');
INSERT INTO first_table (name,surname) VALUES ('reggie','miller');
INSERT INTO first_table (name,surname) VALUES ('ron','harper');
INSERT INTO first_table (name,surname) VALUES ('anfernee','hardaway');
INSERT INTO first_table (name,surname) VALUES ('tim','hardaway');
INSERT INTO first_table (name,surname) VALUES ('mitch','richmond');
INSERT INTO first_table (name,surname) VALUES ('jason','williams');
INSERT INTO first_table (name,surname) VALUES ('allen','iverson');
INSERT INTO first_table (name,surname) VALUES ('dwayne','wade');
Lo importo così dopo aver creato il DB pdo
mysql -p -D pdo < pdo.sql
Per le prove utilizzerò sia la versione con tabella MyISAM che quella con tabella InnoDB, la differenza è che per la prima non sono disponibili le transazioni.
Usiamo le eccezioni per connetterci al DB
//USO le eccezioni
$string_dsn = 'mysql:host=localhost;dbname=pdo'; // mysql
$string_username = 'user';
$string_password = 'password';
try {
$mypdo = new PDO($string_dsn, $string_username, $string_password);
}
catch(PDOException $e) {
echo 'Errore di connessione: '.$e->getMessage();
}
Query fatta con errore, mi restituisce il numero dell’errore:
$mypdo->exec('SELECT * FROM SELECT'); // errore generato appositamente
echo 'Errore N: '.$mypdo->errorCode();
Se la uso così è molto utile, mando io il messaggio che voglio:
if($mypdo->errorCode() !== '' || !$mypdo->exec('SELECT * FROM SELECT'))
echo 'errore nella query SELECT * FROM SELECT';
Così mi ritorna un array di tre elementi con le notizie sull’errore:
var_dump($mypdo->errorInfo());
//array di tre elementi
if(count($mypdo->errorInfo()) == 1) // ... tutto ok
if(count($mypdo->errorInfo()) > 1) { // ... ci sono errori
$errorinfo = $mypdo->errorInfo();
echo $errorinfo[2]; // stringa con l' errore
}
QUERY semplice senza preparare niente,
Mi ritorna i dati accessibili sia con il numero della colonna (partendo da 0) che con il nome della colonna
Query senza parametri aggiuntivi per stabilire che Fetch usare:
foreach($mypdo->query('SELECT * FROM first_table') as $row)
{
echo '--------------<br />';
echo '1: '.$row[0].' - 2: '.$row[1].'<br />';
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
mi ritorna i dati accessibili sia con il numero della colonna (partendo da 0) che con il nome della colonna.
Esplicito il tipo di Fetch con il parametro PDO_FETCH_*
PDo::FETCH_BOTH #usatelo come mysql fetch_array, risultato duplicato, quello di default
foreach($mypdo->query('SELECT * FROM first_table', PDO::FETCH_BOTH) as $row)
{
echo '--------------<br />';
echo '1: '.$row[0].' - 2: '.$row[1].'<br />';
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
PDO::FETCH_ASSOC #usatelo come mysql_fetch_assoc
foreach($mypdo->query('SELECT * FROM first_table', PDO::FETCH_ASSOC) as $row)
{
echo '--------------<br />';
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
PDO::FETCH_NUM #analogo a mysql_fetch_row
foreach($mypdo->query('SELECT * FROM first_table', PDO::FETCH_NUM) as $row)
{
echo '--------------<br/>';
echo '1: '.$row[0].' - 2: '.$row[1].'<br/>';
}
PDO::FETCH_OBJ #ritorna un oggetto con le proprieta’ che hanno il nome dei campi
foreach($mypdo->query('SELECT * FROM first_table', PDO::FETCH_OBJ) as $row)
{
echo '--------------<br />';
echo 'id->'.$row->id.' - name-> '.$row->name.'<br />';
}
PDO::FETCH_LAZY #combina *_BOTH e *_OBJ
foreach($mypdo->query('SELECT * FROM first_table', PDO::FETCH_LAZY) as $row)
{
echo '--------------<br />';
echo '1: '.$row[0].' - 2: '.$row[1].'<br />';
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
echo 'id->'.$row->id.' - name-> '.$row->name.'<br />';
}
PDO::FETCH_BOUND #Lo vediamo con bindColumn,
restituiti come attributi alle variabili PHP
Per Evitare problemi con DB case sensitive e non case sensitive
Setto che i nomi delle colonne siano restituiti sempre minuscoli
$mypdo->setAttribute(PDO::CASE_LOWER);
/*
* Psso usare anche
* PDO::CASE_UPPER
* PDO::CASE_NATURAL come sono fornite dal driver, metodo di default
*/
oppure si usa sempre FETCH_NUM
Utilizzo delle Transazioni
inizio la transazione
Su Mysql devo usare tabelle InnoDB perchè MyIsam non supportano le transazioni
Però…
The following example begins a transaction and issues two statements that
modify the database before rolling back the changes.
On MySQL, however, the DROP TABLE statement automatically commits the
transaction so that none of the changes in the transaction are rolled back.
Quindi niente drop per una transazione.
$mypdo->beginTransaction();
$mypdo->exec('DROP TABLE first_table');
$mypdo->rollBack(); // la tabella mytable rimarra' invariata :-)
come detto su MySQL la cancella lo stesso.
$mypdo->beginTransaction();
$mypdo->exec("INSERT INTO first_table (name,surname) VALUES ('gianni','morandi')");
$mypdo->rollBack(); // la tabella mytable rimarra' invariata :-)
$mypdo->beginTransaction();
$mypdo->exec("INSERT INTO first_table (name,surname) VALUES ('gianni','morandi')");
$mypdo->commit(); // la tabella mytable rimarra' invariata :-)
mi inserisce il nuovo valore.
Prepared Statement
Metodo Fetch
Metodo implementato nello statement che è in grado di spostare il cursore dei risultati di una query in un ciclo.
Gli posso passare i parametri come a query.
/*
* PDO::FETCH_ASSOC, PDO::FETCH_BOTH, PDO::FETCH_BOUND, PDO::FETCH_LAZY,
* PDO::FETCH_OBJ, PDO::FETCH_NUM
*/
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare('UPDATE first_table SET name = "pippo" WHERE id = 17');
// eseguo la query
$stpdo->execute();
$mypdo->commit();
echo 'SELECT';
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare('SELECT * FROM first_table');
// eseguo la query
$stpdo->execute();
//stampo a video il contenuto dell'array che mi crea fetch
while ($row=$stpdo->fetch())
{
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
$mypdo->commit();
La potenza prepare e execute
Potenza del metodo prepare
$mypdo->beginTransaction();
$stpdo = $mypdo->prepare('UPDATE first_table SET name = ? WHERE id = ?');
//i punti interrogativi saranno sostituiti da execute
$stpdo->execute(array('gianni', 17));
$mypdo->commit();
echo 'SELECT';
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare('SELECT * FROM first_table');
// eseguo la query
$stpdo->execute();
//stampo a video il contenuto dell'array che mi crea fetch
while ($row=$stpdo->fetch())
{
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
$mypdo->commit();
Posso preparare la query una volta e fare poi i vari inserimenti.
potenza prepare e execute ancora
$mypdo->beginTransaction();
$stpdo = $mypdo->prepare('UPDATE first_table SET name = :name WHERE id = :id');
//i punti interrogativi non sempre sono chiari, così lo è di più
$stpdo->execute(array(':name'=>'marcello',':id'=>17));
$mypdo->commit();
echo 'SELECT';
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare('SELECT * FROM first_table');
// eseguo la query
$stpdo->execute();
//stampo a video il contenuto dell'array che mi crea fetch
while ($row=$stpdo->fetch())
{
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
$mypdo->commit();
Metodo fetchAll
ritorna un array le righe del resultset
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare('SELECT * FROM first_table');
// eseguo la query
$stpdo->execute();
$row=$stpdo->fetchAll();
//stampo a video il contenuto dell'array che mi crea fetch
var_dump($row);
$mypdo->commit();
Metodo fetchAll
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare('SELECT * FROM first_table');
// eseguo la query
$stpdo->execute();
$row=$stpdo->fetchAll(PDO::FETCH_OBJ);
//così ho un array di oggetti
//stampo a video il contenuto dell'array che mi crea fetch
var_dump($row);
$mypdo->commit();
Metodi bind
bindParam
$id=10;
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare("SELECT * FROM first_table WHERE id = ? ");
$stpdo->bindParam(1,$id, PDO::PARAM_INT);
$stpdo->execute();
while ($row=$stpdo->fetch())
{
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
$mypdo->commit();
Oppure:
$id=10;
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare("SELECT * FROM first_table WHERE id = :id ");
$stpdo->bindParam(':id',$id, PDO::PARAM_INT);
$stpdo->execute();
while ($row=$stpdo->fetch())
{
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
$id=13;
$stpdo->bindParam(':id',$id, PDO::PARAM_INT);
$stpdo->execute();
while ($row=$stpdo->fetch())
{
echo 'id: '.$row['id'].' - name: '.$row['name'].'<br />';
}
$mypdo->commit();
Così ho preparato la query una volta sola, ed ho legato :id al valore della variabile $id.
bindValue
$id=10;
$mypdo->beginTransaction();
$stpdo = $mypdo->prepare('SELECT * FROM first_table WHERE id < ? ');
$stpdo->bindValue(1, $id, PDO::PARAM_INT);
$stpdo->execute();
while ($row=$stpdo->fetch())
{
echo ‘id: ‘.$row[’id’].’ - name: ‘.$row[’name’].’<br />’;
}
$mypdo->commit();
bindColumn
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare("SELECT * FROM first_table ");
$stpdo->bindColumn(1,$id);
$stpdo->bindColumn(2,$name);
$stpdo->bindColumn(3,$surname);
$stpdo->execute();
while($row = $stpdo->fetch(PDO::FETCH_BOUND))
echo $id.' '.$name.' '.$surname.'<br />'; // stampo i nomi
$mypdo->commit();
In pratica specifico prima come devono chiamarsi i dati letti da una query e di che tipo devono essere.
a variabile $id non l’avevo mai creata, nonostante ciò non ho avuto nessun notice o warning perche’ questa viene popolata e inizializzata ( se inesistente ) automaticamente all’ interno della chiamata a bindColumn.
Poi la variabile viene automaticamente riassegnata ad ogni ciclo del while ,
permettendoci di evitare i soliti $row[N] per il fetch_row o $row[’key’] per il fetch_assoc.
bindColumn
$mypdo->beginTransaction();
// preparo la query
$stpdo = $mypdo->prepare("SELECT * FROM first_table ");
$stpdo->bindColumn(1,$id, PDO::PARAM_INT);
$stpdo->bindColumn(2,$name, PDO::PARAM_STR);
$stpdo->bindColumn(3,$surname, PDO::PARAM_STR);
$stpdo->execute();
while($row = $stpdo->fetch(PDO::FETCH_BOUND))
echo $id.' '.$name.' '.$surname.'<br />'; // stampo i nomi
$mypdo->commit();
i parametri non modificano il risultato, ma “forse” ottimizzano le query
così abbiamo visto anche come funizona PDO::FETCH_BOUND
E se volessi usare Postgres invece di Mysql?
Semplice creo il DB pgsql come quello mysql e basta che cambi queste rige con i diversi parametri di connessione:
$string_dsn = 'pgsql:host=localhost;dbname=pdo'; // pgsql
$string_username = 'user';
$string_password = 'password';
ed il gioco è fatto, bello no?!
Lo studio l’ho fatto partendo da questi link:
http://it2.php.net/pdo
Pillola PDO
Gabba Gabba Hey
Bonzo
12 Agosto 2008 alle 15:00
E come faccio ora a dirti che Eccezzioni si scrive con una Z sola? :-D
Senti io sto implementando un metodo proprio con PDO, in particolare una procedura auto installante per un sistema di news.
Il problema che incontro è con l’implementazione tramite PDO della verifica dell’esistenza del database, ovvero se esso non esiste il sistema lo deve creare.
Ma qui viene il problema, usando il metodo try/catch (le eccezioni appunto) per l’individuazione dell’errore esso mi crea un problema, ovvero mi rileva sì l’errore riportando la corretta stringa: “Message: SQLSTATE[42000] [1049] Unknown database ‘news’”, ma se chiedo il codice dell’errore per un controllo sul tipo, mi da sempre e invariabilmente 0 (zero).
Non ha senso dato che $e->getMessage() mi da correttamente la stringa di cui poco sopra, mentre $e->getCode() da 0. getCode() è un metodo di Exception quindi non può essere ignorato, o no?
Sai tu qualcosa al riguardo?
14 Agosto 2008 alle 08:19
Grazie per la segnalazione….. chiedo perdono per l’errore.
Per la domanda tecnica, non so risponderti, appena ho un po’ di tempo faccio delle prove.