Porque Você Deveria Usar a Biblioteca PDO Para Acessar Bases de Dados
() translation by (you can also view the original English article)
Muitos programadores PHP aprenderam a acessar bases de dados usando as extensões MySQL ou MySQLi. Desde o PHP 5.1, há uma maneira muito melhor. Os Objetos de Dados do PHP (PHP Data Objects - PDO) provê métodos para sentenças preparadas e para trabalhar com objetos que permitirão você ser muito mais produtivo!
Introdução ao PDO
PDO – PHP Data Objects – é uma camada de acesso a base de dados que provê uma maneira uniforme de acessar bases de dados diferentes.
Isso não leva em consideração as sintaxes específicas das bases de dados, mas permite que o processo de mudança de bases de dados e plataformas seja, praticamente, sem problemas, simplesmente mudando os dados de conexão.

Esse tutorial não é um tutorial completo sobre SQL. Ele foi escrito, primariamente, para as pessoas que, atualmente, usam as extensões MySQL e MySQLi, para ajudá-las a mudar de hábito e passar a usar a mais poderosa e portátil, PDO.
Suporte as Bases de Dados
Essa extensão dá suporte a qualquer base de dados para o qual o driver PDO tenha sido criado. Até o momento dessa tradução, os drivers de bases de dados a seguir estão disponíveis:
Nome do driver | Bases de dados suportadas |
---|---|
PDO_CUBRID | CUBRID |
PDO_DBLIB | FreeTDS / Microsoft SQL Server / Sybase |
PDO_FIREBIRD | Firebird |
PDO_IBM | IBM DB2 |
PDO_INFORMIX | IBM Informix Dynamic Server |
PDO_MYSQL | MySQL 3.x/4.x/5.x |
PDO_OCI | Oracle Call Interface |
PDO_ODBC | ODBC v3 (IBM DB2, unixODBC e win32 ODBC) |
PDO_PGSQL | PostgreSQL |
PDO_SQLITE | SQLite 3 e SQLite 2 |
PDO_SQLSRV | Microsoft SQL Server / SQL Azure |
PDO_4D | 4D |
Todos esse drivers não estão, necessariamente, disponíveis em seu sistema. Eis uma maneira rápida para descobrir quais os drivers disponíveis:
1 |
print_r(PDO::getAvailableDrivers()); |
Conexão
Bases de dados diferentes podem ter métodos de conexão ligeiramente diferentes. Abaixo, os métodos para conectar-se às bases de dados mais populares são mostrados. Você percberá que as três primeiras são idênticas, a não ser pelo tipo da base de dados – e, então, a SQLite tem sua própria sintaxe.

