Skip to content

12. Utilizzo di SGBD MySQL

Image

Ora scriveremo degli script PHP utilizzando un database MySQL:

Image

Nell'architettura sopra riportata, lo script PHP (1) non comunica direttamente con il SGBD (Sistema di gestione dei database) (3). Comunica invece con un intermediario denominato driver SGBD o driver SGBD. PHP fornisce un'interfaccia standard per questi driver, ovvero l'interfaccia PDO (PHP Data Objects). Questa interfaccia è implementata da diverse classi adatte a ciascun SGBD: una classe per il SGBD MySQL, un’altra per il SGBD PostgreSQL… Per cambiare SGBD, si cambia driver:

Image

Il driver PDO isola lo script PHP (1) da SGBD (3, 6). Poiché questi driver implementano un'interfaccia standard, ci si può aspettare che lo script PHP (1) rimanga invariato se si passa da SGBD, MySQL (3) a SGBD PostgreSQL (6). In realtà questo scenario ideale non esiste. Infatti, per comunicare con il SGBD, lo script PHP invia comandi SQL (Standard Query Language). Si tratta di un linguaggio implementato da tutti i SGBD, ma che risulta incompleto. Pertanto, i SGBD vi hanno aggiunto comandi proprietari. Questa è una delle principali cause di incompatibilità tra i SGBD. Inoltre, i tipi di dati utilizzabili nei database possono variare da un SGBD all’altro. Pertanto, il PostgreSQL accetta un numero di tipi di dati molto più ampio rispetto al SGBD e al MySQL. Questa è una seconda causa di incompatibilità. Un’altra causa è la gestione delle chiavi primarie automatiche (generate dal SGBD): quasi ogni SGBD ha una propria politica. Ecc. Le cause di incompatibilità sono numerose.

Se si vuole evitare di riscrivere lo script PHP (1) passando da MySQL (3) a PostgreSQL (6), si è generalmente costretti a inserire un nuovo livello tra lo script PHP (1) e il driver PDO (2, 5), il cui ruolo sarà quello di eliminare le incompatibilità tra i due SGBD. Tuttavia, nei casi semplici che incontreremo, questo livello aggiuntivo non sarà necessario.

Ora utilizzeremo il SGBD MySQL. Questo è incluso nel pacchetto Laragon (cfr. paragrafo «link»).

