W3docs

PHP MySQL Prepared Statements: Um Guia Completo

Prepared statements aumentam a segurança e eficiência de aplicações PHP que interagem com bancos de dados. Saiba como usá-los com MySQLi e PDO.

Prepared statements são a técnica mais importante para escrever código PHP seguro que se comunica com um banco de dados. Elas separam o comando SQL dos dados que ele opera, o que impede totalmente a injeção de SQL e também acelera consultas executadas repetidamente. Este guia explica o que são prepared statements, por que elas importam e como usá-las com as extensões MySQLi e PDO.

Esta página aborda:

  • O que é uma prepared statement e por que existe o modelo "compilar uma vez, executar muitas vezes"
  • Escrevendo consultas INSERT e SELECT preparadas com MySQLi
  • Os mesmos padrões com PDO (placeholders nomeados)
  • Erros comuns: relatório de erros, binding do tipo errado e reutilização de statements

O que são Prepared Statements?

Uma prepared statement é uma consulta SQL enviada ao banco de dados em dois estágios:

  1. Preparar — você envia o SQL com placeholders ? (ou :nome) em vez de valores reais. O banco de dados analisa, compila e otimiza esse template uma vez.
  2. Executar — você envia os valores reais separadamente. O banco de dados os insere no plano já compilado e o executa.

Como os valores trafegam por um canal diferente do texto SQL, o banco de dados nunca confunde dados com comandos. Um valor como ' OR '1'='1 é tratado como uma string literal a ser pesquisada, não como SQL a ser executado — que é exatamente por que ataques de injeção falham contra prepared statements.

Por que Usar Prepared Statements?

  • Segurança. A entrada do usuário nunca pode alterar a estrutura da sua consulta. Esta é a defesa recomendada contra injeção de SQL e a razão pela qual você nunca deve construir consultas concatenando variáveis em uma string.
  • Desempenho. A consulta é analisada e compilada uma vez. Se você a executar muitas vezes (por exemplo, inserindo 1.000 linhas em um loop), o banco de dados reutiliza o mesmo plano em vez de reanalisar a cada vez.
  • Código mais limpo. Placeholders eliminam a necessidade de escapar manualmente com mysqli_real_escape_string() e de gerenciar aspas. Você faz o binding de uma variável e pronto.

Regra de ouro: no momento em que qualquer parte de uma consulta vier de entrada do usuário — um campo de formulário, um parâmetro de URL, um cookie — use uma prepared statement.

Os Passos

Toda prepared statement segue o mesmo ciclo de vida:

  1. Conectar ao banco de dados.
  2. Preparar o SQL com placeholders.
  3. Fazer o binding das suas variáveis nos placeholders.
  4. Executar a statement.
  5. Buscar resultados (para consultas SELECT).
  6. Fechar a statement.

INSERT Preparado com MySQLi

O MySQLi usa placeholders posicionais ?. Você os vincula com mysqli_stmt_bind_param(), onde o primeiro argumento é uma string de tipos: s para string, i para inteiro, d para double/float, b para blob.

<?php

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // throw on errors

$conn = mysqli_connect("localhost", "username", "password", "database");

$stmt = mysqli_prepare($conn, "INSERT INTO users (name, email) VALUES (?, ?)");

// "ss" => both placeholders are strings, in order
mysqli_stmt_bind_param($stmt, "ss", $name, $email);

$name  = "John";
$email = "[email protected]";
mysqli_stmt_execute($stmt);   // inserts John

$name  = "Jane";
$email = "[email protected]";
mysqli_stmt_execute($stmt);   // reuses the same compiled statement, inserts Jane

mysqli_stmt_close($stmt);
mysqli_close($conn);

bind_param faz o binding por referência, então você pode alterar $name/$email e chamar execute() novamente sem precisar refazer o binding — os novos valores são capturados automaticamente. Essa é a vantagem do "compilar uma vez, executar muitas vezes" em ação.

SELECT Preparado com MySQLi

Para um SELECT, você executa a statement e então lê as linhas. A forma mais limpa é mysqli_stmt_get_result(), que fornece um conjunto de resultados normal sobre o qual você pode iterar:

<?php

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

$conn = mysqli_connect("localhost", "username", "password", "database");

$stmt = mysqli_prepare($conn, "SELECT id, name FROM users WHERE email = ?");
mysqli_stmt_bind_param($stmt, "s", $email);

$email = "[email protected]";
mysqli_stmt_execute($stmt);

$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
    echo $row["id"] . ": " . $row["name"] . "\n";
}

mysqli_stmt_close($stmt);
mysqli_close($conn);

Prepared Statements com PDO

PDO é a outra extensão de banco de dados comum e muitos desenvolvedores a preferem porque funciona em diferentes sistemas de banco de dados e suporta placeholders nomeados (:email), que são mais fáceis de ler.

<?php

$pdo = new PDO(
    "mysql:host=localhost;dbname=database;charset=utf8mb4",
    "username",
    "password",
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

// INSERT with named placeholders
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (:name, :email)");
$stmt->execute([":name" => "John", ":email" => "[email protected]"]);

// SELECT and fetch
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE email = :email");
$stmt->execute([":email" => "[email protected]"]);

foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    echo $row["id"] . ": " . $row["name"] . "\n";
}

Observe que você não precisa declarar tipos com PDO — você passa um array associativo de valores diretamente para execute(). Definir PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION faz o PDO lançar exceções em caso de falha, de modo que os problemas nunca são ignorados silenciosamente.

Erros Comuns

  • Esquecer o relatório de erros. Por padrão, o MySQLi pode falhar silenciosamente. Chame mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) (ou use exceções do PDO) para que uma consulta inválida lance uma exceção em vez de retornar false.
  • Fazer o binding com a contagem de tipos errada. A string de tipos em bind_param deve ter exatamente um caractere por ?. "ss" para dois placeholders, "si" para uma string seguida de um inteiro.
  • Colocar um placeholder onde o SQL não permite. Você pode fazer binding de valores, não de identificadores. WHERE id = ? funciona; ORDER BY ? ou SELECT * FROM ? não funciona — nomes de tabelas e colunas devem ser codificados diretamente ou estar em uma lista de permissões.
  • Concatenar "apenas este único" valor. Não existe exceção segura. Se veio de um usuário, faça o binding.

Conclusão

Prepared statements dividem uma consulta em um template SQL compilado mais os valores que o preenchem. Essa separação é o que as torna seguras (à prova de injeção) e rápidas (analisadas uma vez, executadas muitas vezes). Use os placeholders ? do MySQLi ou os placeholders nomeados :valor do PDO, mas sempre faça o binding da entrada do usuário em vez de construir SQL manualmente.

Continue com os capítulos relacionados: Conectar ao MySQL, Inserir Dados, Selecionar Dados e a referência de mysqli_prepare().

Prática

Prática
Qual é o objetivo de usar prepared statements no MySQL?
Qual é o objetivo de usar prepared statements no MySQL?
Was this page helpful?