12. Utilização do SGBD MySQL

Agora, vamos escrever scripts PHP utilizando um banco de dados MySQL:

Na arquitetura acima, o script PHP (1) não se comunica diretamente com o SGBD (Sistema de Gerenciamento de Bancos de Dados) (3). Ele se comunica com um intermediário chamado driver SGBD ou driver SGBD. O PHP fornece uma interface padrão para esses drivers, a interface PDO (PHP Data Objects). Essa interface é implementada por diferentes classes adaptadas a cada SGBD: uma classe para o SGBD MySQL, outra para o SGBD PostgreSQL… Para mudar de SGBD, troca-se de driver:

O driver PDO isola o script PHP (1) do SGBD (3, 6). Como esses drivers implementam uma interface padrão, é de se esperar que o script PHP (1) não sofra alterações ao passar do SGBD para o MySQL (3) para o SGBD PostgreSQL (6). Na prática, esse ideal não existe. De fato, para se 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 a ela comandos proprietários. Essa é uma das principais causas de incompatibilidade entre os SGBD. Além disso, os tipos de dados utilizáveis nos bancos de dados podem variar de um SGBD para outro. Assim, o PostgreSQL aceita um número de tipos de dados bem maior do que o SGBD e o MySQL. Essa é uma segunda causa de incompatibilidade. Outra causa é o gerenciamento das chaves primárias automáticas (geradas pelo SGBD): praticamente cada SGBD tem sua própria política. E assim por diante… As causas de incompatibilidade são numerosas.
Se quisermos evitar reescrever o script PHP (1) ao passar do MySQL (3) para o PostgreSQL (6), geralmente somos levados a inserir uma nova camada entre o script PHP (1) e o driver PDO (2, 5), cuja função será eliminar as incompatibilidades entre os dois SGBD. No entanto, nos casos simples que encontraremos, essa camada adicional não será necessária.
Agora, vamos utilizar o SGBD MySQL. Ele está incluído no pacote Laragon (ver parágrafo com o link).
Se o leitor for iniciante no conceito de banco de dados e na 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 fundamentos dos bancos de dados e da linguagem SQL. Assim como o MySQL, o Firebird possui uma versão de uso livre e com baixo consumo de memória.
12.1. Criação de um banco de dados
Mostraremos agora como criar um banco de dados e um usuário MySQL com a ferramenta Laragon.

- uma vez iniciado, o Laragon [1] pode ser administrado 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;
- no [7], o servidor Apache é iniciado;
- em [8], o SBD e o MySQL são iniciados;

- em [8-10], cria-se um banco de dados denominado [dbpersonnes] [11]. Vamos construir um banco de dados de pessoas;

- em [11], vamos gerenciar o banco de dados que acabamos de criar;

- a operação [Bases de données] envia uma solicitação da 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 instalamos anteriormente, o [5]. Este utilitário permite gerenciar os bancos de dados MySQL;
- por padrão, as credenciais de conexão do administrador do banco de dados são: root [13] sem senha [14];

- no [16], o banco de dados que criamos anteriormente;

- no momento, temos um banco de dados [dbpersonnes], [17], que está vazio, e [18];
Criamos um usuário [admpersonnes] com a senha [nobody], que terá todos os direitos sobre o banco de dados [dbpersonnes]:

- em [19], estamos posicionados no banco de dados [dbpersonnes];
- em [20], selecionamos a aba [Privileges];
- em [21-22], vemos que o usuário [root] possui todos os direitos sobre o banco de dados [dbpersonnes];
- em [23], cria-se um novo usuário;

- em [25-26], o usuário terá o identificador [admdbpersonnes];
- em [27-29], sua senha será [nobody];
- em [30], phpMyAdmin indica que a senha é muito fraca (fácil de ser quebrada). Em produção, é preferível gerar uma senha forte com [31];
- em [32], indica-se que o usuário [admdbpersonnes] deve ter todos os direitos sobre o banco de dados [dbpersonnes];
- em [33], validam-se as informações fornecidas;