Se il lettore è alle prime armi con il concetto di database e di linguaggio SQL, potrà consultare il documento [http://sergetahe.com/cours-tutoriels-de-programmation/cours-tutoriel-sql-avec-le-sgbd-firebird/]. Questo documento utilizza Firebird SGBD e non MySQL, ma fornisce le nozioni fondamentali sui database e sul linguaggio SQL. Come MySQL, Firebird dispone di una versione liberamente utilizzabile e con un basso consumo di memoria.

12.1. Creazione di un database

Ora mostriamo come creare un database e un utente MySQL con lo strumento Laragon.

Image

  • una volta avviato, Laragon [1] può essere gestito da un menu [2];
  • in [3-5], si installa lo strumento di amministrazione [phpMyAdmin] di MySQL se non è già stato installato;

Image

  • in [6], si avvia il server web Apache insieme a SGBD e MySQL;
  • in [7], il server Apache viene avviato;
  • in [8], vengono avviati SBD e MySQL;

Image

  • in [8-10], si crea un database denominato [dbpersonnes] [11]. Si creerà un database di persone;

Image

  • in [11], gestiremo il database appena creato;

Image

  • l’operazione [Bases de données] invia una richiesta web a URL [http://localhost/phpmyadmin]. È il server web Apache di Laragon a rispondere. URL [http://localhost/phpmyadmin] è l’URL dell’utilità [phpMyAdmin] che abbiamo installato in precedenza [5]. Questa utility consente di gestire i database MySQL;
  • per impostazione predefinita, le credenziali di accesso dell’amministratore del database sono: root [13] senza password [14];

Image

  • in [16], il database che abbiamo creato in precedenza;

Image

  • al momento disponiamo di un database [dbpersonnes] [17] che è vuoto [18];

Creiamo un utente [admpersonnes] con la password [nobody] che avrà tutti i diritti sul database [dbpersonnes]:

Image

  • in [19], ci si posiziona sul database [dbpersonnes];
  • in [20], si seleziona la scheda [Privileges];
  • in [21-22], si vede che l'utente [root] dispone di tutti i diritti sul database [dbpersonnes];
  • in [23], si crea un nuovo utente;

Image

  • in [25-26], l'utente avrà l'ID [admdbpersonnes];
  • in [27-29], la sua password sarà [nobody];
  • in [30], phpMyAdmin segnala che la password è molto debole (facile da violare). In produzione, è preferibile generare una password forte con [31];
  • in [32], si indica che l’utente [admdbpersonnes] deve disporre di tutti i diritti sul database [dbpersonnes];
  • in [33], si convalidano le informazioni fornite;

Image

  • in [35], phpMyAdmin indica che l’utente è stato creato;
  • in [36], l’ordine SQL che è stato emesso sulla base;
  • in [37], l'utente [admpersonnes] dispone di tutti i diritti sul database [dbpersonnes];

Ora abbiamo:

  • un database MySQL [dbpersonnes];
  • un utente [admpersonnes/nobody] che dispone di tutti i diritti su questo database;

Scriveremo degli script PHP per sfruttare il database. PHP dispone di diverse librerie per la gestione dei database. Utilizzeremo la libreria PDO (PHP Data Objects) che si inserisce tra il codice PHP e il codice SGBD:

Image

La libreria PDO consente allo script PHP di astrarsi dalla natura esatta del SGBD utilizzato. Pertanto, nell’esempio sopra riportato, il SGBD MySQL può essere sostituito dal SGBD PostgreSQL con un impatto minimo sul codice dello script PHP. Questa libreria non è disponibile di default. È possibile verificarne la disponibilità nel modo seguente:

Image

  • in [1-4], si controllano le estensioni PDO attive;
  • in [5], si vede che l’estensione PDO per SGBD e MySQL è attiva. Le altre non lo sono. Basterebbe cliccarci sopra per attivarle;

Un altro modo per attivare un’estensione è modificare direttamente il file [php.ini] (paragrafo «link») che configura PHP:

Image

  • in [1], l'estensione PDO di MySQL è attivata;
  • in [2], l'estensione PDO di Firebird è disattivata;

Dopo aver modificato il file [php.ini], è necessario riavviare PHP di Laragon affinché le modifiche vengano applicate.

12.2. Connessione a un database MySQL

La connessione a un SGBD avviene tramite la creazione di un oggetto PDO. Il costruttore accetta diversi parametri:

$dbh=new PDO(string $dsn,string $user,string $passwd,array $driver_options)

Il significato dei parametri è il seguente:

$dsn
(Data Source Name) è una stringa che specifica la natura del SGBD e la sua posizione su Internet. La stringa "mysql:host=localhost" indica che si tratta di un SGBD MySQL in esecuzione sul server locale. Questa stringa può includere altri parametri, in particolare la porta di ascolto del SGBD e il nome del database a cui ci si vuole connettere: "mysql:host=localhost:port=3306:dbname=dbpersonnes";
$user
ID dell'utente che effettua la connessione;
$passwd
la sua password;
$driver_options
un array di opzioni per il driver di SGBD;

Solo il primo parametro è obbligatorio. L'oggetto così creato fungerà da supporto per tutte le operazioni effettuate sul database a cui ci si è collegati. Se non è stato possibile creare l'oggetto PDO, viene generata un'eccezione di tipo PDOException.

Ecco un esempio di connessione [mysql-01.php]:


<?php

// connessione a un database locale MySql
// l'identità dell'utente è (admpersonnes,nobody)
const ID = "admpersonnes";
const PWD = "nobody";
const HOTE = "localhost";

try {
  // connessione
  $dbh = new PDO("mysql:host=".HOTE, ID, PWD);
  print "Connexion réussie\n";
  // chiusura della connessione
  $dbh = NULL;
} catch (PDOException $e) {
  print "Erreur : " . $e->getMessage() . "\n";
  exit();
}

Risultati:

Connexion réussie

Commenti

  • riga 11: la connessione a un SGBD avviene tramite la creazione di un oggetto PDO. Il costruttore viene qui utilizzato con i seguenti parametri:
    • una stringa che specifica la natura del SGBD e la sua posizione su Internet. La stringa "mysql:host=localhost" indica che si tratta di un SGBD MySQL che opera sul server locale. La porta non è stata specificata. Viene quindi utilizzata per impostazione predefinita la porta 3306. Non è indicato nemmeno il nome del database. Verrà quindi stabilita una connessione al SGBD MySQL; la selezione di un database specifico avverrà in un secondo momento;
    • un nome utente;
    • la relativa password;
  • riga 14: la chiusura della connessione avviene eliminando l'oggetto PDO creato inizialmente;
  • riga 15: la connessione a un SGBD potrebbe non andare a buon fine. In tal caso, viene generata un’eccezione di tipo PDOException. Questa deriva dall’eccezione PHP [RuntimeException];
  • riga 16: viene visualizzato il messaggio di errore dell’eccezione;

Rieseguiamo lo script inserendo una password errata alla riga 6. Il risultato è quindi il seguente:


Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes'@'localhost' (using password: YES)

12.3. Creazione di una tabella

Lo script [mysql-02.php] mostra la creazione di una tabella in un database:


<?php

// identificativo del database
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenziali dell'utente
const ID = "admpersonnes";
const PWD = "nobody";

try {
  // connessione al database MySql
  $connexion = new PDO(DSN, ID, PWD);
  // eliminazione della tabella "persone" se esiste
  $sql = "drop table personnes";
  $connexion->exec($sql);
  // creazione della tabella "persone"
  $sql = "create table personnes (prenom varchar(30) NOT NULL, nom varchar(30) NOT NULL, age integer NOT NULL, primary key(nom,prenom))";
  $connexion->exec($sql);
} catch (PDOException $ex) {
  // visualizzazione dell'errore
  print "Erreur : " . $ex->getMessage() . "\n";
} finally {
  // disconnessione se necessario
  $connexion = NULL;
}
// fine
print "Terminé\n";
exit;

Commenti

  • riga 11: connessione al database. È sempre la prima cosa da fare. Il risultato della connessione è un oggetto [PDO] attraverso il quale verranno eseguite le operazioni sul database;
  • riga 13: il comando SQL [drop table personnes] eliminerà la tabella [personnes] dal database [dbpersonnes]. Se la tabella [personnes] non esiste, non si verifica alcun errore;
  • riga 14: esecuzione del comando precedente SQL sul database [dbpersonnes]. Questa esecuzione può avviare un [PDOException] che verrà intercettato alla riga 18;
  • riga 16: questo comando SQL crea una tabella [personnes]. Una tabella contiene righe e colonne. Le colonne costituiscono quella che viene chiamata la struttura della tabella. Le righe costituiscono il contenuto della tabella. Un database può contenere una o più tabelle. La tabella [personnes] avrà in questo caso tre colonne:
    • nome: il nome di una persona sotto forma di stringa di massimo 30 caratteri;
    • cognome: il cognome della stessa persona sotto forma di stringa di massimo 30 caratteri;
    • età: l’età della persona sotto forma di numero intero;
    • l'attributo NOT NULL su una colonna impone che la colonna abbia un valore. Se non gli viene assegnato alcun valore, si verifica un errore [PDOException];
    • [primary key(nom,prenom)] definisce una chiave primaria per la tabella [personnes]. Una chiave primaria ha un valore univoco per ogni riga della tabella. In questo caso, la chiave primaria sarà ottenuta concatenando le colonne [nom] e [prenom] della riga. Questo vincolo fa sì che nella tabella non possano esserci due persone con lo stesso nome e cognome, ovvero due omonimi. La creazione di un omonimo di una persona nella tabella genera un errore [PDOException];
  • riga 17: esecuzione del comando SQL sul database [dbpersonnes];
  • riga 20: se si verifica un errore [PDOException], viene visualizzato il messaggio di errore associato;
  • righe 21-24: si passa alla clausola [finally] in ogni caso, indipendentemente dalla presenza o meno di un'eccezione, per chiudere la connessione al database (riga 23);

Risultati:

Se l’esecuzione dello script avviene senza errori, è possibile verificare la presenza della tabella in phpMyAdmin:

Image

Image

  • in [3] il database;
  • in [4], la tabella visualizzata;
  • in [5], la struttura delle tabelle è presentata nella scheda [Structure];
  • in [6-8], le tre colonne della tabella;
  • in [9], nessuna delle tre colonne può essere vuota;

Image

  • in [10], l’elenco degli indici della tabella. Un indice consente di individuare nella tabella le righe con un determinato indice più rapidamente rispetto a una scansione sequenziale delle righe della tabella. La chiave primaria fa sempre parte degli indici, ma un indice può non essere una chiave primaria;
  • in [11], l’indice è qui la chiave primaria;
  • in [12], l’indice è costituito dalle colonne [nom, prenom] di ogni riga;

Ora vediamo cosa succede se si generano degli errori, rispettivamente sul nome del database, sul nome dell’utente e sulla sua password:

Se si inserisce un nome di database inesistente:


Erreur : SQLSTATE[HY000] [1044] Access denied for user 'admpersonnes'@'%' to database 'dbpersonnes2'

Se si inserisce un nome utente inesistente:


Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes2'@'localhost' (using password: YES)

Se si inserisce una password errata:


Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes'@'localhost' (using password: YES)

12.4. Compilazione di una tabella

Scriveremo uno script PHP che esegue i comandi SQL presenti nel seguente file di testo [creation.txt]:

drop table if exists personnes
SET NAMES 'utf8'
create table personnes (prenom varchar(30) not null, nom varchar(30) not null, age integer not null, primary key (nom,prenom))
insert into personnes (prenom, nom, age) values('Paul','Langevin',48)
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70)
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70)
insert into personnes (prenom, nom, age) values ('Pierre','Nicazou',35)
insert into personnes (prenom, nom, age) values ('Géraldine','Colou',26)
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56)
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56)

