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

Exercícios

  1. 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 usando exec().

    ✓ 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')");
  2. 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'];
  3. 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();
  4. 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>';
  5. 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>";
    }