Introdução
Durante a depuração de uma aplicação PHP que utiliza mysqli com prepare() e bind_param(), é comum o desenvolvedor se deparar com um comportamento aparentemente anômalo: a impossibilidade de visualizar a query final com os valores já preenchidos.
Esse post desvenda esse comportamento com precisão técnica, mostra por que isso ocorre, como simular a query completa para debug e explica por que isso não é um bug, mas sim uma decisão arquitetural deliberada.
O que acontece quando usamos prepare() com bind_param()
Quando você escreve:
$stmt = $mysqli->prepare("SELECT * FROM usuarios WHERE nome = ? AND status = ?");
$stmt->bind_param("ss", $nome, $status);
$stmt->execute();
O que o PHP faz não é:
SELECT * FROM usuarios WHERE nome = 'João' AND status = 'ativo'
Em vez disso, o PHP:
- Mantém os
?como placeholders binários - Usa
bind_param()para empacotar os valores nos tipos corretos - Envia o statement e os parâmetros de forma separada via protocolo binário ao servidor MySQL
- O MySQL interpreta e executa o comando diretamente no servidor
Ou seja: a query montada com valores substituídos nunca existe no PHP.
Isso é um bug?
Não. É o comportamento projetado do protocolo MySQL Native Driver (mysqlnd) utilizado pelo PHP. Ele foi feito assim por motivos de:
Segurança
- Evita SQL Injection sem depender de validação manual
- Protege mesmo se o desenvolvedor esquecer de escapar entradas
Performance
- Permite reuso do statement pré-compilado no servidor
- Envia apenas os parâmetros novos a cada execução, reduzindo overhead
Mas eu quero ver a query final com os dados
Como o PHP não a monta, você precisa simular a query para fins de debug. Aqui está uma função simples e eficaz:
function simulaQuery($sql, $params) {
foreach ($params as $p) {
$p = is_string($p) ? "'" . addslashes($p) . "'" : $p;
$sql = preg_replace('/\?/', $p, $sql, 1);
}
return $sql;
}
Exemplo de uso:
$query = "SELECT * FROM usuarios WHERE nome = ? AND status = ?";
$params = ["João", "ativo"];
echo simulaQuery($query, $params);
Saída:
SELECT * FROM usuarios WHERE nome = 'João' AND status = 'ativo'
Essa função é útil em logs, debug, auditoria e ferramentas próprias de inspeção.
Por que isso confundiu até devs experientes?
Porque o comportamento padrão com interpolação ($sql = "SELECT ... WHERE campo = '$valor'") sempre gerava a string completa antes de enviar para o MySQL. Com prepare(), a string com os dados nunca é gerada, o que faz parecer que a função está ignorando ou corrompendo os valores, quando na verdade está apenas enviando-os separadamente.
Conclusão
O uso de prepare() com bind_param() é a forma correta, segura e eficiente de trabalhar com MySQL em PHP. No entanto, essa abordagem exige uma mudança de mentalidade na hora de debugar: não espere conseguir echoar a query com os dados substituídos. Ela nunca existiu como string no processo.
Para simular a query real, use funções auxiliares como simulaQuery() ou crie wrappers específicos para seu projeto.