Note

  • il linguaggio SQL (Structured Query Language) non fa distinzione tra maiuscole e minuscole nei comandi SQL;
  • riga 1: si elimina la tabella [personnes] se esiste;
  • riga 2: si indica al server MySQL che gli verranno inviati caratteri codificati in UTF-8. Questo comando SQL specifico per MySQL è necessario in questo caso, ad esempio, per inserire nella banca dati la riga 7, ovvero la "é" di Géraldine. Se non si inserisce la riga 2, la "é" verrà convertita in una sequenza di due caratteri strani. Il client è lo script PHP scritto in NetBeans. Questo, però, codifica i file in UTF-8 e [1-4] riportati di seguito:

Image

  • riga 3: creazione della tabella [personnes] con le tre colonne (nome, cognome, età) e la chiave primaria (cognome, nome);
  • righe 4-10: inserimento di 7 righe nella tabella [personnes];
  • riga 6: questo comando di inserimento dovrebbe fallire perché tenta lo stesso inserimento della riga 5. Il vincolo della chiave primaria dovrebbe impedire tale inserimento: non è possibile avere due persone con lo stesso nome e cognome;
  • riga 10: questo comando di inserimento dovrebbe fallire poiché tenta lo stesso inserimento della riga 9;

Lo script PHP incaricato di eseguire i comandi SQL contenuti in questo file di testo è il seguente [mysql-03.php]:


<?php

// ID del database
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenziali utente
const ID = "admpersonnes";
const PWD = "nobody";
// ID del file di testo contenente i comandi SQL da eseguire
const SQL_COMMANDS_FILENAME = "creation.txt";

// apertura della connessione al database MySql
try {
  $connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
  // visualizzazione dell'errore
  print "Erreur : " . $ex->getMessage() . "\n";
  exit;
}
// si desidera che ad ogni errore di SGBD venga generata un'eccezione
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// esecuzione del file di comandi SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// chiusura della connessione
$connexion = NULL;
//visualizzazione del numero di errori
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
  print "$erreurs[$i]\n";
}

// operazione completata
print "Terminé\n";
exit;

// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE, bool $arrêt = TRUE): array {
// utilizza la connessione $connexion
// esegue i comandi SQL contenuti nel file di testo SQLFileName
// questo file è un file di comandi SQL da eseguire uno per riga
// se $suivi=1, allora ogni esecuzione di un comando SQL viene accompagnata da un messaggio che ne indica il successo o il fallimento
// se $arrêt=1, la funzione si interrompe al primo errore riscontrato, altrimenti esegue tutti i comandi SQL
// la funzione restituisce un array (numero di errori, errore1, errore2…)
// si verifica la presenza del file SQLFileName

  if (!file_exists($SQLFileName)) {
    return ["Le fichier [$SQLFileName] n'existe pas"];
  }

  // esecuzione delle query SQL contenute in SQLFileName
  // vengono inserite in una tabella
  $requêtes = file($SQLFileName);
  // errore?
  if ($requêtes === FALSE) {
    return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
  }
  // si eseguono le query una per una - inizialmente nessun errore
  $erreurs = [];
  $i = 0;
  $fini = FALSE;
  while ($i < count($requêtes) && !$fini) {
    // si recupera il testo della query
    // trim rimuoverà il carattere di fine riga
    $requête = trim($requêtes[$i]);
    // richiesta vuota?
    if (strlen($requête) == 0) {
      // si ignora la richiesta e si passa a quella successiva
      $i++;
      continue;
    }
    try {
      // esecuzione della richiesta - potrebbe verificarsi un'eccezione
      $connexion->exec($requête);
      // monitoraggio dello schermo o no?
      if ($suivi) {
        print "$requête : Exécution réussie\n";
      }
    } catch (PDOException $ex) {
      // Si è verificato un errore
      addError($erreurs, $requête, $ex->getMessage(), $suivi);
      // Si interrompe l'esecuzione?
      $fini = $arrêt;
    }
    // richiesta successiva
    $i++;
  }
  // risultato
  return $erreurs;
}