- em [35], phpMyAdmin indica que o usuário foi criado;
- em [36], a ordem SQL que foi emitida no banco de dados;
- em [37], o usuário [admpersonnes] possui todos os direitos sobre o banco de dados [dbpersonnes];
Agora temos:
- um banco de dados MySQL [dbpersonnes];
- um usuário [admpersonnes/nobody] que possui todos os direitos sobre essa base de dados;
Vamos escrever scripts PHP para explorar o banco de dados. O PHP dispõe de diversas bibliotecas para gerenciar bancos de dados. Utilizaremos 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. Essa biblioteca não está disponível por padrão. É possível verificar sua disponibilidade da seguinte maneira:

- em [1-4], verifica-se quais extensões PDO estão ativas;
- no [5], verifica-se que a extensão PDO para o SGBD e o MySQL está ativa. As demais não estão. Basta clicar nelas para ativá-las;
Outra maneira de ativar uma extensão é modificar diretamente o arquivo [php.ini] (parágrafo “link”), que configura o PHP:

- em [1], a extensão PDO de MySQL está ativada;
- no [2], a extensão PDO do Firebird está desativada;
Após modificar o arquivo [php.ini], é necessário reiniciar o PHP do Laragon para que as alterações sejam aplicadas.
12.2. Conexão a um banco de dados MySQL
A conexão com um SGBD é feita por meio da criação de um objeto PDO. O construtor aceita diversos parâmetros:
O significado dos parâmetros é o seguinte:
$dsn | (Data Source Name) é uma sequência de caracteres que especifica a natureza do SGBD e sua localização na internet. A string “mysql:host=localhost” indica que se trata de um SGBD MySQL operando no servidor local. Essa sequência pode incluir outros parâmetros, como a porta de escuta do SGBD e o nome do banco de dados ao qual se deseja se conectar: “mysql:host=localhost:port=3306:dbname=dbpersonnes”; |
$user | identificador do usuário que está se conectando; |
$passwd | sua senha; |
$driver_options | um array de opções para o driver do SGBD; |
Apenas o primeiro parâmetro é obrigatório. O objeto assim criado servirá, então, de suporte para todas as operações realizadas no banco de dados ao qual se conectou. Se o objeto PDO não puder ser criado, será lançada uma exceção do tipo PDOException.
Segue um exemplo de conexão [mysql-01.php]:
<?php
// conexão a um banco de dados local MySql
// a identidade do usuário é (admpersonnes,nobody)
const ID = "admpersonnes";
const PWD = "nobody";
const HOTE = "localhost";
try {
// conexão
$dbh = new PDO("mysql:host=".HOTE, ID, PWD);
print "Connexion réussie\n";
// encerramento da conexão
$dbh = NULL;
} catch (PDOException $e) {
print "Erreur : " . $e->getMessage() . "\n";
exit();
}
Resultados:
Comentários
- linha 11: a conexão com um SGBD é feita por meio da criação de um objeto PDO. O construtor é utilizado aqui com os seguintes parâmetros:
- uma string que especifica a natureza do SGBD e sua localização na internet. A string “mysql:host=localhost” indica que se trata de um SGBD MySQL operando no servidor local. A porta não foi especificada. Nesse caso, a porta 3306 é usada por padrão. O nome do banco de dados também não foi indicado. Haverá, então, conexão com o SGBD MySQL, sendo que a seleção de um banco específico será feita posteriormente;
- um nome de usuário;
- sua senha;
- linha 14: o encerramento da conexão é feito mediante a exclusão do objeto PDO criado inicialmente;
- linha 15: a conexão com um SGBD pode falhar. Nesse caso, é lançada uma exceção do tipo PDOException. Essa exceção deriva da exceção PHP [RuntimeException];
- linha 16: exibe-se a mensagem de erro da exceção;
Vamos executar o script novamente, inserindo uma senha incorreta na linha 6. O resultado é 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 em um banco de dados:
<?php
// identidade do banco de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenciais do usuário
const ID = "admpersonnes";
const PWD = "nobody";
try {
// conexão com o banco de dados MySql
$connexion = new PDO(DSN, ID, PWD);
// exclusã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 {
// desconexão, se necessário
$connexion = NULL;
}
// fim
print "Terminé\n";
exit;
Comentários
- linha 11: conexão com o banco de dados. Essa é sempre a primeira coisa a se fazer. O resultado da conexão é um objeto [PDO], por meio do qual serão realizadas as operações no banco de dados;
- linha 13: a ordem SQL [drop table personnes] irá excluir a tabela [personnes] do banco de dados [dbpersonnes]. Se a tabela [personnes] não existir, isso não causará nenhum erro;
- linha 14: execução do comando SQL anterior no banco de dados [dbpersonnes]. Essa execução pode acionar 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 estrutura da tabela. As linhas formam o conteúdo da tabela. Um banco de dados pode conter uma ou mais tabelas. A tabela [personnes] terá, neste caso, três colunas:
- nome: o nome de uma pessoa na forma de uma sequência de, no máximo, 30 caracteres;
- sobrenome: o sobrenome dessa mesma pessoa na forma de uma sequência de, no máximo, 30 caracteres;
- idade: a idade da pessoa na forma de um número inteiro;
- o atributo NOT NULL em uma coluna exige que a coluna tenha um valor. Não atribuir um valor a ela resulta em um [PDOException];
- [primary key(nom,prenom)] define uma chave primária para a tabela [personnes]. Uma chave primária possui um valor único para cada linha da tabela. Aqui, a chave primária será obtida pela concatenação das colunas [nom] e [prenom] da linha. Essa restrição faz com que, na tabela, não seja possível ter duas pessoas com o mesmo nome e sobrenome, ou seja, dois homônimos. Criar um homônimo de uma pessoa na tabela gera um erro [PDOException];
- linha 17: execução do comando SQL no banco de dados [dbpersonnes];
- linha 20: se ocorrer um [PDOException], exibe-se a mensagem de erro associada;
- linhas 21-24: passa-se para a cláusula [finally] em todos os casos, haja exceção ou não, para encerrar a conexão com o banco de dados (linha 23);
Resultados:
Se a execução do script ocorrer sem erros, é possível verificar a presença da tabela em phpMyAdmin:


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

