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
INSERTeSELECTpreparadas 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:
- 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. - 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:
- Conectar ao banco de dados.
- Preparar o SQL com placeholders.
- Fazer o binding das suas variáveis nos placeholders.
- Executar a statement.
- Buscar resultados (para consultas
SELECT). - 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 retornarfalse. - Fazer o binding com a contagem de tipos errada. A string de tipos em
bind_paramdeve 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 ?ouSELECT * 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().