function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
  // si aggiunge un messaggio di errore
  $msg = "$requête : Erreur (" . $msg . ")";
  $erreurs[] = $msg;
  // visualizzazione a schermo o no?
  if ($suivi) {
    print "$msg\n";
  }
}

Commenti

  • la funzione [exécuterCommandes] (righe 36-89) ha il compito di eseguire i comandi SQL che trova nel file di testo [$SQLFileName] (parametro 2). Per eseguirli, utilizza la connessione aperta [$connexion] (parametro 1) con il server MySQL. Il terzo parametro [$suivi] è un valore booleano che controlla le visualizzazioni sullo schermo: con TRUE, l’ordine SQL eseguito viene visualizzato sullo schermo con l’indicazione di successo o fallimento; in caso contrario, l’esecuzione dell’ordine SQL avviene in modo silenzioso. Il quarto parametro [$arrêt] controlla cosa fare quando un comando SQL fallisce: in TRUE, indica che l’esecuzione dei comandi SQL deve interrompersi; in caso contrario, l’esecuzione prosegue. La funzione [exécuterCommandes] restituisce un array di messaggi di errore, vuoto se non si sono verificati errori;
  • righe 11-18: si apre la connessione al database MySQL [dbpersonnes]. Se l’apertura non va a buon fine, viene visualizzato un messaggio di errore e il programma si interrompe (righe 14-18);
  • riga 22: si passa quindi una connessione aperta alla funzione [exécuterCommandes]. La connessione verrà chiusa al ritorno dalla funzione (riga 24);
  • riga 20: prima di passarla alla funzione [exécuterCommandes], si configura la connessione. In caso di errore, le operazioni SQL con un oggetto [PDO] possono restituire il valore booleano FALSE (valore predefinito) oppure generare un'eccezione. La riga 20 opta per il secondo caso. Infatti, è facile «dimenticarsi» di verificare il risultato booleano dell’esecuzione di un comando SQL. Ciò genererà un errore in un secondo momento, ma in un altro punto del codice, rendendo così più difficile individuarne la causa originaria. Nel caso di un’eccezione non gestita (assenza di catch), l’eccezione risalirà nel codice fino a incontrare un catch o fino a raggiungere l’interprete PHP, che la intercetterà. In questo caso, vengono visualizzati la natura dell’eccezione e il punto di origine nel codice;
  • riga 22: viene chiamata la funzione [exécuterCommandes] per eseguire il file di comandi SQL [$SQLFileName];
  • righe 45-47: si verifica che il file di comandi SQL esista effettivamente. In caso contrario, si registra l’errore e si restituisce tale risultato;
  • riga 51: si inseriscono gli ordini SQL in un array [$requêtes]. Righe 53-55: se l’operazione fallisce, si restituisce un array di errori con un unico messaggio;
  • riga 57: si accumulano gli errori nell’array [$erreurs];
  • riga 58: numero della richiesta;
  • riga 59: il valore booleano [$fini] controlla l'esecuzione dei comandi SQL dell'array [$requêtes]. Quando si passa a TRUE, l'esecuzione si interrompe;
  • riga 60: si esegue un ciclo su tutte le richieste;
  • riga 63: si estrae il testo dell’ordine SQL n. i. La funzione [trim] rimuoverà gli spazi che precedono e seguono il testo dell’ordine SQL. Per «spazi» si intendono il carattere di spazio \b, il ritorno a capo \r, il carattere di fine riga \n, il salto di pagina \f, il carattere di tabulazione \t… Ciò che ci interessa in questo caso è che il carattere di fine riga del testo SQL scomparirà;
  • righe 65-69: se il testo SQL è vuoto, allora la richiesta viene ignorata e si passa a quella successiva;
  • riga 72: si invia il comando SQL al server MySQL. Il metodo [PDO::exec] genererà un'eccezione se l'esecuzione fallisce. Si ricorda che questo comportamento è dovuto alla configurazione effettuata alla riga 20;
  • riga 79: il messaggio di errore viene aggiunto all’array degli errori;
  • riga 81: si imposta il valore booleano [$fini] che controlla il ciclo. Se il parametro [$arrêt] (riga 36) è pari a TRUE, il ciclo deve essere interrotto;
  • righe 74-76: se l’esecuzione del comando SQL ha avuto esito positivo, lo si visualizza sullo schermo se il parametro [$suivi] (riga 36) è pari a TRUE;
  • riga 87: una volta eseguiti tutti i comandi SQL, si restituisce la tabella degli errori [$erreurs];

La funzione [adError] delle righe 90-97 consente di aggiungere un errore all'array degli errori [$erreurs]:

  • riga 90: la funzione riceve 4 parametri:
    • il parametro [$erreurs] viene passato per riferimento. Infatti, si vuole agire sull'array passato come parametro e non su una sua copia;
    • il parametro [$requête] è il testo SQL dell’ordine che ha generato l’errore;
    • il parametro [$msg] è il messaggio di errore relativo all'ordine che non è andato a buon fine;
    • il valore booleano [$suivi] indica se il messaggio di errore deve essere visualizzato ($suivi=TRUE) o meno ($suivi=FALSE) sulla console;

La funzione [exécuterCommandes] viene richiamata dallo script alle righe 3-33:

  • righe 11-18: viene stabilita una connessione con il database MySQL [dbpersonnes];
  • riga 20: la connessione viene configurata;
  • riga 22: viene quindi eseguito il file di comandi SQL;
  • riga 24: si chiude la connessione;
  • righe 26-29: vengono visualizzati gli errori restituiti dalla funzione [exécuterCommandes];

Risultati sullo schermo:


drop table if exists personnes : Exécution réussie
SET NAMES 'utf8' : Exécution réussie
create table personnes (prenom varchar(30) not null, nom varchar(30) not null, age integer not null, primary key (nom,prenom)) : Exécution réussie
insert into personnes (prenom, nom, age) values('Paul','Langevin',48) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Lefur-Sylvie' for key 'PRIMARY')
insert into personnes (prenom, nom, age) values ('Pierre','Nicazou',35) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Géraldine','Colou',26) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Girond-Paulette' for key 'PRIMARY')

