Introdução: por que as injeções SQL ainda são relevantes?
\n\nImagine: seu site funciona perfeitamente, usuários se registram, deixam comentários, fazem pedidos. De repente — o banco de dados cai nas mãos de um invasor. Não porque os hackers são gênios, mas porque alguém esqueceu de colocar um ponto de interrogação em uma consulta SQL.
\n\nInjeções SQL não são uma ameaça arcaica dos anos 2000. Em 2024, a OWASP ainda as coloca entre as 3 vulnerabilidades mais perigosas em aplicações web. E a pergunta "How can I prevent SQL injection in PHP?" no Stack Overflow já ultrapassou 1,5 milhão de visualizações — e continua crescendo. Ou seja, o problema está vivo.
Neste artigo, vamos entender como fechar a porta para injeções SQL em PHP em 2025 de forma definitiva. Sem mágica, sem frameworks complexos, apenas código limpo e melhores práticas.
\n\nO que é injeção SQL em termos simples?
\n\nInjeção SQL é quando a entrada do usuário se torna parte do comando SQL, e não apenas dados. Exemplo prático:
\n\n<?php\n// CÓDIGO RUIM — nunca faça isso!\n$username = $_POST['username'];\n$query = "SELECT * FROM users WHERE username = '$username'";\n$result = mysqli_query($conn, $query);\n?>\n\nSe um invasor digitar no campo username a string ' OR '1'='1, a consulta se torna:
SELECT * FROM users WHERE username = '' OR '1'='1'\n\nIsso retorna todos os usuários. E se digitar '; DROP TABLE users; -- — você pode perder toda a tabela. Assustador? Com certeza.
Melhores práticas de proteção contra injeções SQL em PHP 2025
\n\n1. Use PDO com consultas preparadas (é o padrão)
\n\nPDO (PHP Data Objects) é uma interface universal para trabalhar com bancos de dados. Sua principal vantagem: ele separa a lógica SQL dos dados. Você escreve a consulta com placeholders, e o PDO cuida da sanitização segura.
\n\n<?php\n// CÓDIGO BOM — use PDO\n$dsn = 'mysql:host=localhost;dbname=testdb;charset=utf8mb4';\n$pdo = new PDO($dsn, $user, $pass, [\n PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n PDO::ATTR_EMULATE_PREPARES => false, // desativa emulação\n]);\n\n$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');\n$stmt->execute(['email' => $_POST['email']]);\n$user = $stmt->fetch();\n?>\n\nPor que isso funciona? O banco de dados recebe a estrutura da consulta separada dos dados. Mesmo que o usuário insira SQL malicioso, ele será interpretado como uma string comum, não como parte do comando.
\n\n2. Mysqli com consultas preparadas (alternativa)
\n\nSe você trabalha apenas com MySQL, pode usar mysqli — mas apenas com consultas preparadas, não com sanitização via mysqli_real_escape_string().
<?php\n$mysqli = new mysqli('localhost', 'user', 'pass', 'testdb');\n$stmt = $mysqli->prepare('INSERT INTO comments (user_id, text) VALUES (?, ?)');\n$stmt->bind_param('is', $userId, $commentText);\n$stmt->execute();\n?>\n\nImportante: em 2025, mysqli_real_escape_string() ainda existe, mas confiar nele para proteção é uma má ideia. Ele sanitiza apenas strings, mas não protege contra injeções em campos numéricos ou operadores LIKE.
3. Filtragem e validação — segunda linha de defesa
\n\nConsultas preparadas são a base. Mas adicione filtragem por tipo de dado. Se espera um número — verifique se é um número. Se espera um email — verifique o formato.
\n\n<?php\n$id = filter_input(INPUT_POST, 'user_id', FILTER_VALIDATE_INT);\nif ($id === false) {\n throw new InvalidArgumentException('ID deve ser um inteiro');\n}\n\n$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');\n$stmt->execute(['id' => $id]);\n?>\n\nCombinar