Consultas com PDO
Esta aula aborda a execução de consultas SQL com PDO em PHP, incluindo os métodos query e exec, prepared statements, bind de parâmetros e fetch modes. O aluno aprenderá a realizar operações seguras e eficientes no banco de dados, evitando SQL injection.
Nesta aula, vamos explorar como realizar consultas ao banco de dados utilizando a extensão PDO (PHP Data Objects). PDO fornece uma interface consistente para acesso a diferentes bancos de dados, como MySQL, PostgreSQL, SQLite, entre outros. Com PDO, você pode executar consultas simples, preparar statements com parâmetros e recuperar dados de diversas formas.
Dominar as consultas com PDO é essencial para construir aplicações web seguras e performáticas. Veremos na prática como usar os métodos query e exec, como preparar statements com placeholders, como vincular parâmetros de forma explícita e implícita, e como controlar o formato dos resultados através dos fetch modes.
query e exec
O método query() é utilizado para executar consultas SQL que não necessitam de parâmetros externos, como um SELECT simples. Ele retorna um objeto PDOStatement que pode ser usado para iterar sobre os resultados. Já o método exec() é usado para comandos que não retornam resultados, como INSERT, UPDATE ou DELETE, e retorna o número de linhas afetadas.
É importante notar que query() executa a consulta diretamente, sem qualquer preparação ou escape de parâmetros. Portanto, nunca deve ser usado com dados fornecidos pelo usuário, pois isso abre brecha para ataques de SQL injection. O uso correto é apenas para consultas fixas, como listar todos os registros de uma tabela.
Exemplo prático:
// Conexão PDO
$pdo = new PDO('mysql:host=localhost;dbname=teste', 'usuario', 'senha');
// query() para SELECT
$stmt = $pdo->query('SELECT id, nome FROM usuarios');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['nome'] . "<br>";
}
// exec() para INSERT
$affected = $pdo->exec("INSERT INTO usuarios (nome) VALUES ('João')");
echo "Linhas inseridas: $affected";
Prepared statements
Prepared statements (ou declarações preparadas) são a forma recomendada de executar consultas que envolvem dados variáveis. Eles separam a estrutura SQL dos dados, permitindo que o banco de dados compile a consulta uma vez e execute múltiplas vezes com parâmetros diferentes. Além disso, os parâmetros são tratados de forma segura, evitando SQL injection.
Para usar prepared statements, utilize o método prepare() da conexão PDO, que retorna um objeto PDOStatement. Em seguida, execute o statement com execute() passando um array de valores. Os placeholders podem ser posicionais (?) ou nomeados (:nome).
Exemplo com placeholders posicionais:
$stmt = $pdo->prepare('SELECT * FROM usuarios WHERE id = ? AND ativo = ?');
$stmt->execute([1, true]);
$usuario = $stmt->fetch(PDO::FETCH_ASSOC);
Exemplo com placeholders nomeados:
$stmt = $pdo->prepare('INSERT INTO usuarios (nome, email) VALUES (:nome, :email)');
$stmt->execute([':nome' => 'Maria', ':email' => 'maria@example.com']);
Bind de parâmetros
O bind de parâmetros permite associar variáveis PHP a placeholders de forma explícita, usando os métodos bindParam() ou bindValue(). A diferença é que bindParam() liga a variável por referência, ou seja, se a variável mudar antes de executar, o novo valor será usado; já bindValue() liga o valor no momento da chamada.
Também é possível especificar o tipo do parâmetro usando constantes como PDO::PARAM_INT, PDO::PARAM_STR, etc. Isso ajuda o banco de dados a tratar corretamente os dados.
Exemplo com bindParam:
$stmt = $pdo->prepare('SELECT * FROM usuarios WHERE id = :id');
$id = 1;
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
Exemplo com bindValue:
$stmt = $pdo->prepare('SELECT * FROM usuarios WHERE id = :id');
$stmt->bindValue(':id', 1, PDO::PARAM_INT);
$stmt->execute();
No caso de bindParam, se alterarmos a variável $id antes de executar, o novo valor será usado. Já bindValue fixa o valor 1 independentemente de alterações posteriores.
Fetch modes
Os fetch modes determinam como os dados são retornados ao recuperar linhas do resultado. O método fetch() aceita uma constante que define o formato. Os modos mais comuns são:
PDO::FETCH_ASSOC: retorna um array associativo com nomes das colunas como chaves.PDO::FETCH_NUM: retorna um array indexado numericamente.PDO::FETCH_BOTH: retorna ambos (associativo e numérico) – é o padrão.PDO::FETCH_OBJ: retorna um objeto anônimo com propriedades correspondentes às colunas.PDO::FETCH_CLASS: retorna uma instância de uma classe específica, mapeando as colunas para propriedades.
Você também pode usar fetchAll() para obter todas as linhas de uma vez, passando o fetch mode desejado.
Exemplo com vários fetch modes:
$stmt = $pdo->query('SELECT id, nome FROM usuarios');
// FETCH_ASSOC
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $row['nome'];
}
// FETCH_OBJ
$stmt->execute(); // re-executa se necessário
while ($obj = $stmt->fetch(PDO::FETCH_OBJ)) {
echo $obj->nome;
}
// FETCH_CLASS
class Usuario {
public $id;
public $nome;
}
$stmt->execute();
$usuarios = $stmt->fetchAll(PDO::FETCH_CLASS, 'Usuario');
foreach ($usuarios as $usuario) {
echo $usuario->nome;
}
Boas práticas
- Sempre utilize prepared statements para consultas que envolvam dados do usuário, mesmo que pareçam seguros.
- Escolha o fetch mode adequado para cada situação.
FETCH_ASSOCé leve e claro;FETCH_CLASSé útil para mapeamento objeto-relacional. - Configure o PDO para lançar exceções em caso de erro usando
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION). - Feche statements e conexões quando não forem mais necessários, embora o PHP faça isso automaticamente ao final da requisição.
- Use transações para garantir atomicidade em operações múltiplas.
Referências
- PHP: PDO - Manual
- PDO::query
- PDO::exec
- PDO::prepare
- PDOStatement::execute
- PDOStatement::bindParam
- PDOStatement::bindValue
- PDOStatement::fetch
- PDOStatement::fetchAll
- PDO: Constantes pré-definidas
Exercícios
-
Crie uma conexão PDO com um banco de dados SQLite em memória e use
exec()para criar uma tabela 'produtos' com colunas id (INTEGER PRIMARY KEY) e nome (TEXT). Em seguida, insira dois registros usandoexec().✓ Resposta:$pdo = new PDO('sqlite::memory:'); $pdo->exec('CREATE TABLE produtos (id INTEGER PRIMARY KEY, nome TEXT)'); $pdo->exec("INSERT INTO produtos (nome) VALUES ('Produto A')"); $pdo->exec("INSERT INTO produtos (nome) VALUES ('Produto B')"); -
Usando a tabela do exercício anterior, prepare um statement com placeholder nomeado para selecionar um produto pelo id. Execute com id=1 e exiba o nome usando
fetch(PDO::FETCH_ASSOC).✓ Resposta:$stmt = $pdo->prepare('SELECT nome FROM produtos WHERE id = :id'); $stmt->execute([':id' => 1]); $produto = $stmt->fetch(PDO::FETCH_ASSOC); echo $produto['nome']; -
Utilize
bindParam()para preparar um INSERT com placeholders nomeados (:nome) e execute duas inserções alterando a variável entre as execuções.✓ Resposta:$stmt = $pdo->prepare('INSERT INTO produtos (nome) VALUES (:nome)'); $nome = 'Produto C'; $stmt->bindParam(':nome', $nome, PDO::PARAM_STR); $stmt->execute(); $nome = 'Produto D'; $stmt->execute(); -
Após inserir alguns registros, use
fetchAll(PDO::FETCH_OBJ)para obter todos os produtos e exibir seus nomes em uma lista não ordenada.✓ Resposta:$stmt = $pdo->query('SELECT nome FROM produtos'); $produtos = $stmt->fetchAll(PDO::FETCH_OBJ); echo '<ul>'; foreach ($produtos as $produto) { echo '<li>' . $produto->nome . '</li>'; } echo '</ul>'; -
Crie uma classe 'Produto' com propriedades públicas 'id' e 'nome'. Use
fetchAll(PDO::FETCH_CLASS, 'Produto')para obter todos os produtos e exiba o id e nome de cada um.✓ Resposta:class Produto { public $id; public $nome; } $stmt = $pdo->query('SELECT id, nome FROM produtos'); $produtos = $stmt->fetchAll(PDO::FETCH_CLASS, 'Produto'); foreach ($produtos as $p) { echo "{$p->id}: {$p->nome}<br>"; }