-----------------------
Il y a eu 2 erreur(s)
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Lefur-Sylvie' for key 'PRIMARY')
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Girond-Paulette' for key 'PRIMARY')
Terminé

Gli inserimenti effettuati sono visibili con phpMyAdmin:

Image

12.5. Esecuzione di comandi SQL a scelta

Il seguente script mostra l'esecuzione dei comandi SQL dal seguente file di testo [sql.txt]:

1
2
3
4
5
6
7
8
9
select * from personnes
select nom,prenom from personnes order by nom asc, prenom desc
select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
insert into personnes values('Josette','Bruneau',46)
update personnes set age=47 where nom='Bruneau'
select * from personnes where nom='Bruneau'
delete from personnes where nom='Bruneau'
select * from personnes where nom='Bruneau'
xselect * from personnes where nom='Bruneau'

Tra questi comandi SQL, vi è il comando select che restituisce risultati dal database, i comandi insert, update, delete che modificano il database senza restituire risultati e infine comandi errati come l'ultimo (xselect). Lo script [mysql-04.php] è il seguente:


<?php

// identificativo del database
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenziali dell'utente
const ID = "admpersonnes";
const PWD = "nobody";
// identificativo del file di testo contenente i comandi SQL da eseguire
const SQL_COMMANDS_FILENAME = "sql.txt";

try {
  // connessione al database MySql
  $connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
  // visualizzazione degli errori
  print "Erreur : " . $ex->getMessage() . "\n";
  exit;
}
// si desidera che ad ogni errore di SGBD venga generata un'eccezione
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// esecuzione del file di comandi SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// chiusura della connessione
$connexion = NULL;
//visualizzazione del numero di errori
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
  print "$erreurs[$i]\n";
}

// operazione completata
print "Terminé\n";
exit;

// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE, bool $arrêt = TRUE): array {
………………………………………………………….
  // si eseguono le richieste una per una - inizialmente nessun errore
  $erreurs = [];
  $i = 0;
  $fini = FALSE;
  while ($i < count($requêtes) && !$fini) {
    // si recupera il testo della richiesta
    // trim rimuoverà il carattere di fine riga
    $requête = trim($requêtes[$i]);
    // richiesta vuota?
    if (strlen($requête) == 0) {
      // si ignora la richiesta e si passa a quella successiva
      $i++;
      continue;
    }
    // esecuzione della richiesta
    // si recupera il nome
    $commande = "";
    if (preg_match("/^\s*(\S+)/", $requête, $champs)) {
      $commande = strtolower($champs[0]);
    }
    try {
      // si tratta di un ordine SELECT?
      if ($commande === "select") {
        $résultat = $connexion->query($requête);
      } else {
        $résultat = $connexion->exec($requête);
      }
      // monitoraggio sullo schermo o no?
      if ($suivi) {
        print "[$requête] : Exécution réussie\n";
      }
      // visualizzazione del risultato dell'esecuzione
      afficherInfos($commande, $résultat);
    } catch (PDOException $ex) {
      // Si è verificato un errore
      addError($erreurs, $requête, $ex->getMessage(), $suivi);
      // ci si ferma?
      $fini = $arrêt;
    }
    // richiesta successiva
    $i++;
  }
  // risultato
  return $erreurs;
}

function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
  
}

// ---------------------------------------------------------------------------------
function afficherInfos(string $commande, $résultat): void {
  // visualizza il risultato $résultat di una query SQL
  // si trattava di un SELECT?
  switch ($commande) {
    case "select" :
      // vengono visualizzati i nomi dei campi
      $titre = "";
      $nbColonnes = $résultat->columnCount();
      for ($i = 0; $i < $nbColonnes; $i++) {
        $infos = $résultat->getColumnMeta($i);
        $titre .= $infos['name'] . ",";
      }
      // si rimuove l'ultimo carattere,
      $titre = substr($titre, 0, strlen($titre) - 1);
      // si visualizza l'elenco dei campi
      print "$titre\n";
      // riga di separazione
      $séparateurs = "";
      for ($i = 0; $i < strlen($titre); $i++) {
        $séparateurs .= "-";
      }
      print "$séparateurs\n";
      // dati
      foreach ($résultat as $ligne) {
        $data = "";
        for ($i = 0; $i < $nbColonnes; $i++) {
          $data .= $ligne[$i] . ",";
        }
        // si rimuove l'ultimo carattere ,
        $data = substr($data, 0, strlen($data) - 1);
        // visualizza
        print "$data\n";
      }
      break;
    case "update":
    case "insert":
    case "delete";
      print " $résultat lignes(s) a (ont) été modifiée(s)\n";
      break;
  }
}

Commenti

  • righe 36-83: la funzione [exécuterCommandes] è leggermente modificata: il comando SQL [select] non viene eseguito allo stesso modo degli altri comandi SQL. Questo comando è l'unico a restituire come risultato una tabella, ovvero un insieme di righe e colonne del database;
  • righe 55-57: si isola la prima parola del comando SQL utilizzando un’espressione regolare;
  • righe 60-64: se il comando SQL è [select], si utilizza il metodo [PDO::query], altrimenti il metodo [PDO::exec] per eseguire il comando SQL. In entrambi i casi, se l’esecuzione fallisce, verrà generata un’eccezione che verrà intercettata alle righe 71-77. Se l’esecuzione va a buon fine, la riga 70 ne visualizza il risultato;
  • righe 90-130: la funzione afficherInfos visualizza informazioni sul risultato dell’esecuzione di un ordine SQL;
  • riga 94: si gestisce il caso del [select]. Il suo risultato è un oggetto di tipo [PDOStatement];
  • riga 96: il metodo [PDOStatement::getColumnCount()] restituisce il numero di colonne della tabella risultante da select;
  • righe 98-99: il metodo [PDOStatement::getMeta(i)] restituisce un dizionario contenente informazioni sulla colonna n. i della tabella risultante da select.. In questo dizionario, il valore associato alla chiave 'name' è il nome della colonna;
  • righe 97-102: i nomi delle colonne della tabella di risultato di select vengono concatenati in una stringa di caratteri;
  • righe 105-110: viene creata una riga di separazione della stessa lunghezza della stringa di caratteri creata in precedenza;
  • righe 112-121: un oggetto di tipo PDOStatement può essere iterato tramite un ciclo foreach. Ad ogni iterazione, l'elemento ottenuto è una riga della tabella dei risultati del ciclo select sotto forma di un array di valori che rappresentano i valori delle diverse colonne della riga. Tutti questi valori vengono visualizzati con un ciclo for (righe 114-116);
  • righe 123-127: il risultato dell'esecuzione di un ordine insert, update, delete è il numero di righe modificate dall'ordine;