1 |
try { |
2 |
# Conexão com MS SQL SERVER e Sybase usando PDO_DBLIB
|
3 |
$DBH = new PDO("mssql:host=$servidor;dbname=$baseDeDados", $usuario, $senha); |
4 |
$DBH = new PDO("sybase:host=$servidor;dbname=$baseDeDados", $usuario, $senha); |
5 |
$DBH = new PDO("sqlsrv:Server=$servidor;Database=$baseDeDados", $usuario, $senha); |
6 |
|
7 |
# Conexão com MySQL via PDO_MYSQL
|
8 |
$DBH = new PDO("mysql:host=$servidor;dbname=$baseDeDados", $usuario, $senha); |
9 |
|
10 |
# Conexão com SQLite usando PDO_SQLITE
|
11 |
$DBH = new PDO("sqlite:caminho/para/minha/base/de/dados/baseDeDados.db"); |
12 |
} catch (PDOException $e) { |
13 |
echo $e->getMessage(); |
14 |
}
|
Por favor, atente ao bloco de código do try/catch
– você sempre deve envolver suas operações PDO em um bloco try/catch
e usar o mecanismo de exceção. Logo mais saberá o porque disso. Geralmente, você só fará uma única conexão – somente listamos várias para poder exemplificar a sintaxe. $DBH
, em inglês, significa database handle, que, em português seria "manipulador de base de dados". Nós usaremos essa nomenclatura pelo resto do tutorial.
Você pode encerrar a conexão de forma simples. Basta atribuir o valornull
ao manipulador:
1 |
# fecha a conexão
|
2 |
$DBH = null; |
Você pode conseguir masi informações sobre opções ou itens de conexão específicos para cada base de dados, direto do PHP.net.
Exceções e PDO
A PDO pode usar exceções para manipular erros, significando que, qualquer coisa que você fizer com PDO, deverá estar envolta em um bloco try-catch
. Você pode forçar a PDO a acessar o banco em três diferentes modos, ao usar o atributo de modo de erro em seu manipulador de base de dados recém criado. Eis a sintaxe:
1 |
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); |
2 |
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING ); |
3 |
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); |
Independente do modo de erro que você indicar, um erro de conexão sempre produzirá uma exceção. É por isso que o código de criação de uma conexão sempre deve vir dentro de um bloco try-catch
.
PDO::ERRMODE_SILET
Esse é o modo de erro padrão. Se você o deixar nesse modo, você terá de checar os erros da maneira que você, provavelmente, está acostumado a fazer com as extensões MySQL e MySQLi. Os outros dois métodos são ideiais para programação DRY (do inglês, Don't Repeat Yourself — Não se repita).
PDO::ERRMODE_WARNING
Esse modo lançará um aviso padrão do PHP e permitirá que o programa continue executando. É um método útil para depuração.
PDO::ERRMODE_EXCEPTION
Esse é o modo que você deveria usar na maioria das situações. Ele lança uma exceção, permitindo que você lide com os erros de forma graciosa e esconda os dados que possa permitir alguém a tirar proveito, explorar seu sistema. Eis um exemplo lançando mão das exceções:
1 |
# Conecta-se à base de dados
|
2 |
try { |
3 |
$DBH = new PDO("mysql:host=$servidor;dbname=$baseDeDados", $usuario, $senha); |
4 |
$DBH->setAttribute( PDO:ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); |
5 |
|
6 |
# Oh, não! Digitou DELECT ao invés de SELECT!
|
7 |
$DBH->prepare('DELECT name FROM people'); |
8 |
} catch (PDOException $e) { |
9 |
echo 'Desculpe, cara. Sinto que não posso fazer isso'; |
10 |
file_put_contents('PDOErrors.txt', $e->getMessage(), FILE_APPEND); |
11 |
}
|
Há um erro intencional na consulta select
acima. Esse erro causará uma exceção. A exceção envia os detalhes do erro para o arquivo de log, e mostrar uma mensagem amigável (talvez nem tão amigável) para o usuário.
Inserção e Atualização
Inserir novos dados ou atualiza algum dado que já exista são das operações mais comuns em bases de dados. Usando PDO, isso costuma ser um processo de duas etapas. Tudo que explicarmos nessa seção servirá, igualmente, para as operações INSERT
e UPDATE
.

Veja um exemplo do tipo mais básico de inserção:
1 |
# STH significa "Statement handle" ou "manipulador de sentença"
|
2 |
$STH->$DBH->prepare("INSERT INTO folks ( first_name ) values ( 'Cathy' )"); |
3 |
$STH->execute(); |
Você também poderia realizar essa operação através do métodoexec()
, usando uma chamada de método a menos. Na maioria das situações, você usará o método mais long para que possa aproveitar das sentenças preparadas. Mesmo se você usa-la uma única vez, usar sentenças preparadas ajudará você a se proteger de ataques de injeção de SQL.
Sentenças Preparadas
Usar sentenças preparadas ajudará você a se proteger de injeções de SQL
Uma sentença preparada é uma sentença SQL precompilada que pode ser usada inúmeras vezes, enviando somente os dados para o servidor. Elas tem a vantagem de, automaticamente, tornar seguros, em relação a ataques de injeção SQL, os dados usados nos espaços reservados.
Você faz uso de uma sentença preparada ao incluir espaços reservados em suas SQL. Logo abaixo, temos três exemplos: uma sem espaços reservados, uma com espaços sem nomes e a última com espaços nomeados.
1 |
# sem espaços reservados - pronto para injeção de SQL!
|
2 |
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) VALUES ($name, $addr, $city)"); |
3 |
|
4 |
# espaços reservados sem nomes
|
5 |
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) VALUES (?, ?, ?)"); |
6 |
|
7 |
# espaços reservados com nomes
|
8 |
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) VALUES (:name, :addr, :city)"); |
Você deve evitar o primeiro método, uma vez que está aqui só para comparação. A escolha por espaços reservados com ou sem nome afetará como você atribui os dados em suas sentenças.
Espaços Reservados Sem Nome
1 |
# atribui as variáveis a cada espaço reservado, usando índices de 1 a 3
|
2 |
$STH->bindParam(1, $name); |
3 |
$STH->bindParam(2, $addr); |
4 |
$STH->bindParam(3, $addr); |
5 |
|
6 |
# insere um novo registro
|
7 |
$name = "Daniel"; |
8 |
$addr = "Rua São João"; |
9 |
$city = "Teresina"; |
10 |
$STH->execute(); |
11 |
|
12 |
# insere outro registro, com outros valores
|
13 |
$name = "Jonas"; |
14 |
$addr = "Rua Albertão"; |
15 |
$city = "Rio de Janeiro"; |
16 |
$STH->execute(); |
Há dois passos aqui. Primeiro, nós indicamos os vários espaços reservados (linhas 2 a 4). Depois, atribuimos valores a esses espaços reservados e executamos as sentenças. Para enviar outro conjunto de dados, basta modificar os valores dessas variáveis e executar a sentença novamente.
Isso parece um pouco difícil de lidar quando se tem vários parâmetros? E é! Entretanto, se seus dados são guardados em um vetor, há um atalho:
1 |
# os dados que queremos inserir
|
2 |
$dados = ["Cathy", "Rua Borboleta Mecânica", "Lugar nenhum"]; |
3 |
|
4 |
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) VALUES (?, ?, ?)"); |
5 |
$STH->execute($data); |
Tão simples!
Os dados no vetor devem estar na ordem que serão aplicados aos espaços reservados. $dados[0]
irá para o primeiro espaço reservado,$data[1]
para o segundo e assim por diante. Entretanto, se os índices do seu vetor não estão em ordem, isso não funcionará apropriadamente e precisará reordenar o vetor.
Espaços Reservados Nomeados
Você, provavelmente, já deve imaginar a sintaxe, mas aqui vai um exemplo:
1 |
# O primeiro argumento é o espaço reservado nomeado
|
2 |
# Atente: espaços reservados nomeados sempre começam com o `:` (dois pontos)
|
3 |
$STH->bindParam(':name', $name); |
Você pode usar o atalho aqui, também, mas ele funciona com vetores associativos. Veja o exemplo:
1 |
# Os dados que queremos inserir
|
2 |
$dados = [ |
3 |
'name' => 'Cathy', |
4 |
'addr' => 'Rua Borboleta Mecânica', |
5 |
'city' => 'Lugar Nenhum' |
6 |
];
|
7 |
|
8 |
# O atalho!
|
9 |
$STH = $DBH->prapre("INSERT INTO folks (name, addr, city) VALUES (:name, :addr, :city)"); |
10 |
$STH->execute($data); |
As chaves do seu vetor não precisam começar com :
, mas precisam combinar com o nome dos espaços reservados. Se você tem um vetor de vetores, pode iterá-los e, simplesmente, chamar o métodoexecute()
para cada vetor de dados.
Outra característica interessante dos espaços reservados nomeados é a habilidade de inserir objetos, diretamente, em sua base de dados, assumindo que as propriedades do objeto combinam com os campos nomeados. Eis um exemplo de um objeto e de como faria para inseri-lo na base de dados:
1 |
# Um simples objeto
|
2 |
class Pessoa { |
3 |
public $name; |
4 |
public $addr; |
5 |
public $city; |
6 |
|
7 |
public function __construct($name, $addr, $city) |
8 |
{
|
9 |
$this->name = $name; |
10 |
$this->addr = $addr; |
11 |
$this->city = $city; |
12 |
}
|
13 |
|
14 |
# etc ...
|
15 |
}
|
16 |
|
17 |
$cathy = new Pessoa('Cathy', 'Rua Borboleta Mecânica', 'Lugar Nenhum'); |
18 |
|
19 |
# Eis a parte legal
|
20 |
$STH = $DBH->prepare("INSERT INTO folks (name, addr, city) VALUES (:name, :addr, :city)"); |
21 |
$STH->execute((array)$cathy); |
Ao converter o objeto em uum vetor dentro da execução, as propriedades são tratadas como chaves de um vetor.
Selecionando Dados