- na [10], a lista de índices da tabela. Um índice permite localizar na tabela as linhas com determinado índice mais rapidamente do que se percorresse sequencialmente as linhas da tabela. A chave primária sempre faz 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, respectivamente, no nome do banco de dados, no nome do usuário e na senha dele:
Se inserirmos um nome de banco de dados inexistente:
Erreur : SQLSTATE[HY000] [1044] Access denied for user 'admpersonnes'@'%' to database 'dbpersonnes2'
Se inserirmos um nome de usuário inexistente:
Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes2'@'localhost' (using password: YES)
Se inserirmos uma senha incorreta:
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 execute as ordens SQL encontradas no seguinte arquivo de texto [creation.txt]:
Comentários
- a linguagem SQL (Structured Query Language) não diferencia maiúsculas de minúsculas nos comandos SQL;
- linha 1: a tabela [personnes] é excluída, caso exista;
- linha 2: indica-se ao servidor MySQL que serão enviados caracteres codificados em UTF-8. Essa ordem SQL, específica para MySQL, é necessária aqui, por exemplo, para que a linha 7, o “é” de Géraldine, seja incluída no banco de dados. Se a linha 2 não for incluída, o “é” será convertido em uma sequência de dois caracteres estranhos. O cliente é o script PHP escrito no NetBeans. Esse script codifica os arquivos em UTF-8 e [1-4], conforme mostrado abaixo:

- linha 3: criação da tabela [personnes] com as três colunas (nome, sobrenome, idade) e a chave primária (sobrenome, nome);
- linhas 4 a 10: inserção de 7 linhas na tabela [personnes];
- linha 6: essa instrução de inserção deve falhar, pois tenta a mesma inserção da linha 5. A restrição da chave primária deve impedir essa inserção: não é possível ter duas pessoas com o mesmo nome e sobrenome;
- linha 10: esta instrução de inserção deve falhar, pois tenta a mesma inserção da linha 9;
O script PHP, responsável por executar as ordens SQL deste arquivo de texto, é o seguinte: [mysql-03.php]:
<?php
// identificador do banco de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenciais do usuário
const ID = "admpersonnes";
const PWD = "nobody";
// identidade do arquivo de texto com os comandos SQL a serem executados
const SQL_COMMANDS_FILENAME = "creation.txt";
// abertura da conexão com o banco de dados MySql
try {
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// exibição de erros
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// deseja-se que, a cada erro do SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// execução do arquivo de ordens SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// encerramento da conexã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 {
// usa a conexão $connexion
// executa os comandos SQL contidos no arquivo de texto SQLFileName
// este arquivo é um arquivo de comandos SQL a ser executado, um por linha
// se $suivi=1, então cada execução de um comando SQL é acompanhada de uma mensagem indicando se foi bem-sucedida ou falhou
// se $arrêt=1, a função interrompe a execução ao encontrar o primeiro erro; caso contrário, ela executa todos os comandos SQL
// a função retorna um array (número de erros, erro1, erro2…)
// verifica-se a existência do arquivo SQLFileName
if (!file_exists($SQLFileName)) {
return ["Le fichier [$SQLFileName] n'existe pas"];
}
// execução das consultas SQL contidas em SQLFileName
// colocam-se em uma tabela
$requêtes = file($SQLFileName);
// erro?
if ($requêtes === FALSE) {
return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
}
// executamos 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` removerá o caractere de fim de linha
$requête = trim($requêtes[$i]);
// consulta vazia?
if (strlen($requête) == 0) {
// a consulta é ignorada e passa-se para a próxima consulta
$i++;
continue;
}
try {
// execução da consulta — pode ocorrer uma exceção
$connexion->exec($requête);
// exibição na tela 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);
// devemos interromper?
$fini = $arrêt;
}
// próxima consulta
$i++;
}
// resultado
return $erreurs;
}
function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
// adicionamos uma mensagem de erro
$msg = "$requête : Erreur (" . $msg . ")";
$erreurs[] = $msg;
// exibir na tela 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 ela encontra no arquivo de texto [$SQLFileName] (parâmetro 2). Para executá-los, ela utiliza a conexão aberta [$connexion] (parâmetro 1) com o servidor MySQL. O terceiro parâmetro, [$suivi], é um valor booleano que controla as exibições na tela: no TRUE, a ordem SQL executada é exibida na tela com sua aprovação ou reprovação; caso contrário, a execução da ordem SQL ocorre silenciosamente. O quarto parâmetro, [$arrêt], controla o que deve ser feito quando um comando SQL falha: em TRUE, ele indica que a execução dos comandos SQL deve ser interrompida; caso contrário, ela continua. A função [exécuterCommandes] retorna uma matriz de mensagens de erro, vazia se não houver erros;
- linhas 11-18: abre-se a conexão com o banco de dados MySQL [dbpersonnes]. Se a abertura falhar, uma mensagem de erro é exibida e o processo é interrompido (linhas 14-18);
- linha 22: passa-se, portanto, uma conexão aberta para a função [exécuterCommandes]. Ela será fechada ao retorno da função (linha 24);
- linha 20: antes de passá-la para a função [exécuterCommandes], configura-se a conexão. Em caso de erro, as operações SQL com um objeto [PDO] podem retornar o valor booleano FALSE (valor padrão) ou lançar uma exceção. A linha 20 opta por esse segundo caso. De fato, é fácil “esquecer” de verificar o resultado booleano da execução de um comando SQL. Isso gerará um erro posteriormente, mas em outro ponto do código, tornando mais difícil identificar a origem do mesmo. No caso de uma exceção não tratada (ausência de catch), a exceção subirá pelo código até encontrar um catch ou até chegar ao interpretador PHP, que a interceptará. Nesse caso, a natureza da exceção e seu local de origem no código são exibidos;
- linha 22: a função [exécuterCommandes] é chamada para executar o arquivo de ordens SQL [$SQLFileName];
- linhas 45-47: verifica-se se o arquivo de ordens SQL realmente existe. Caso contrário, registra-se o erro e retorna-se esse resultado;
- linha 51: insere-se as ordens SQL em uma tabela [$requêtes]. Linhas 53-55: se a operação falhar, retorna-se uma tabela de erros com uma única mensagem;
- linha 57: vamos acumular os erros na matriz [$erreurs];
- linha 58: número da consulta;
- linha 59: o booleano [$fini] controla a execução das ordens SQL da tabela [$requêtes]. Quando chega a TRUE, a execução é interrompida;
- linha 60: é feito um ciclo por todas as consultas;
- linha 63: extrai-se o texto da ordem SQL n.º i. A função [trim] 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 desaparecerá;
- linhas 65-69: se o texto SQL estiver vazio, a solicitação é ignorada e passamos para a seguinte;
- linha 72: enviamos o comando SQL ao servidor MySQL. O método [PDO::exec] lançará uma exceção se a execução falhar. Vale lembrar que esse comportamento se deve à configuração feita na linha 20;
- linha 79: a mensagem de erro é adicionada à matriz de erros;
- linha 81: define-se a variável booleana [$fini], que controla o loop. Se o parâmetro [$arrêt] (linha 36) for igual a TRUE, o loop deve ser interrompido;
- linhas 74-76: se a execução do comando SQL for bem-sucedida, ele é exibido na tela caso o parâmetro [$suivi] (linha 36) tenha o valor TRUE;
- linha 87: uma vez executadas todas as ordens SQL, retorna-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. De fato, queremos atuar sobre a matriz que é passada como parâmetro e não sobre uma cópia dela;
- o parâmetro [$requête] é o texto SQL do pedido que falhou;
- o parâmetro [$msg] é a mensagem de erro relacionada à ordem que falhou;
- o valor booleano [$suivi] indica se a mensagem de erro deve ser exibida ($suivi=TRUE) ou não ($suivi=FALSE) no console;
A função [exécuterCommandes] é chamada pelo script das linhas 3 a 33:
- linhas 11 a 18: é estabelecida uma conexão com o banco de dados MySQL [dbpersonnes];
- linha 20: a conexão é configurada;
- linha 22: o arquivo de comandos SQL é então executado;
- linha 24: a conexão é encerrada;
- linhas 26-29: são exibidos os erros retornados pela função [exécuterCommandes];
Resultados na tela:
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 realizadas podem ser visualizadas com o phpMyAdmin:

12.5. Execução de quaisquer ordens SQL
O script a seguir mostra a execução das ordens SQL do arquivo de texto [sql.txt] a seguir:
Entre essas ordens SQL, há a ordem select, que retorna resultados do banco de dados; as ordens **insert**, update** e delete, que modificam o banco de dados sem retornar resultados; e, por fim, ordens com erros, como a última (xselect**). O script [mysql-04.php] é o seguinte:
<?php
// identificação do banco de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenciais do usuário
const ID = "admpersonnes";
const PWD = "nobody";
// identidade do arquivo de texto com os comandos SQL a serem executados
const SQL_COMMANDS_FILENAME = "sql.txt";
try {
// conexão com o banco de dados MySql
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// exibição de erros
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// deseja-se que, a cada erro do SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// execução do arquivo de comandos SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// encerramento da conexã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 {
………………………………………………………….
// as consultas são executadas uma a uma — inicialmente, sem erros
$erreurs = [];
$i = 0;
$fini = FALSE;
while ($i < count($requêtes) && !$fini) {
// recuperando o texto da consulta
// o comando `trim` 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 próxima
$i++;
continue;
}
// execução da consulta
// recuperando o 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 na tela 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 interromper?
$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 consulta SELECT?
switch ($commande) {
case "select" :
// exibe 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 caractere,
$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 caractere,
$data = substr($data, 0, strlen($data) - 1);
// exibe
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 modificada: a instrução SQL [select] não é executada da mesma forma que as outras instruções SQL. Essa ordem é a única que retorna como resultado uma tabela, ou seja, um conjunto de linhas e colunas do banco de dados;
- linhas 55-57: isola-se a primeira palavra do comando SQL por meio de 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, uma exceção será lançada e interceptada nas linhas 71-77. Se a execução for bem-sucedida, a linha 70 exibe o resultado;
- linhas 90 a 130: a função afficherInfos exibe informações sobre o resultado da execução de uma ordem SQL;
- linha 94: trata-se do caso da ordem [select]. Seu resultado é um objeto do tipo [PDOStatement];
- linha 96: o método [PDOStatement::getColumnCount()] retorna o número de colunas da tabela de resultados do select;
- linhas 98-99: o método [PDOStatement::getMeta(i)] retorna um dicionário com informações sobre a coluna nº i da tabela de resultados do select.. Nesse 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 em uma sequência de caracteres;
- linhas 105-110: constrói-se uma linha de separação com o mesmo comprimento que a sequência de caracteres criada anteriormente;
- linhas 112-121: um objeto do tipo PDOStatement pode ser percorrido por um loop foreach. A cada iteração, o elemento obtido é uma linha da tabela de resultados do select na forma de uma matriz de valores que representa os valores das diferentes colunas da linha. Todos esses valores são exibidos por meio de um loop for (linhas 114-116);
- linhas 123-127: o resultado da execução das ordens insert, update e delete é o número de linhas modificadas por cada ordem;
Resultados na tela:
[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
Vamos examinar o seguinte script [mysql-05.php]:
<?php
// identidade do banco de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// identificadores do usuário
const ID = "admpersonnes";
const PWD = "nobody";
try {
// conexão com o banco de dados MySql
$connexion = new PDO(DSN, ID, PWD);
// deseja-se que, a cada erro de 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");
// uma lista de pessoas
$personnes = [];
$personnes[] = ["nom" => "Langevin", "prenom" => "Paul", "age" => 47];
$personnes[] = ["nom" => "Lefur", "prenom" => "Sylvie", "age" => 28];
// vamos inserir essas pessoas no banco 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 conexão
$connexion = NULL;
}
// pronto
print "Terminé\n";
exit;
Comentários
Aqui, estamos interessados nas linhas 16 a 24, que inserem duas pessoas na tabela de pessoas do banco de dados [dbpersonnes].
- linha 21: “prepara-se” uma ordem SQL configurada. Os parâmetros são precedidos pelos caracteres: :sobrenome, :nome, :idade. 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 isso, é preciso atribuir valores aos parâmetros :nom, :prenom e :age. Há várias maneiras de fazer isso. Aqui, utilizamos um dicionário cujas chaves são os parâmetros da ordem preparada, que passamos para o método [PDOStatement::execute]. Outra maneira de fazer isso é 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 é preciso repetir essa 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 caso a execução falhe;
- o método usado aqui para fazer as inserções:
- um método preparado;
- n execuções do comando preparado;
é mais econômica em termos de tempo de execução do que executar n ordens SQL diferentes. Portanto, esse método deve ser priorizado. Ele pode ser utilizado para as ordens SQL select, update, delete e insert. No caso das ordens SQL e select, após sua execução com [PDOStatement::execute], recuperam-se as linhas do resultado com o método [PDOStatement::fetchAll];
12.6.2. Exemplo 2
O script a seguir, [mysql-06.php], mostra o uso de uma ordem preparada para a operação SQL select, bem como diversas maneiras de recuperar as linhas retornadas por essa operação:
<?php
// identificação do banco de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenciais do usuário
const ID = "admpersonnes";
const PWD = "nobody";
try {
// conexão com o banco de dados MySql
$connexion = new PDO(DSN, ID, PWD);
// queremos que, a cada erro do SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// vazamos a tabela de pessoas
$connexion->exec("delete from personnes");
// vamos inserir essas pessoas no banco 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 o banco 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 conexão
$connexion = NULL;
}
// concluído
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] do banco de dados [admpersonnes]:

- linha 22: “prepara-se” uma ordem SQL [select], que é executada na linha 23;
- linha 25: utiliza-se o método [PDOStatement::fetch] para recuperar uma linha do resultado da operação SQL [select] executada. O método [PDOStatement::fetch] pode recuperar, de diversas maneiras, as linhas de resultado de uma operação SQL [select] preparada. O script apresenta algumas delas. O método [PDOStatement::fetch], sem parâmetros, retorna a linha atual do [select] na forma de um dicionário indexado tanto pelos números das colunas quanto por seus nomes;
- linha 26: exibe 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 retornada 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 retornada 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] por meio do método [PDOStatement::setFetchMode]. Esse modo passa a ser o padrão até que seja alterado, seja por outra operação [PDOStatement::setFetchMode], seja passando um modo como parâmetro para o método [PDOStatement::fetch], conforme feito anteriormente. A operação [setFetchMode(PDO::FETCH_CLASS, "Person")] indica que a linha lida deve ser colocada em um objeto do tipo [Person]. Essa classe deve ter, entre seus atributos, alguns com os nomes das colunas da linha lida. É o caso da classe [Person] definida nas linhas 56 a 63;
- a linha 36 exibe o seguinte resultado:
- linhas 38 a 43: mostram como processar sequencialmente os resultados do [select];
- linha 42: a exibição de [$personne] utilizará o método [__toString] da classe [Person];
12.7. Uso de transações
Uma transação permite agrupar uma sequência de ordens SQL em uma 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 canceladas. Em outras palavras, ao utilizar uma transação para executar ordens SQL, após a execução da transação, o banco de dados fica em um estado estável:
- ou em um novo estado criado pela execução bem-sucedida de todas as ordens SQL da transação;
- ou no estado em que se encontrava antes do início da execução da transação;
Vamos retomar o exemplo da execução dos comandos SQL contidos em um arquivo de texto analisado no parágrafo “link”. Vamos incluir essa execução em uma transação. Os comandos SQL estarão contidos no seguinte arquivo [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. Portanto, deveríamos encontrar o banco de dados 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 sofre pouquíssimas alterações. No entanto, apresentamos novamente o código completo [mysql-07.php]:
<?php
// identificação do banco de dados
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// credenciais do usuário
const ID = "admpersonnes";
const PWD = "nobody";
// identidade do arquivo de texto com os comandos SQL a serem executados
const SQL_COMMANDS_FILENAME = "sql2.txt";
try {
// conexão com o banco de dados MySql
$connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
// exibição de erros
print "Erreur : " . $ex->getMessage() . "\n";
exit;
}
// deseja-se que, a cada erro do SGBD, seja lançada uma exceção
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// execução do arquivo de comandos SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE);
// encerramento da conexã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 {
// usa a conexão $connexion
// executa os comandos SQL contidos no arquivo de texto SQLFileName
// esse arquivo é um arquivo de comandos SQL a ser executado, um por linha
// os comandos SQL são executados em uma transação
// se algum dos comandos falhar, a transação é cancelada e o banco de dados volta ao estado em que se encontrava antes da transação
// se $suivi=1, então cada execução de um comando SQL é acompanhada de uma mensagem indicando se foi bem-sucedida ou falhou
// a função retorna um array (número de erros, erro1, erro2…)
//
// verifica-se a presença do arquivo SQLFileName
if (!file_exists($SQLFileName)) {
return ["Le fichier [$SQLFileName] n'existe pas"];
}
// execução das consultas SQL contidas em SQLFileName
// coloca-se em uma tabela
$requêtes = file($SQLFileName);
// erro?
if ($requêtes === FALSE) {
return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
}
// as consultas serão colocadas em uma transação
$connexion->beginTransaction();
// executamos 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 removerá o caractere de fim de linha
$requête = trim($requêtes[$i]);
// consulta vazia?
if (strlen($requête) == 0) {
// a consulta é ignorada e passa-se para a próxima consulta
$i++;
continue;
}
// execução da consulta
// recuperando o 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 na tela 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 foi validada
$connexion->commit();
} else {
// ocorreram erros: a transação é cancelada
$connexion->rollBack();
// adicionado 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
Destacamos as alterações em relação ao script original [mysql-04.php].
- linhas 22, 36: a função [exécuterCommandes] perdeu seu quarto parâmetro [$arrêt=TRUE]. De fato, como as ordens SQL são executadas dentro 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 comando SQL executado no loop das linhas 62-99 é executado dentro dessa transação;
- linhas 101-109: a variável booleana [$fini] assume o valor TRUE caso tenha ocorrido um erro (linha 95). Quando seu valor é FALSE, não houve erros e, portanto, a transação é validada (linha 103). Quando o valor é TRUE, ocorreram erros e, portanto, 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, o banco de dados [admpersonnes] apresenta o seguinte estado:

Executa-se o script [mysql-07.php]. As telas exibidas sã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 do banco de dados, ele se encontra no mesmo estado de antes da execução do script. Notavelmente, não se observa a linha [Josette, Bruneau, 46] da linha 52 dos resultados acima.

Resumo
- uma transação é iniciada com o método [PDO::beginTransaction];
- ela é concluída com sucesso pelo método [PDO::commit];
- ela é encerrada com falha pelo método [PDO::rollback];
Ao operar um banco de dados, é recomendável incluir todas as operações SQL em uma transação para isolar-se dos demais usuários do banco (essa também é uma de suas funções). Uma transação deve ser a mais curta possível. Portanto, não se esqueça de encerrá-la com um [commit] ou um [rollback], conforme o caso.