Risultati sullo schermo:


[set names 'utf8'] : Exécution réussie
[select * from personnes] : Exécution réussie
prenom,nom,age
--------------
Géraldine,Colou,26
Paulette,Girond,56
Paul,Langevin,48
Sylvie,Lefur,70
Pierre,Nicazou,35
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom,prenom
----------
Colou,Géraldine
Girond,Paulette
Langevin,Paul
Lefur,Sylvie
Nicazou,Pierre
[select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
prenom,nom,age
--------------
Pierre,Nicazou,35
Géraldine,Colou,26
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,47
[delete from personnes where nom='Bruneau'] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 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 'xselect * from personnes where nom='Bruneau'' at line 1)

-----------------------
Il y a eu 1 erreur(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 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 'xselect * from personnes where nom='Bruneau'' at line 1)
Terminé

12.6. Utilizzo di comandi SQL preparati

12.6.1. Esempio 1

Esaminiamo il seguente script [mysql-05.php]:


<?php

// identificativo del database
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenziali dell'utente
const ID = "admpersonnes";
const PWD = "nobody";

try {
  // connessione al database MySql
  $connexion = new PDO(DSN, ID, PWD);
  // si desidera che ad ogni errore di SGBD venga generata un'eccezione
  $connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  // si svuota la tabella delle persone
  $connexion->exec("delete from personnes");
  // un elenco di persone
  $personnes = [];
  $personnes[] = ["nom" => "Langevin", "prenom" => "Paul", "age" => 47];
  $personnes[] = ["nom" => "Lefur", "prenom" => "Sylvie", "age" => 28];
  // si inseriranno queste persone nel database
  $statement = $connexion->prepare("insert into personnes (nom, prenom, age) values (:nom, :prenom, :age)");
  for ($i = 0; $i < count($personnes); $i++) {
    $statement->execute($personnes[$i]);
  }
} catch (PDOException $ex) {
  // visualizzazione dell'errore
  print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// chiusura della connessione
  $connexion = NULL;
}

// È tutto
print "Terminé\n";
exit;

Commenti

Ci concentriamo qui sulle righe 16-24 che inseriscono due persone nella tabella delle persone del database [dbpersonnes].

  • riga 21: si “prepara” un ordine SQL con i parametri specificati. I parametri sono preceduti dal carattere : :cognome, :nome, :età. Per “preparare” un ordine SQL, si utilizza il metodo [PDO::prepare]. Il risultato è un tipo [PDOStatement]. La “preparazione” non è un’esecuzione: non viene eseguito nulla;
  • riga 23: esecuzione dell’ordine “preparato” con il metodo [PDOStatement::execute]. A tal fine, è necessario assegnare dei valori ai parametri :nom, :prenom e :age. Esistono diversi modi per farlo. Qui si utilizza un dizionario con come chiavi i parametri dell’ordine preparato, che vengono passati al metodo [PDOStatement::execute]. Un altro modo per farlo è assegnare un valore ai parametri con il metodo [PDOStatement::bindValue($paramètre,$valeur)]. Ad esempio qui:

$statement→bindValue(“nom”,”Langevin”);
$statement→bindValue(“prenom”,”Paul”);
$statement→bindValue(“age”,47);
$statement→execute();

Lo svantaggio è che occorre ripetere questa istruzione per ogni parametro. Il metodo del dizionario può quindi risultare più pratico. Il metodo [PDOStatement::execute] restituisce FALSE se l’esecuzione fallisce;

  • il metodo utilizzato qui per effettuare gli inserimenti:
    • un metodo preparato;
    • n esecuzioni del comando preparato;

è più efficiente in termini di tempo di esecuzione rispetto all’esecuzione di n diversi comandi SQL. Questo metodo è quindi da privilegiare. È utilizzabile per i comandi SQL select, update, delete, insert. Nel caso dell’ordine SQL select, dopo la sua esecuzione con [PDOStatement::execute], si recuperano le righe del risultato con il metodo [PDOStatement::fetchAll];

12.6.2. Esempio 2

Il seguente script [mysql-06.php] mostra l’utilizzo di un ordine preparato per l’operazione SQL select, nonché diversi modi per recuperare le righe restituite da tale operazione:


<?php

// identificativo del database
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenziali dell'utente
const ID = "admpersonnes";
const PWD = "nobody";

try {
  // connessione al database MySql
  $connexion = new PDO(DSN, ID, PWD);
  // si desidera che ad ogni errore di SGBD venga generata un'eccezione
  $connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  // si svuota la tabella delle persone
  $connexion->exec("delete from personnes");
  // si inseriscono queste persone nel database
  $statement = $connexion->prepare("insert into personnes (nom, prenom, age) values (:nom, :prenom, :age)");
  for ($i = 0; $i < 10; $i++) {
    $statement->execute(["nom" => "nom" . $i, "prenom" => "prenom" . $i, "age" => $i * 10]);
  }
  // si esegue una query sul database
  $statement = $connexion->prepare("select nom, prenom, age from personnes");
  $statement->execute();
  // prima riga
  $ligne = $statement->fetch();
  var_dump($ligne);
  // seconda riga
  $ligne = $statement->fetch(PDO::FETCH_ASSOC);
  var_dump($ligne);
  // Terza riga
  $ligne = $statement->fetch(PDO::FETCH_OBJ);
  var_dump($ligne);
  // quarta riga
  $statement->setFetchMode(PDO::FETCH_CLASS, "Person");
  $ligne = $statement->fetch();
  var_dump($ligne);
  // lettura sequenziale di tutte le righe
  $statement = $connexion->prepare("select nom, prenom, age from personnes");
  $statement->execute();
  $statement->setFetchMode(PDO::FETCH_CLASS, "Person");
  while ($personne = $statement->fetch()) {
    print "$personne\n";
  }
} catch (PDOException $ex) {
  // visualizzazione errore
  print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// chiusura della connessione
  $connexion = NULL;
}