Dados são retornados pelo método fetch()
. Antes de chamá=lo, é melhor apontar ao PDO qual o tipo de retorno que você prefere. Você tem as seguintes opções:
- PDO::FETCH_ASSOC: retorna um vetor com índices associados aos nomes das colunas da tabela;
- PDO::FETCH_BOTH (default): retonra um vetor com índices númericos e índeces associados aos nomes das colunas da tabela;
-
PDO::FETCH_BOUND: Associa os valores de suas colunas às variáveis atribuídas com o método
binColumnd()
; - PDO::FETCH_CLASS: Associa os valores das colunas da tabela às propriedades da classe nomeada. Proprieades serão criadas se não houver propriedades combinantes;
- PDO::FETCH_INTO: Atualiza a instância de uma classe nomeada;
- PDO::FETCH_LAZY: CombinaPDO::FETCH_BOTH/PDO::FETCH_OBJ, criando os nomes das propriedades do objetos de acordo com que elas forem usadas;
- PDO::FETCH_NUM: retorna um vetor com índices numéricos;
- PDO::FETCH_OBJ: retorna um objeto anônimo com nomes de propriedades que correspondem aos nomes das colunas da tabela;
Na verdde, há três opções que cobrem a maioria das situações: FETCH_ASSOC, FETCH_CLASS e FETCH_OBJ. Para indicar o método de retorno, a sintaxe abaixo é usada:
1 |
$STH->setFetchMode(PDO::FETCH_ASSOC); |
FETCH_ASSOC
Esse tipo de busca cria um vetor associativo, indexado por nome de coluna. Ele deve ser bem familiar para qualquer pessoa que já usou as extensões MySQL/MySQLi. Eis um exemplo de como selecionar dados com esse método:
1 |
# usando o método atalho `query()`, uma vez que não há valores
|
2 |
# para preparar nessa seleção
|
3 |
$STH = $DBH->query("SELECT name, addr, city FROM folks"); |
4 |
|
5 |
# Definindo o tipo de busca/retorno
|
6 |
$STH->setFetchMode(PDO::FETCH_ASSOC); |
7 |
|
8 |
while($row = $STH->fetch()) { |
9 |
echo $row['name'] . '\n'; |
10 |
echo $row['addr'] . '\n'; |
11 |
echo $row['city'] . '\n'; |
12 |
}
|
Essa repetição passará por todo os resultados, um de cada vez.
FETCH_OBJ
Esse tipo de busca cria um objeto do tipo STDCLASS
para cada registro retornado. Eis um exemplo:
1 |
# criando a sentença
|
2 |
$STH = $DBH->query("SELECT name, addr, city FROM folks"); |
3 |
|
4 |
# Definindo o tipo de busca/retorno
|
5 |
$STH->setFetchMode(PDO::FETCH_OBJ); |
6 |
|
7 |
# Mostrando os resultados
|
8 |
while ($row = $STH->fetch()) { |
9 |
echo $row->name . "\n"; |
10 |
echo $row->addr . "\n"; |
11 |
echo $row->city . "\n"; |
12 |
}
|
FETCH_CLASS
As propriedades do seu objeto são atribuidas ANTES do construtor ser chamado. Isso é importante.
Esse modo permite você buscar dados diretamente em uma classe de sua escolha. Quando você usa o FETCH_CLASS
, as propriedades do seu objeto são atribuidas *ANTES do construtor ser chamado. Leia novamente, isso é importante. Se qualquer campo da tabela não tiver uma propriedade equivalente, será criado uma propriedade pública na classe para você.
Isso significa que se seus dados precisam de qualquer transformação depois que eles vem da base de dados, essa transformação pode ser feita automaticamente pelo seu objeto de acordo assim que cada um deles é criado.
Como um exemplo, imagina uma situação onde o endereço precisa ser modificado para cada registro. Nós poderíamos fazer isso, alterando essa propriedade dentro do método construtor. Veja um exemplo:
1 |
class PessoaSecreta { |
2 |
public $name; |
3 |
public $addr; |
4 |
public $city; |
5 |
public $other_data; |
6 |
|
7 |
public function __construct($other = '') |
8 |
{
|
9 |
$this->address = preg_replace('/[a-z]/', 'x', $this->address); |
10 |
$this->other_data = $other; |
11 |
}
|
12 |
}
|
Assim que um dos registro é vinculado a essa classe (lembre, uma classe é uma espécie de módelo/forma para objetos), o endereço tem todos seus caracteres alfabéticos substituídos pela letra x. Agora, usar essa classe e fazer com que os dados sejam transformados é totalmente transparente:
1 |
$STH = $DBH->query("SELECT name, addr, city FROM folks"); |
2 |
$STH->setFetchMode(PDO::FETCH_CLASS, 'PessoaSecreta'); |
3 |
|
4 |
while ($obj = $STH->fetch()) { |
5 |
echo $obj->addr; |
6 |
}
|
Se o endereço era "Rua Borboleta 5", você verá "Rxx Bxxxxxxx 5" como resultado. Claro, há inúmeras outras situações onde você pode querer que o método construtor seja chamado antes dos dados serem vinculados. A PDO também permite isso:
1 |
$STH->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'PessoaSecreta'); |
Agora, quando você repetir o exemplo anterior, usando esse modo (PDO::FETCH_PROPS_LATE), o endereço não será obscurecido, uma vez que o construtor será chamado e as propriedades só depois serão atribuidas.
Finalmente, se você realmente precisar, você pode passar argumentos para o método construtor enquanto busca os dados e passa-os para objetos usando PDO:
1 |
$STH->setFetchMode(PDO::FETCH_CLASS, 'PessoaSecreta', ['dados']); |
Se precisar passar ddos diferentes para cada objeto, você pode atribuir o tipo de busca/retorno dentro do método fetch()
:
1 |
$i = 0; |
2 |
while ($registroObj = $STH->fetch(PDO::FETCH_CLASS, 'PessoaSecreta', [$i])) { |
3 |
// Realize as operações
|
4 |
$i ++; |
5 |
}
|
Outros Métodos Úteis
Enquanto esse tutorial não foi pensado para cobrir cada detalhe da PDO, que é uma extensão gigantesca, há alguns outros métodos que você pode querer saber para poder realizar alguns coisas básicas com ela.
1 |
$DBH->lastInsertedID(); |
O método lastInsertedId()
é sempre chamado no manipulador da base de dados, não no manipulador de sentenças, e retornará o valor autoincrementado do último id inserido no registro por aquela conexão.
1 |
$DBH->exec('DELETE FROM folks WHERE 1'); |
2 |
$DBH->exec("SET time_zone = '-3:00'"); |
O método exec()
é usado para operações que não podem retornar dados além daqueles que foram afetados pela consulta. O exemplo acima mostra dois exemplo de utilização do método.
1 |
$safe = $DBH->quote($unsafe); |
O método quote()
fixa as aspas dos elementos das suas consultas. Esse é seu método de segurança, caso não esteja usando sentenças preparadas.
1 |
$registros_afetados = $STH->rowCount(); |
O método rowCount()
retorna um número inteiro indicando o número de registros afetados por uma operação. Em pelo menos um versão do PHP, de acordo com esse reporte de erro, esse método não funciona com sentenças do tipo select
. Se você está com esse problema e não pode atualizar sua versão do PHP, você pode obter o número de registros afetados com o código a seguir:
1 |
$sql = "SELECT COUNT(*) FROM folks"; |
2 |
if ($STH = $DBH->query($sql)) { |
3 |
# verifica a quantidade de registros
|
4 |
if ($STH->fetchColumn() > 0) { |
5 |
# realize um `select` de verdade aqui, porque há registros
|
6 |
} else { |
7 |
echo 'Não há registros que satisfaçam a busca'; |
8 |
}
|
9 |
}
|
Conclusão
Espero que esse artigo tenha ajuda alguns de vocês a migrarem de vez da extensões MySQL e MySQLi. O que vocês acham? Algum de vocês fará a migração?
Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!