12. Utilização do SGBD MySQL

Vamos agora escrever scripts PHP utilizando uma base de dados MySQL:

Na arquitetura acima, o script PHP (1) não comunica diretamente com o SGBD (Sistema de Gestão de Bases de Dados) (3). Interage com um intermediário denominado «piloto» de SGBD ou ainda «driver» de SGBD. O PHP fornece uma interface padrão para estes controladores, a interface PDO (PHP Data Objects). Esta interface é implementada por diferentes classes adaptadas a cada SGBD: uma classe para o SGBD MySQL, outra para o SGBD PostgreSQL… Para mudar de SGBD, muda-se de controlador:

O controlador PDO isola o script PHP (1) do SGBD (3, 6). Como estes controladores implementam uma interface padrão, é de esperar que o script PHP (1) não se altere se passarmos do SGBD, MySQL (3) para o SGBD PostgreSQL (6). Na realidade, esse ideal não existe. Com efeito, para comunicar com o SGBD, o script PHP envia comandos SQL (Standard Query Language). Trata-se de uma linguagem implementada por todos os SGBD, mas que é incompleta. Por isso, os SGBD adicionaram-lhe comandos proprietários. Esta é uma das principais causas de incompatibilidade entre os SGBD. Além disso, os tipos de dados utilizáveis nas bases de dados podem variar de um SGBD para outro. Assim, o PostgreSQL aceita um número de tipos de dados muito maior do que o SGBD e o MySQL. Esta é uma segunda causa de incompatibilidade. Outra causa é a gestão das chaves primárias automáticas (geradas pelo SGBD): praticamente cada SGBD tem a sua própria política. Etc… As causas de incompatibilidade são numerosas.
Se quisermos evitar reescrever o script PHP (1), passando do MySQL (3) para o PostgreSQL (6), geralmente somos levados a inserir uma nova camada entre o script PHP (1) e o controlador PDO (2, 5), cuja função será eliminar as incompatibilidades entre os dois SGBD. No entanto, nos casos simples com que nos vamos deparar, esta camada adicional não será necessária.
Vamos agora utilizar o SGBD MySQL. Este está incluído no pacote Laragon (ver parágrafo com o link).
Se o leitor for iniciante no que diz respeito ao conceito de base de dados e à linguagem SQL, poderá consultar o documento [http://sergetahe.com/cours-tutoriels-de-programmation/cours-tutoriel-sql-avec-le-sgbd-firebird/]. Este documento utiliza o Firebird SGBD e não o MySQL, mas apresenta os conceitos fundamentais das bases de dados e da linguagem SQL. Tal como o MySQL, o Firebird dispõe de uma versão de utilização livre e com baixo consumo de memória.
12.1. Criação de uma base de dados
Vamos agora mostrar como criar uma base de dados e um utilizador MySQL com a ferramenta Laragon.

- uma vez iniciado, o Laragon [1] pode ser gerido a partir de um menu [2];
- em [3-5], instala-se a ferramenta [phpMyAdmin] de administração do MySQL, caso ainda não tenha sido instalada;

- no [6], inicia-se o servidor web Apache, bem como o SGBD e o MySQL;
- em [7], o servidor Apache é iniciado;
- em [8], o SBD MySQL é iniciado;

- em [8-10], cria-se uma base de dados a que se dá o nome de [dbpersonnes] [11]. Vamos construir uma base de dados de pessoas;

- em [11], vamos gerir a base de dados que acabámos de criar;

- a operação [Bases de données] envia um pedido web para o URL [http://localhost/phpmyadmin]. É o servidor web Apache do Laragon que responde. O URL [http://localhost/phpmyadmin] é o URL do utilitário [phpMyAdmin] que instalámos anteriormente, o [5]. Este utilitário permite gerir as bases de dados MySQL;
- por predefinição, as credenciais de ligação do administrador da base de dados são: root [13] sem palavra-passe [14];

- na base de dados [16], que criámos anteriormente;

- por enquanto, temos uma base de dados [dbpersonnes], [17], que está vazia, e [18];
Criamos um utilizador [admpersonnes] com a palavra-passe [nobody], que terá todos os direitos sobre a base de dados [dbpersonnes]:

- em [19], estamos posicionados na base de dados [dbpersonnes];
- em [20], selecionamos o separador [Privileges];
- em [21-22], verifica-se que o utilizador [root] tem todos os direitos sobre a base de dados [dbpersonnes];
- em [23], cria-se um novo utilizador;

- em [25-26], o utilizador terá o identificador [admdbpersonnes];
- em [27-29], a sua palavra-passe será [nobody];
- em [30], phpMyAdmin indica que a palavra-passe é muito fraca (fácil de descodificar). Em produção, é preferível gerar uma palavra-passe forte com [31];
- em [32], indica-se que o utilizador [admdbpersonnes] deve ter todos os direitos sobre a base de dados [dbpersonnes];
- em [33], validam-se os dados fornecidos;

- em [35], phpMyAdmin indica que o utilizador foi criado;
- em [36], a ordem SQL que foi emitida na base de dados;
- em [37], o utilizador [admpersonnes] tem todos os direitos sobre a base de dados [dbpersonnes];
Agora temos:
- uma base de dados MySQL [dbpersonnes];
- um utilizador [admpersonnes/nobody] que tem todos os direitos sobre esta base de dados;
Vamos escrever scripts PHP para explorar a base de dados. O PHP dispõe de várias bibliotecas para gerir as bases de dados. Iremos utilizar a biblioteca PDO (PHP Data Objects), que se insere entre o código PHP e o SGBD:

A biblioteca PDO permite que o script PHP ignore a natureza exata do SGBD utilizado. Assim, no exemplo acima, o SGBD MySQL pode ser substituído pelo SGBD PostgreSQL com um impacto mínimo no código do script PHP. Esta biblioteca não está disponível por predefinição. É possível verificar a sua disponibilidade da seguinte forma:

- em [1-4], verifica-se quais as extensões PDO ativas;
- no [5], verifica-se que a extensão PDO para o SGBD e o MySQL está ativa. As outras não estão. Basta clicar nelas para as ativar;
Outra forma de ativar uma extensão é modificar diretamente o ficheiro [php.ini] (parágrafo «ligação») que configura o PHP:

- No [1], a extensão PDO do MySQL está ativada;
- no [2], a extensão PDO do Firebird está desativada;
Após alterar o ficheiro [php.ini], é necessário reiniciar o PHP do Laragon para que as alterações sejam aplicadas.
12.2. Ligação a uma base de dados MySQL
A ligação a um SGBD é feita através da criação de um objeto PDO. O construtor aceita vários parâmetros:
O significado dos parâmetros é o seguinte:
$dsn | (Data Source Name) é uma cadeia de caracteres que especifica a natureza do SGBD e a sua localização na Internet. A cadeia «mysql:host=localhost» indica que se trata de um SGBD MySQL a funcionar no servidor local. Esta cadeia pode incluir outros parâmetros, nomeadamente a porta de escuta do SGBD e o nome da base à qual se pretende ligar: «mysql:host=localhost:port=3306:dbname=dbpersonnes»; |
$user | identificador do utilizador que se liga; |
$passwd | a sua palavra-passe; |
$driver_options | um conjunto de opções para o controlador do SGBD; |
Apenas o primeiro parâmetro é obrigatório. O objeto assim criado servirá, posteriormente, de suporte para todas as operações realizadas na base de dados à qual se estabeleceu a ligação. Se não for possível criar o objeto PDO, é lançada uma exceção do tipo PDOException.
Eis um exemplo de ligação [mysql-01.php]:
<?php
// ligação a uma base de dados local MySql
// a identidade do utilizador é (admpersonnes,nobody)
const ID = "admpersonnes";
const PWD = "nobody";
const HOTE = "localhost";
try {
// ligação
$dbh = new PDO("mysql:host=".HOTE, ID, PWD);
print "Connexion réussie\n";
// encerramento da ligação
$dbh = NULL;
} catch (PDOException $e) {
print "Erreur : " . $e->getMessage() . "\n";
exit();
}
Resultados:
Comentários
- linha 11: a ligação a um SGBD é feita através da criação de um objeto PDO. O construtor é aqui utilizado com os seguintes parâmetros:
- uma cadeia de caracteres que especifica a natureza do SGBD e a sua localização na Internet. A cadeia «mysql:host=localhost» indica que se trata de um SGBD MySQL a operar no servidor local. A porta não foi especificada. Nesse caso, é utilizada a porta 3306 por predefinição. O nome da base de dados também não é indicado. Será então estabelecida uma ligação ao SGBD MySQL, sendo a seleção de uma base de dados específica efetuada posteriormente;
- um nome de utilizador;
- a sua palavra-passe;
- linha 14: o encerramento da ligação é efetuado através da eliminação do objeto PDO criado inicialmente;
- linha 15: a ligação a um SGBD pode falhar. Nesse caso, é lançada uma exceção do tipo PDOException. Esta deriva da exceção PHP [RuntimeException];
- linha 16: é exibida a mensagem de erro da exceção;
Vamos executar novamente o script, introduzindo uma palavra-passe errada na linha 6. O resultado é então o seguinte:
Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes'@'localhost' (using password: YES)
12.3. Criação de uma tabela
O script [mysql-02.php] mostra a criação de uma tabela numa base de dados:
<?php
// identidade da base de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenciais do utilizador
const ID = "admpersonnes";
const PWD = "nobody";
try {
// ligação à base de dados MySql
$connexion = new PDO(DSN, ID, PWD);
// eliminação da tabela «pessoas», caso exista
$sql = "drop table personnes";
$connexion->exec($sql);
// criação da tabela «pessoas»
$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) {
// exibição de erro
print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// desligar-se, se necessário
$connexion = NULL;
}
// fim
print "Terminé\n";
exit;
Comentários
- linha 11: ligação à base de dados. É sempre a primeira coisa a fazer. O resultado da ligação é um objeto [PDO], através do qual serão realizadas as operações na base de dados;
- linha 13: a ordem SQL [drop table personnes] irá eliminar a tabela [personnes] da base de dados [dbpersonnes]. Se a tabela [personnes] não existir, isso não provoca qualquer erro;
- linha 14: execução do comando SQL anterior na base de dados [dbpersonnes]. Esta execução pode iniciar um [PDOException] que será interceptado na linha 18;
- linha 16: esta ordem SQL cria uma tabela [personnes]. Uma tabela contém linhas e colunas. As colunas formam o que se denomina a estrutura da tabela. As linhas formam o conteúdo da tabela. Uma base de dados pode conter uma ou mais tabelas. A tabela [personnes] terá, neste caso, três colunas:
- nome próprio: o nome próprio de uma pessoa na forma de uma cadeia de, no máximo, 30 caracteres;
- apelido: o apelido dessa mesma pessoa na forma de uma cadeia de, no máximo, 30 caracteres;
- idade: a idade da pessoa na forma de um número inteiro;
- o atributo NOT NULL numa coluna exige que a coluna tenha um valor. Não lhe atribuir um valor provoca um [PDOException];
- [primary key(nom,prenom)] define uma chave primária para a tabela [personnes]. Uma chave primária tem um valor único para cada linha da tabela. Neste caso, a chave primária será obtida através da concatenação das colunas [nom] e [prenom] da linha. Esta restrição faz com que, na tabela, não possam existir duas pessoas com o mesmo nome e apelido, ou seja, dois homónimos. Criar um homónimo de uma pessoa na tabela provoca um erro [PDOException];
- linha 17: execução da ordem SQL na base de dados [dbpersonnes];
- linha 20: se ocorrer um [PDOException], é apresentada a mensagem de erro associada;
- linhas 21-24: passa-se para a cláusula [finally] em todos os casos, haja ou não exceção, para encerrar a ligação à base de dados (linha 23);
Resultados:
Se a execução do script decorrer sem erros, é possível verificar a existência da tabela em phpMyAdmin:


- em [3], a base de dados;
- em [4], a tabela apresentada;
- em [5], a estrutura das tabelas é apresentada no separador [Structure];
- em [6-8], as três colunas da tabela;
- em [9], nenhuma das três colunas pode estar vazia;

- em [10], a lista de índices da tabela. Um índice permite localizar na tabela as linhas com um determinado índice, mais rapidamente do que se percorressemos sequencialmente as linhas da tabela. A chave primária faz sempre parte dos índices, mas um índice pode não ser uma chave primária;
- em [11], o índice é, neste caso, a chave primária;
- em [12], o índice é constituído pelas colunas [nom, prenom] de cada linha;
Agora, vamos ver o que acontece se criarmos erros, respetivamente no nome da base de dados, no nome do utilizador e na sua palavra-passe:
Se introduzirmos um nome de base de dados inexistente:
Erreur : SQLSTATE[HY000] [1044] Access denied for user 'admpersonnes'@'%' to database 'dbpersonnes2'
Se introduzirmos um nome de utilizador inexistente:
Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes2'@'localhost' (using password: YES)
Se introduzirmos uma palavra-passe errada:
Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes'@'localhost' (using password: YES)
12.4. Preenchimento de uma tabela
Vamos escrever um script PHP que executa as ordens SQL encontradas no seguinte ficheiro de texto [creation.txt]:
Comentários
- a linguagem SQL (Structured Query Language) não distingue entre maiúsculas e minúsculas nos comandos SQL;
- linha 1: elimina-se a tabela [personnes], caso exista;
- linha 2: indica-se ao servidor MySQL que lhe serão enviados caracteres codificados em UTF-8. Esta ordem SQL, específica para MySQL, é necessária aqui, por exemplo, para que a linha 7, o «é» de Géraldine, conste na base de dados. Se não se incluir a linha 2, o «é» será traduzido numa sequência de dois caracteres estranhos. O cliente é o script PHP escrito no NetBeans. Ora, este codifica os ficheiros em UTF-8 e [1-4], conforme se pode ver abaixo:

- linha 3: criação da tabela [personnes] com as três colunas (nome próprio, apelido, idade) e a chave primária (apelido, nome próprio);
- linhas 4-10: inserção de 7 linhas na tabela [personnes];
- linha 6: esta instrução de inserção deverá falhar, pois tenta a mesma inserção que a linha 5. A restrição da chave primária deverá impedir esta inserção: não é possível ter duas pessoas com o mesmo nome e apelido;
- linha 10: esta ordem de inserção deverá falhar, pois tenta a mesma inserção que a linha 9;
O script PHP, responsável por executar as ordens SQL deste ficheiro de texto, é o seguinte: [mysql-03.php]:
<?php
// identificação da base de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// identificadores do utilizador
const ID = "admpersonnes";
const PWD = "nobody";
// identidade do ficheiro de texto com os comandos SQL a executar
const SQL_COMMANDS_FILENAME = "creation.txt";
// abertura da ligação à base de dados MySql
try {
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// exibição de erros
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// pretende-se que, sempre que ocorrer um erro no SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// execução do ficheiro de ordens SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// encerramento da ligação
$connexion = NULL;
// exibição do número de erros
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
print "$erreurs[$i]\n";
}
// concluído
print "Terminé\n";
exit;
// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE, bool $arrêt = TRUE): array {
// utiliza a ligação $connexion
// executa os comandos SQL contidos no ficheiro de texto SQLFileName
// este ficheiro é um ficheiro de comandos SQL a executar à razão de um por linha
// se $suivi=1, então cada execução de um comando SQL é acompanhada de uma mensagem indicando o seu sucesso ou falha
// se $arrêt=1, a função pára ao encontrar o primeiro erro; caso contrário, executa todos os comandos SQL
// a função devolve um array (número de erros, erro1, erro2…)
// verifica-se a existência do ficheiro SQLFileName
if (!file_exists($SQLFileName)) {
return ["Le fichier [$SQLFileName] n'existe pas"];
}
// execução das consultas SQL contidas em SQLFileName
// colocam-se numa tabela
$requêtes = file($SQLFileName);
// erro?
if ($requêtes === FALSE) {
return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
}
// executam-se as consultas uma a uma — inicialmente, sem erros
$erreurs = [];
$i = 0;
$fini = FALSE;
while ($i < count($requêtes) && !$fini) {
// recuperamos o texto da consulta
// o comando «trim» remove o caractere de fim de linha
$requête = trim($requêtes[$i]);
// consulta vazia?
if (strlen($requête) == 0) {
// ignora-se a consulta e passa-se para a consulta seguinte
$i++;
continue;
}
try {
// execução da consulta — pode ser lançada uma exceção
$connexion->exec($requête);
// acompanhamento no ecrã ou não?
if ($suivi) {
print "$requête : Exécution réussie\n";
}
} catch (PDOException $ex) {
// ocorreu um erro
addError($erreurs, $requête, $ex->getMessage(), $suivi);
// deve-se interromper?
$fini = $arrêt;
}
// próxima consulta
$i++;
}
// resultado
return $erreurs;
}
function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
// adiciona-se uma mensagem de erro
$msg = "$requête : Erreur (" . $msg . ")";
$erreurs[] = $msg;
// exibir no ecrã ou não?
if ($suivi) {
print "$msg\n";
}
}
Comentários
- A função [exécuterCommandes] (linhas 36-89) é responsável por executar os comandos SQL que encontra no ficheiro de texto [$SQLFileName] (parâmetro 2). Para as executar, utiliza a ligação aberta [$connexion] (parâmetro 1) com o servidor MySQL. O terceiro parâmetro, [$suivi], é um valor booleano que controla as mensagens apresentadas no ecrã: no TRUE, a ordem SQL executada é apresentada no ecrã com a indicação de sucesso ou falha; caso contrário, a execução da ordem SQL é silenciosa. O quarto parâmetro, [$arrêt], controla o que deve ser feito quando um comando SQL falha: no TRUE, indica que a execução dos comandos SQL deve ser interrompida; caso contrário, esta continua. A função [exécuterCommandes] devolve um tabuleiro de mensagens de erro, vazio se não tiverem ocorrido erros;
- linhas 11-18: abre-se a ligação à base de dados MySQL [dbpersonnes]. Se a abertura falhar, é exibida uma mensagem de erro e o processo é interrompido (linhas 14-18);
- linha 22: passa-se, assim, uma ligação aberta para a função [exécuterCommandes]. Esta será encerrada quando a função regressar (linha 24);
- linha 20: antes de a passar para a função [exécuterCommandes], configura-se a ligação. Em caso de erro, as operações SQL com um objeto [PDO] podem ou devolver o valor booleano FALSE (valor por predefinição), ou lançar uma exceção. A linha 20 opta por este segundo caso. Com efeito, é fácil «esquecer-se» de verificar o resultado booleano da execução de uma ordem SQL. Isso irá provocar um erro posteriormente, mas noutro local do código, tornando assim mais difícil identificar a sua origem. No caso de uma exceção não tratada (ausência de catch), a exceção irá subir no código até encontrar um catch ou até chegar ao interpretador PHP, que irá interceptar a exceção. Neste caso, a natureza da exceção e o seu local de origem no código são apresentados;
- linha 22: a função [exécuterCommandes] é chamada para executar o ficheiro de ordens SQL [$SQLFileName];
- linhas 45-47: verifica-se se o ficheiro de ordens SQL existe efetivamente. Se não for o caso, regista-se o erro e devolve-se esse resultado;
- linha 51: colocam-se as ordens SQL numa tabela [$requêtes]. Linhas 53-55: se a operação falhar, devolve-se uma tabela de erros com uma única mensagem;
- linha 57: acumulamos os erros na matriz [$erreurs];
- linha 58: número da consulta;
- linha 59: o valor booleano [$fini] controla a execução das ordens SQL da tabela [$requêtes]. Quando passa para TRUE, a execução é interrompida;
- linha 60: é executado um ciclo sobre todas as consultas;
- linha 63: extrai-se o texto da ordem SQL n.º i. A função [trim] irá remover os espaços que precedem e seguem o texto da ordem SQL. Por «espaços», entenda-se o espaço em branco \b, o retorno de carro \r, o caractere de fim de linha \n, o salto de página \f, a tabulação \t… O que nos interessa aqui é que o caractere de fim de linha do texto SQL irá desaparecer;
- linhas 65-69: se o texto SQL estiver vazio, então a solicitação é ignorada e passa-se para a seguinte;
- linha 72: enviamos o comando SQL para o servidor MySQL. O método [PDO::exec] irá lançar uma exceção se a execução falhar. Recorde-se que este comportamento se deve à configuração efetuada na linha 20;
- linha 79: a mensagem de erro é adicionada à tabela de erros;
- linha 81: define-se a variável booleana [$fini] que controla o ciclo. Se o parâmetro [$arrêt] (linha 36) tiver o valor TRUE, o ciclo deve ser interrompido;
- linhas 74-76: se a execução do comando SQL for bem-sucedida, este é apresentado no ecrã se o parâmetro [$suivi] (linha 36) tiver o valor TRUE;
- linha 87: assim que todas as ordens SQL forem executadas, devolve-se a tabela de erros [$erreurs];
A função [adError] das linhas 90-97 permite adicionar um erro à tabela de erros [$erreurs]:
- linha 90: a função recebe 4 parâmetros:
- o parâmetro [$erreurs] é passado por referência. Com efeito, pretende-se atuar sobre a tabela que é passada como parâmetro e não sobre uma cópia da mesma;
- o parâmetro [$requête] é o texto SQL da ordem que falhou;
- o parâmetro [$msg] corresponde à mensagem de erro associada à ordem que falhou;
- o valor booleano [$suivi] indica se a mensagem de erro deve ser apresentada ($suivi=TRUE) ou não ($suivi=FALSE) na consola;
A função [exécuterCommandes] é chamada pelo script das linhas 3 a 33:
- linhas 11-18: é estabelecida uma ligação à base de dados MySQL [dbpersonnes];
- linha 20: a ligação é configurada;
- linha 22: o ficheiro de comandos SQL é, em seguida, executado;
- linha 24: a ligação é encerrada;
- linhas 26-29: são apresentados os erros devolvidos pela função [exécuterCommandes];
Resultados no ecrã:
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é
As inserções efetuadas podem ser visualizadas com o phpMyAdmin:

12.5. Execução de quaisquer ordens SQL
O script seguinte mostra a execução das ordens SQL do ficheiro de texto [sql.txt] a seguir:
Entre estas ordens SQL, encontra-se a ordem «select», que devolve resultados da base de dados, as ordens «insert», «update» e «delete», que alteram a base de dados sem devolver resultados, e, por fim, ordens incorretas, como a última («xselect»). O script [mysql-04.php] é o seguinte:
<?php
// identificação da base de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenciais do utilizador
const ID = "admpersonnes";
const PWD = "nobody";
// identificação do ficheiro de texto com os comandos SQL a executar
const SQL_COMMANDS_FILENAME = "sql.txt";
try {
// ligação à base de dados MySql
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// exibição de erros
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// pretende-se que, sempre que ocorrer um erro no SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// execução do ficheiro de ordens SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// encerramento da ligação
$connexion = NULL;
// exibição do número de erros
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
print "$erreurs[$i]\n";
}
// concluído
print "Terminé\n";
exit;
// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE, bool $arrêt = TRUE): array {
………………………………………………………….
// estão a ser executadas as consultas uma a uma — inicialmente sem erros
$erreurs = [];
$i = 0;
$fini = FALSE;
while ($i < count($requêtes) && !$fini) {
// recuperar o texto da consulta
// o comando «trim» irá remover o caractere de fim de linha
$requête = trim($requêtes[$i]);
// consulta vazia?
if (strlen($requête) == 0) {
// ignora-se a consulta e passa-se para a consulta seguinte
$i++;
continue;
}
// execução da consulta
// recupera-se o seu nome
$commande = "";
if (preg_match("/^\s*(\S+)/", $requête, $champs)) {
$commande = strtolower($champs[0]);
}
try {
// trata-se de uma ordem SELECT?
if ($commande === "select") {
$résultat = $connexion->query($requête);
} else {
$résultat = $connexion->exec($requête);
}
// Acompanhamento no ecrã ou não?
if ($suivi) {
print "[$requête] : Exécution réussie\n";
}
// exibe-se o resultado da execução
afficherInfos($commande, $résultat);
} catch (PDOException $ex) {
// ocorreu um erro
addError($erreurs, $requête, $ex->getMessage(), $suivi);
// devemos parar?
$fini = $arrêt;
}
// próxima consulta
$i++;
}
// resultado
return $erreurs;
}
function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
…
}
// ---------------------------------------------------------------------------------
function afficherInfos(string $commande, $résultat): void {
// exibe o resultado $résultat de uma consulta SQL
// era uma instrução SELECT?
switch ($commande) {
case "select" :
// são apresentados os nomes dos campos
$titre = "";
$nbColonnes = $résultat->columnCount();
for ($i = 0; $i < $nbColonnes; $i++) {
$infos = $résultat->getColumnMeta($i);
$titre .= $infos['name'] . ",";
}
// remove-se o último carácter,
$titre = substr($titre, 0, strlen($titre) - 1);
// exibe-se a lista de campos
print "$titre\n";
// linha separadora
$séparateurs = "";
for ($i = 0; $i < strlen($titre); $i++) {
$séparateurs .= "-";
}
print "$séparateurs\n";
// dados
foreach ($résultat as $ligne) {
$data = "";
for ($i = 0; $i < $nbColonnes; $i++) {
$data .= $ligne[$i] . ",";
}
// remove-se o último carácter,
$data = substr($data, 0, strlen($data) - 1);
// exibe-se
print "$data\n";
}
break;
case "update":
case "insert":
case "delete";
print " $résultat lignes(s) a (ont) été modifiée(s)\n";
break;
}
}
Comentários
- linhas 36-83: a função [exécuterCommandes] foi ligeiramente alterada: a ordem SQL [select] não é executada da mesma forma que as outras ordens SQL. Esta ordem é a única que devolve como resultado uma tabela, ou seja, um conjunto de linhas e colunas da base de dados;
- linhas 55-57: isola-se a primeira palavra do comando SQL utilizando uma expressão regular;
- linhas 60-64: se o comando SQL for [select], utiliza-se o método [PDO::query]; caso contrário, utiliza-se o método [PDO::exec] para executar o comando SQL. Em ambos os casos, se a execução falhar, será lançada uma exceção, que será interceptada nas linhas 71-77. Se a execução for bem-sucedida, a linha 70 apresenta o resultado;
- linhas 90-130: a função afficherInfos apresenta informações sobre o resultado da execução de uma ordem SQL;
- linha 94: trata-se do caso da ordem [select]. O seu resultado é um objeto do tipo [PDOStatement];
- linha 96: o método [PDOStatement::getColumnCount()] devolve o número de colunas da tabela de resultados do select;
- linhas 98-99: o método [PDOStatement::getMeta(i)] devolve um dicionário com informações sobre a coluna n.º i da tabela de resultados do select.. Neste dicionário, o valor associado à chave «name» é o nome da coluna;
- linhas 97-102: os nomes das colunas da tabela de resultados do select são concatenados numa cadeia de caracteres;
- linhas 105-110: constrói-se uma linha de separação com o mesmo comprimento que a cadeia de caracteres construída anteriormente;
- linhas 112-121: um objeto do tipo PDOStatement pode ser percorrido por um ciclo foreach. Em cada iteração, o elemento obtido é uma linha da tabela de resultados do select, sob a forma de um tabular de valores que representa os valores das diferentes colunas da linha. Todos estes valores são apresentados através de um ciclo for (linhas 114-116);
- linhas 123-127: o resultado da execução de uma ordem insert, update, delete é o número de linhas alteradas pela ordem;
Resultados no ecrã:
[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. Utilização de ordens SQL preparadas
12.6.1. Exemplo 1
Analisemos o seguinte script [mysql-05.php]:
<?php
// identificação da base de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// identificadores do utilizador
const ID = "admpersonnes";
const PWD = "nobody";
try {
// ligação à base de dados MySql
$connexion = new PDO(DSN, ID, PWD);
// pretende-se que, a cada erro de SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// esvazia-se a tabela de pessoas
$connexion->exec("delete from personnes");
// uma lista de pessoas
$personnes = [];
$personnes[] = ["nom" => "Langevin", "prenom" => "Paul", "age" => 47];
$personnes[] = ["nom" => "Lefur", "prenom" => "Sylvie", "age" => 28];
// vamos inserir estas pessoas na base de dados
$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) {
// exibição de erro
print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// encerramento da ligação
$connexion = NULL;
}
// Está concluído
print "Terminé\n";
exit;
Comentários
Estamos aqui interessados nas linhas 16-24, que inserem duas pessoas na tabela de pessoas da base de dados [dbpersonnes].
- linha 21: «prepara-se» uma ordem SQL com parâmetros definidos. Os parâmetros são precedidos pelos caracteres: :nom, :prenom, :age. Para «preparar» uma ordem SQL, utiliza-se o método [PDO::prepare]. O resultado é um tipo [PDOStatement]. A «preparação» não é uma execução: nada é executado;
- linha 23: execução da ordem «preparada» com o método [PDOStatement::execute]. Para tal, é necessário atribuir valores aos parâmetros :nom, :prenom e :age. Existem várias formas de o fazer. Aqui, utiliza-se um dicionário cujas chaves são os parâmetros da ordem preparada, que são passados para o método [PDOStatement::execute]. Outra forma de o fazer é atribuir um valor aos parâmetros com o método [PDOStatement::bindValue($paramètre,$valeur)]. Por exemplo, aqui:
$statement→bindValue(“nom”,”Langevin”);
$statement→bindValue(“prenom”,”Paul”);
$statement→bindValue(“age”,47);
$statement→execute();
A desvantagem é que é necessário repetir esta instrução para cada parâmetro. O método do dicionário pode, então, ser mais prático. O método [PDOStatement::execute] retorna FALSE se a execução falhar;
- o método utilizado aqui para efetuar as inserções:
- um método preparado;
- n execuções do comando preparado;
é mais eficiente em termos de tempo de execução do que executar n ordens SQL diferentes. Este método deve, portanto, ser privilegiado. Pode ser utilizado para as ordens SQL select, update, delete e insert. No caso das ordens SQL e select, após a sua execução com [PDOStatement::execute], recuperam-se as linhas do resultado com o método [PDOStatement::fetchAll];
12.6.2. Exemplo 2
O seguinte script [mysql-06.php] mostra a utilização de uma ordem preparada para a operação SQL select, bem como várias formas de recuperar as linhas devolvidas por esta operação:
<?php
// identificação da base de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// dados de identificação do utilizador
const ID = "admpersonnes";
const PWD = "nobody";
try {
// ligação à base de dados MySql
$connexion = new PDO(DSN, ID, PWD);
// pretende-se que, sempre que ocorra um erro no SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// esvaziamos a tabela de pessoas
$connexion->exec("delete from personnes");
// vamos inserir essas pessoas na base de dados
$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]);
}
// consultamos a base de dados
$statement = $connexion->prepare("select nom, prenom, age from personnes");
$statement->execute();
// 1.ª linha
$ligne = $statement->fetch();
var_dump($ligne);
// 2.ª linha
$ligne = $statement->fetch(PDO::FETCH_ASSOC);
var_dump($ligne);
// 3.ª linha
$ligne = $statement->fetch(PDO::FETCH_OBJ);
var_dump($ligne);
// 4.ª linha
$statement->setFetchMode(PDO::FETCH_CLASS, "Person");
$ligne = $statement->fetch();
var_dump($ligne);
// leitura sequencial de todas as linhas
$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) {
// exibição de erro
print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// encerramento da ligação
$connexion = NULL;
}
// terminado
print "Terminé\n";
exit;
class Person {
private $nom;
private $prenom;
private $age;
public function __toString() {
return "Personne[$this->nom,$this->prenom,$this->age]";
}
}
Comentários
- linhas 17-20: inserem-se 10 linhas na tabela [personnes] da base de dados [admpersonnes]:

- linha 22: «prepara-se» uma ordem SQL [select], que é executada na linha 23;
- linha 25: recupera-se, através do método [PDOStatement::fetch], uma linha do resultado da operação SQL [select] executada. O método [PDOStatement::fetch] pode recuperar, de várias formas, as linhas de resultado de uma operação SQL [select] preparada. O script apresenta algumas delas. O método [PDOStatement::fetch], sem parâmetros, devolve a linha atual do [select] sob a forma de um dicionário indexado tanto pelos números das colunas como pelos seus nomes;
- linha 26: apresenta o seguinte resultado:
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"
}
- linhas 28-29: o parâmetro [PDO::FETCH_ASSOC] faz com que a linha devolvida seja um dicionário indexado pelos nomes das colunas da tabela:
- linhas 31-32: o parâmetro [PDO::FETCH_OBJ] faz com que a linha devolvida seja um objeto do tipo [stdclass], cujos atributos são os nomes das colunas da tabela:
- linha 34: define-se o modo de pesquisa do método [fetch] através do método [PDOStatement::setFetchMode]. Este modo passa então a ser o modo predefinido, desde que não seja alterado, quer por outra operação [PDOStatement::setFetchMode], quer passando um modo como parâmetro ao método [PDOStatement::fetch], tal como foi feito anteriormente. A operação [setFetchMode(PDO::FETCH_CLASS, "Person")] indica que a linha lida deve ser colocada num objeto do tipo [Person]. Esta classe deve ter, entre os seus atributos, atributos com os nomes das colunas da linha lida. É o caso da classe [Person] definida nas linhas 56-63;
- a linha 36 apresenta o seguinte resultado:
- linhas 38-43: mostram como explorar sequencialmente os resultados do [select];
- linha 42: a exibição de [$personne] utilizará o método [__toString] da classe [Person];
12.7. Utilização de transações
Uma transação permite agrupar uma sequência de ordens SQL numa unidade de execução: ou todas as ordens são bem-sucedidas, ou uma delas falha e, nesse caso, todas as ordens SQL que a precederam são anuladas. Por outras palavras, quando se utiliza uma transação para executar ordens SQL, após a execução da mesma, a base de dados encontra-se num estado estável:
- ou num novo estado criado pela execução bem-sucedida de todas as ordens SQL da transação;
- ou no estado em que se encontrava antes de a transação começar a ser executada;
Vamos retomar o exemplo da execução das ordens SQL contidas num ficheiro de texto analisado no parágrafo «ligação». Vamos incluir esta execução numa transação. As ordens SQL estarão contidas no seguinte ficheiro [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'
A ordem incorreta da linha 12 fará com que toda a transação falhe. Por conseguinte, deveríamos recuperar a base de dados tal como estava antes da transação. No exemplo acima, não deveríamos, portanto, ver na tabela a linha inserida pela linha 10 acima. O script sofreu poucas alterações. No entanto, apresentamos novamente o código completo [mysql-07.php]:
<?php
// identificação da base de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenciais do utilizador
const ID = "admpersonnes";
const PWD = "nobody";
// identificação do ficheiro de texto com os comandos SQL a executar
const SQL_COMMANDS_FILENAME = "sql2.txt";
try {
// ligação à base de dados MySql
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// exibição de erros
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// pretende-se que, sempre que ocorrer um erro no SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// execução do ficheiro de ordens SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE);
// encerramento da ligação
$connexion = NULL;
// exibição do número de erros
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
print "$erreurs[$i]\n";
}
// concluído
print "Terminé\n";
exit;
// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE): array {
// utiliza a ligação $connexion
// executa os comandos SQL contidos no ficheiro de texto SQLFileName
// este ficheiro é um ficheiro de comandos SQL a executar à razão de um por linha
// os comandos SQL são executados numa transação
// se algum dos comandos falhar, a transação é cancelada e a base de dados volta ao estado em que se encontrava antes da transação
// se $suivi=1, então cada execução de uma ordem SQL é acompanhada de uma mensagem indicando o seu sucesso ou falha
// a função devolve um array (n.º de erros, erro1, erro2…)
//
// verifica-se a existência do ficheiro SQLFileName
if (!file_exists($SQLFileName)) {
return ["Le fichier [$SQLFileName] n'existe pas"];
}
// execução das consultas SQL contidas em SQLFileName
// colocam-se numa tabela
$requêtes = file($SQLFileName);
// erro?
if ($requêtes === FALSE) {
return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
}
// as consultas vão ser colocadas numa transação
$connexion->beginTransaction();
// executam-se as consultas uma a uma — inicialmente, sem erros
$erreurs = [];
$i = 0;
$fini = FALSE;
while ($i < count($requêtes) && !$fini) {
// recuperamos o texto da consulta
// o comando «trim» irá remover o caractere de fim de linha
$requête = trim($requêtes[$i]);
// consulta vazia?
if (strlen($requête) == 0) {
// ignora-se a consulta e passa-se para a consulta seguinte
$i++;
continue;
}
// execução da consulta
// recupera-se o seu nome
$commande = "";
if (preg_match("/^\s*(\S+)/", $requête, $champs)) {
$commande = strtolower($champs[0]);
}
try {
// trata-se de uma ordem SELECT?
if ($commande === "select") {
$résultat = $connexion->query($requête);
} else {
$résultat = $connexion->exec($requête);
}
// Acompanhamento no ecrã ou não?
if ($suivi) {
print "[$requête] : Exécution réussie\n";
}
// exibe-se o resultado da execução
afficherInfos($commande, $résultat);
} catch (PDOException $ex) {
// ocorreu um erro
addError($erreurs, $requête, $ex->getMessage(), $suivi);
// paramos na próxima iteração
$fini = TRUE;
}
// próxima consulta
$i++;
}
// fim da transação
if (!$fini) {
// não ocorreram erros: a transação é validada
$connexion->commit();
} else {
// ocorreram erros: a transação é anulada
$connexion->rollBack();
// adicionar erro
addError($erreurs, "", "Transaction annulée", $suivi);
}
// resultado
return $erreurs;
}
function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
…
}
// ---------------------------------------------------------------------------------
function afficherInfos(string $commande, $résultat): void {
…
}
Comentários
Destacámos as alterações ao script original [mysql-04.php].
- linhas 22, 36: a função [exécuterCommandes] perdeu o seu quarto parâmetro [$arrêt=TRUE]. Com efeito, como as ordens SQL são executadas no âmbito de uma transação, qualquer erro provocará a interrupção da transação;
- linhas 40-41: chamada da função de uma transação;
- linha 57: inicia-se uma transação. A partir de agora, qualquer ordem SQL executada no ciclo das linhas 62-99 é executada no âmbito desta transação;
- linhas 101-109: a variável booleana [$fini] assume o valor TRUE se tiver ocorrido um erro (linha 95). Quando o valor é FALSE, não ocorreram erros e a transação é então validada (linha 103). Quando o valor é TRUE, ocorreram erros e, nesse caso, a transação é cancelada (linha 106) e o erro da transação é adicionado à lista de erros (linha 108);
Resultados
Antes da execução do script, a base de dados [admpersonnes] encontra-se no seguinte estado:

Executa-se o script [mysql-07.php]. As mensagens no ecrã são então as seguintes:
[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é
- linha 53: ocorre um erro no comando [xselect];
- linha 54: a transação é, então, cancelada;
Se verificarmos o estado da base de dados, verificamos que se encontra no mesmo estado que antes da execução do script. Em particular, não se observa a linha [Josette, Bruneau, 46] da linha 52 dos resultados acima.

Resumo
- uma transação começa com o método [PDO::beginTransaction];
- é concluída com sucesso através do método [PDO::commit];
- é concluída com falha através do método [PDO::rollback];
Ao utilizar uma base de dados, é boa prática incluir todas as operações SQL numa transação, para se isolar dos outros utilizadores da base de dados (essa é também a sua função). Uma transação deve ser o mais curta possível. Por isso, não se deve esquecer de a encerrar com um [commit] ou um [rollback], conforme o caso.