// fine
print "Terminé\n";
exit;

class Person {
  private $nom;
  private $prenom;
  private $age;

  public function __toString() {
    return "Personne[$this->nom,$this->prenom,$this->age]";
  }

}

Commenti

  • righe 17-20: si inseriscono 10 righe nella tabella [personnes] del database [admpersonnes]:

Image

  • riga 22: si «prepara» un ordine SQL [select] che viene eseguito alla riga 23;
  • riga 25: con il metodo [PDOStatement::fetch] si recupera una riga del risultato dell'operazione SQL [select] eseguita. Il metodo [PDOStatement::fetch] può recuperare in vari modi le righe risultanti da un'operazione SQL [select] preparata. Lo script ne illustra alcune. Il metodo [PDOStatement::fetch] senza parametri restituisce la riga corrente del [select] sotto forma di un dizionario indicizzato sia sui numeri delle colonne che sui loro nomi;
  • riga 26: visualizza il seguente risultato:

array(6) {
  ["nom"]=>
  string(4) "nom0"
  [0]=>
  string(4) "nom0"
  ["prenom"]=>
  string(7) "prenom0"
  [1]=>
  string(7) "prenom0"
  ["age"]=>
  string(1) "0"
  [2]=>
  string(1) "0"
}
  • righe 28-29: il parametro [PDO::FETCH_ASSOC] fa sì che la riga restituita sia un dizionario indicizzato in base ai nomi delle colonne della tabella:
1
2
3
4
5
6
7
8
array(3) {
  ["nom"]=>
  string(4) "nom1"
  ["prenom"]=>
  string(7) "prenom1"
  ["age"]=>
  string(2) "10"
}
  • righe 31-32: il parametro [PDO::FETCH_OBJ] fa sì che la riga restituita sia un oggetto di tipo [stdclass] i cui attributi sono i nomi delle colonne della tabella:
1
2
3
4
5
6
7
8
object(stdClass)#2 (3) {
  ["nom"]=>
  string(4) "nom2"
  ["prenom"]=>
  string(7) "prenom2"
  ["age"]=>
  string(2) "20"
}
  • riga 34: si imposta la modalità di ricerca del metodo [fetch] tramite il metodo [PDOStatement::setFetchMode]. Questa modalità diventa quindi quella predefinita finché non viene modificata tramite un’altra operazione [PDOStatement::setFetchMode] oppure passando una modalità come parametro al metodo [PDOStatement::fetch], come è stato fatto in precedenza. L’operazione [setFetchMode(PDO::FETCH_CLASS, "Person")] indica che la riga letta deve essere inserita in un oggetto di tipo [Person]. Questa classe deve avere, tra i propri attributi, alcuni che riportano i nomi delle colonne della riga letta. È il caso della classe [Person] definita alle righe 56-63;
  • la riga 36 mostra il seguente risultato:
1
2
3
4
5
6
7
8
object(Person)#4 (3) {
  ["nom":"Person":private]=>
  string(4) "nom3"
  ["prenom":"Person":private]=>
  string(7) "prenom3"
  ["age":"Person":private]=>
  string(2) "30"
}
  • righe 38-43: mostrano come utilizzare in sequenza i risultati di [select];
  • riga 42: la visualizzazione di [$personne] utilizzerà il metodo [__toString] della classe [Person];

12.7. Utilizzo delle transazioni

Una transazione consente di raggruppare una sequenza di comandi SQL in un’unica unità di esecuzione: o tutti i comandi vanno a buon fine oppure uno di essi fallisce e, in tal caso, tutti i comandi SQL che lo hanno preceduto vengono annullati. In altre parole, quando si utilizza una transazione per eseguire gli ordini SQL, al termine della transazione il database si trova in uno stato stabile:

  • o in un nuovo stato creato dall’esecuzione riuscita di tutti gli ordini SQL della transazione;
  • oppure nello stato in cui si trovava prima che la transazione iniziasse ad essere eseguita;

Riprendiamo l’esempio dell’esecuzione dei comandi SQL contenuti in un file di testo analizzato nel paragrafo «link». Includeremo questa esecuzione in una transazione. I comandi SQL saranno contenuti nel seguente file [sql2.txt]:


set names 'utf8'
select * from personnes
select nom,prenom from personnes order by nom asc, prenom desc
select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
insert into personnes values('Josette','Bruneau',46)
update personnes set age=47 where nom='Bruneau'
select * from personnes where nom='Bruneau'
delete from personnes where nom='Bruneau'
select * from personnes where nom='Bruneau'
insert into personnes values('Josette','Bruneau',46)
select * from personnes where nom='Bruneau'
xselect * from personnes where nom='Bruneau'

L’ordine errato della riga 12 causerà il fallimento dell’intera transazione. Dovremmo quindi ritrovare il database così com’era prima della transazione. Nell’esempio sopra riportato, non dovremmo quindi vedere nella tabella la riga inserita dalla riga 10 sopra indicata. Lo script cambia molto poco. Riportiamo comunque il codice completo [mysql-07.php]:


<?php

// identificativo del database
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenziali dell'utente
const ID = "admpersonnes";
const PWD = "nobody";
// identificativo del file di testo contenente i comandi SQL da eseguire
const SQL_COMMANDS_FILENAME = "sql2.txt";

try {
  // connessione al database MySql
  $connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
  // visualizzazione degli errori
  print "Erreur : " . $ex->getMessage() . "\n";
  exit;
}
// si desidera che ad ogni errore di SGBD venga generata un'eccezione
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// esecuzione del file di comandi SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE);
// chiusura della connessione
$connexion = NULL;
//visualizzazione del numero di errori
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
  print "$erreurs[$i]\n";
}

// operazione completata
print "Terminé\n";
exit;

// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE): array {
// utilizza la connessione $connexion
// esegue i comandi SQL contenuti nel file di testo SQLFileName
// questo file è un file di comandi SQL da eseguire, uno per riga
// i comandi SQL vengono eseguiti in una transazione
// se uno degli ordini fallisce, la transazione viene annullata e il database torna allo stato in cui si trovava prima della transazione
// se $suivi=1, allora ogni esecuzione di un comando SQL viene accompagnata da un messaggio che ne indica il successo o il fallimento
// la funzione restituisce un array (numero di errori, errore1, errore2…)
//
// si verifica la presenza del file SQLFileName
  if (!file_exists($SQLFileName)) {
    return ["Le fichier [$SQLFileName] n'existe pas"];
  }
  // esecuzione delle query SQL contenute in SQLFileName
  // vengono inserite in una tabella
  $requêtes = file($SQLFileName);
  // errore?
  if ($requêtes === FALSE) {
    return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
  }
  // le richieste verranno inserite in una transazione
  $connexion->beginTransaction();
  // si eseguono le query una per una - inizialmente nessun errore
  $erreurs = [];
  $i = 0;
  $fini = FALSE;
  while ($i < count($requêtes) && !$fini) {
    // si recupera il testo della query
    // trim rimuoverà il carattere di fine riga
    $requête = trim($requêtes[$i]);
    // Richiesta vuota?
    if (strlen($requête) == 0) {
      // si ignora la richiesta e si passa a quella successiva
      $i++;
      continue;
    }
    // esecuzione della richiesta
    // si recupera il nome
    $commande = "";
    if (preg_match("/^\s*(\S+)/", $requête, $champs)) {
      $commande = strtolower($champs[0]);
    }
    try {
      // si tratta di un ordine SELECT?
      if ($commande === "select") {
        $résultat = $connexion->query($requête);
      } else {
        $résultat = $connexion->exec($requête);
      }
      // monitoraggio sullo schermo o no?
      if ($suivi) {
        print "[$requête] : Exécution réussie\n";
      }
      // visualizzazione del risultato dell'esecuzione
      afficherInfos($commande, $résultat);
    } catch (PDOException $ex) {
      // Si è verificato un errore
      addError($erreurs, $requête, $ex->getMessage(), $suivi);
      // si passa al ciclo successivo
      $fini = TRUE;
    }
    // richiesta successiva
    $i++;
  }
  // fine della transazione
  if (!$fini) {
    // non si sono verificati errori: la transazione viene convalidata
    $connexion->commit();
  } else {
    // si sono verificati degli errori: la transazione viene annullata
    $connexion->rollBack();
    // errore di aggiunta
    addError($erreurs, "", "Transaction annulée", $suivi);
  }
  // risultato
  return $erreurs;
}

function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
  
}

// ---------------------------------------------------------------------------------
function afficherInfos(string $commande, $résultat): void {
  
}

Commenti

Abbiamo evidenziato le modifiche apportate allo script originale [mysql-04.php].

  • righe 22, 36: la funzione [exécuterCommandes] ha perso il suo quarto parametro [$arrêt=TRUE]. Infatti, poiché i comandi SQL vengono eseguiti all’interno di una transazione, qualsiasi errore provocherà l’interruzione della transazione;
  • righe 40-41: richiamo della funzione all’interno di una transazione;
  • riga 57: si avvia una transazione. Da questo momento in poi, ogni comando SQL eseguito nel ciclo delle righe 62-99 viene eseguito all’interno di questa transazione;
  • righe 101-109: il valore booleano [$fini] assume il valore TRUE se si è verificato un errore (riga 95). Quando il valore è FALSE, non si sono verificati errori e la transazione viene quindi convalidata (riga 103). Quando il valore è TRUE, si sono verificati degli errori e la transazione viene quindi annullata (riga 106) e l’errore di transazione viene aggiunto all’elenco degli errori (riga 108);

Risultati

Prima dell’esecuzione dello script, il database [admpersonnes] si trova nelle seguenti condizioni:

Image

Si esegue lo script [mysql-07.php]. Le schermate visualizzate sono le seguenti:


[set names 'utf8'] : Exécution réussie
[select * from personnes] : Exécution réussie
prenom,nom,age
--------------
prenom0,nom0,0
prenom1,nom1,10
prenom2,nom2,20
prenom3,nom3,30
prenom4,nom4,40
prenom5,nom5,50
prenom6,nom6,60
prenom7,nom7,70
prenom8,nom8,80
prenom9,nom9,90
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom,prenom
----------
nom0,prenom0
nom1,prenom1
nom2,prenom2
nom3,prenom3
nom4,prenom4
nom5,prenom5
nom6,prenom6
nom7,prenom7
nom8,prenom8
nom9,prenom9
[select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
prenom,nom,age
--------------
prenom4,nom4,40
prenom3,nom3,30
prenom2,nom2,20
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,47
[delete from personnes where nom='Bruneau'] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,46
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 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 'xselect * from personnes where nom='Bruneau'' at line 1)
[] : Erreur (Transaction annulée)

-----------------------
Il y a eu 2 erreur(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 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 'xselect * from personnes where nom='Bruneau'' at line 1)
[] : Erreur (Transaction annulée)
Terminé
  • riga 53: si verifica un errore nel comando [xselect];
  • riga 54: la transazione viene quindi annullata;

Se si verifica lo stato del database, lo si trova nelle stesse condizioni di prima dell’esecuzione dello script. In particolare, non è visibile la riga [Josette, Bruneau, 46] della riga 52 dei risultati sopra riportati.

Image

Riepilogo

  • una transazione inizia con il metodo [PDO::beginTransaction];
  • viene completata con esito positivo con il metodo [PDO::commit];
  • viene conclusa con esito negativo tramite il metodo [PDO::rollback];

Quando si utilizza un database, è buona norma inserire ogni operazione SQL in una transazione per isolarsi dagli altri utenti del database (la transazione svolge anche questa funzione). Una transazione deve essere il più breve possibile. Non bisogna quindi dimenticare di terminarla con un [commit] o un [rollback] a seconda dei casi.