Formulários são a porta de entrada dos dados do usuário para o servidor. Tudo que um sistema web faz de significativo — login, cadastro, busca, compra, envio de mensagem — passa por um formulário. Saber processar formulários com segurança e corretamente é uma das habilidades mais fundamentais do desenvolvimento web com PHP.
Neste artigo cobrimos o ciclo completo: como o navegador envia dados, como o PHP os recebe, como validar com rigor e como dar feedback útil ao usuário — tudo sem framework, para que você entenda o que acontece por baixo.
Como os dados chegam ao PHP
Quando um usuário submete um formulário HTML, o navegador empacota os dados e os envia para o servidor via HTTP. O método do envio determina como os dados chegam:
GET — os dados vão na URL, como query string: ?nome=Alice&idade=30. Visíveis, limitados em tamanho (~2000 caracteres), não adequados para dados sensíveis. Use para buscas e filtros.
POST — os dados vão no corpo da requisição HTTP, invisíveis na URL. Sem limite prático de tamanho (configurável). Use para envio de dados, login, cadastro.
No PHP, os dados chegam em superglobais:
<?php
// Dados enviados por GET (query string)
$nome = $_GET['nome'] ?? '';
// Dados enviados por POST (corpo da requisição)
$email = $_POST['email'] ?? '';
// $_REQUEST contém GET + POST + COOKIE — evite, é ambíguo
Formulário simples — ciclo completo
<?php
declare(strict_types=1);
// O mesmo arquivo processa o formulário e o exibe
// Técnica "Post/Redirect/Get" — veremos adiante
$erros = [];
$sucesso = false;
$dados = ['nome' => '', 'email' => '', 'mensagem' => ''];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Lê os dados com trim() para remover espaços acidentais
$dados['nome'] = trim($_POST['nome'] ?? '');
$dados['email'] = trim($_POST['email'] ?? '');
$dados['mensagem'] = trim($_POST['mensagem'] ?? '');
// Validação
if ($dados['nome'] === '') {
$erros['nome'] = 'O nome é obrigatório.';
} elseif (strlen($dados['nome']) < 2) {
$erros['nome'] = 'O nome deve ter pelo menos 2 caracteres.';
} elseif (strlen($dados['nome']) > 100) {
$erros['nome'] = 'O nome deve ter no máximo 100 caracteres.';
}
if ($dados['email'] === '') {
$erros['email'] = 'O email é obrigatório.';
} elseif (!filter_var($dados['email'], FILTER_VALIDATE_EMAIL)) {
$erros['email'] = 'Informe um email válido.';
}
if ($dados['mensagem'] === '') {
$erros['mensagem'] = 'A mensagem é obrigatória.';
} elseif (strlen($dados['mensagem']) < 10) {
$erros['mensagem'] = 'A mensagem deve ter pelo menos 10 caracteres.';
}
// Se não houver erros, processa
if (empty($erros)) {
// Aqui: salvar no banco, enviar email, etc.
$sucesso = true;
// Boa prática: redirecionar após sucesso (Post/Redirect/Get)
// Evita reenvio do formulário ao pressionar F5
header('Location: contato.php?enviado=1');
exit;
}
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Formulário de Contato</title>
</head>
<body>
<?php if (isset($_GET['enviado'])): ?>
<p style="color: green">Mensagem enviada com sucesso!</p>
<?php endif; ?>
<form method="POST" action="contato.php" novalidate>
<div>
<label for="nome">Nome:</label>
<!-- htmlspecialchars evita XSS ao repopular o campo -->
<input type="text" id="nome" name="nome"
value="<?= htmlspecialchars($dados['nome']) ?>"
maxlength="100">
<?php if (isset($erros['nome'])): ?>
<span class="erro"><?= htmlspecialchars($erros['nome']) ?></span>
<?php endif; ?>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email"
value="<?= htmlspecialchars($dados['email']) ?>">
<?php if (isset($erros['email'])): ?>
<span class="erro"><?= htmlspecialchars($erros['email']) ?></span>
<?php endif; ?>
</div>
<div>
<label for="mensagem">Mensagem:</label>
<textarea id="mensagem" name="mensagem" rows="5"><?=
htmlspecialchars($dados['mensagem'])
?></textarea>
<?php if (isset($erros['mensagem'])): ?>
<span class="erro"><?= htmlspecialchars($erros['mensagem']) ?></span>
<?php endif; ?>
</div>
<button type="submit">Enviar</button>
</form>
</body>
</html>
Validação robusta com filter_var e filter_input
O PHP oferece funções nativas para validação e sanitização de entrada:
<?php
// filter_var — valida ou sanitiza um valor
$email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL);
if ($email === false) {
// Email inválido
}
// FILTER_VALIDATE_INT — valida inteiro com faixa opcional
$idade = filter_var($_POST['idade'] ?? '', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 150]
]);
// FILTER_VALIDATE_FLOAT
$preco = filter_var($_POST['preco'] ?? '', FILTER_VALIDATE_FLOAT, [
'flags' => FILTER_FLAG_ALLOW_FRACTION
]);
// FILTER_VALIDATE_URL
$site = filter_var($_POST['site'] ?? '', FILTER_VALIDATE_URL);
// FILTER_SANITIZE_NUMBER_INT — remove tudo que não é dígito ou sinal
$telefone = filter_var($_POST['telefone'] ?? '', FILTER_SANITIZE_NUMBER_INT);
// filter_input — lê e filtra diretamente de $_POST, $_GET etc.
$nome = filter_input(INPUT_POST, 'nome', FILTER_SANITIZE_SPECIAL_CHARS);
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
Classe de validação reutilizável
Em vez de repetir validações espalhadas, crie uma classe:
<?php
declare(strict_types=1);
class Validador
{
private array $erros = [];
public function obrigatorio(string $campo, string $valor, string $rotulo): static
{
if (trim($valor) === '') {
$this->erros[$campo] = "{$rotulo} é obrigatório.";
}
return $this;
}
public function tamanho(string $campo, string $valor, int $min, int $max, string $rotulo): static
{
$len = mb_strlen(trim($valor));
if ($len < $min) {
$this->erros[$campo] = "{$rotulo} deve ter pelo menos {$min} caracteres.";
} elseif ($len > $max) {
$this->erros[$campo] = "{$rotulo} deve ter no máximo {$max} caracteres.";
}
return $this;
}
public function email(string $campo, string $valor): static
{
if (!filter_var(trim($valor), FILTER_VALIDATE_EMAIL)) {
$this->erros[$campo] = 'Informe um email válido.';
}
return $this;
}
public function inteiro(string $campo, mixed $valor, string $rotulo, int $min = PHP_INT_MIN, int $max = PHP_INT_MAX): static
{
$v = filter_var($valor, FILTER_VALIDATE_INT, ['options' => ['min_range' => $min, 'max_range' => $max]]);
if ($v === false) {
$this->erros[$campo] = "{$rotulo} deve ser um número inteiro entre {$min} e {$max}.";
}
return $this;
}
public function regex(string $campo, string $valor, string $padrao, string $mensagem): static
{
if (!preg_match($padrao, $valor)) {
$this->erros[$campo] = $mensagem;
}
return $this;
}
public function valido(): bool { return empty($this->erros); }
public function erros(): array { return $this->erros; }
public function erro(string $campo): ?string { return $this->erros[$campo] ?? null; }
}
// Uso:
$v = new Validador();
$v->obrigatorio('nome', $_POST['nome'] ?? '', 'Nome')
->tamanho('nome', $_POST['nome'] ?? '', 2, 100, 'Nome')
->obrigatorio('email', $_POST['email'] ?? '', 'Email')
->email('email', $_POST['email'] ?? '')
->inteiro('idade', $_POST['idade'] ?? '', 'Idade', 1, 150);
if ($v->valido()) {
// processa
} else {
$erros = $v->erros();
}
Checkboxes, radios e selects
<?php
// Checkbox: presente no POST se marcado, ausente se desmarcado
$aceita_termos = isset($_POST['termos']) && $_POST['termos'] === '1';
// Radio: só um valor possível
$plano = $_POST['plano'] ?? '';
$planosValidos = ['basico', 'pro', 'enterprise'];
if (!in_array($plano, $planosValidos, strict: true)) {
$erros['plano'] = 'Selecione um plano válido.';
}
// Select múltiplo: array de valores
$interesses = $_POST['interesses'] ?? [];
if (!is_array($interesses)) {
$interesses = [];
}
// Sanitiza cada valor do array
$interesses = array_filter($interesses, fn($i) => in_array($i, ['php', 'js', 'python'], true));
// No HTML: name="interesses[]" para enviar como array
<input type="checkbox" name="termos" value="1"> Li e aceito os termos
<input type="radio" name="plano" value="basico"> Básico
<input type="radio" name="plano" value="pro"> Pro
<select name="interesses[]" multiple>
<option value="php">PHP</option>
<option value="js">JavaScript</option>
<option value="python">Python</option>
</select>
Padrão Post/Redirect/Get
O maior erro de iniciantes em formulários PHP é não usar o padrão PRG. Sem ele, ao pressionar F5 após enviar um formulário, o navegador pergunta "reenviar dados?" — e se o usuário confirmar, o formulário é enviado novamente, criando duplicatas.
A solução é simples: após processar com sucesso, redirecione. Nunca retorne uma resposta HTML diretamente de um POST:
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// valida e processa...
if ($sucesso) {
// Redireciona — o próximo acesso será GET
// Pode passar mensagem via session ou query string
$_SESSION['flash'] = 'Cadastro realizado com sucesso!';
header('Location: /usuarios/lista');
exit; // SEMPRE após header Location
}
}
Resumo
| Conceito | Ponto principal |
|---|---|
| $_POST / $_GET | Superglobais que recebem os dados do formulário |
| htmlspecialchars() | Obrigatório ao exibir dados do usuário no HTML |
| filter_var() | Validação e sanitização nativa do PHP |
| Post/Redirect/Get | Evita reenvio acidental do formulário |
| Validador | Classe reutilizável que centraliza regras de negócio |
Exercício da semana
-
Crie um formulário de cadastro com: nome (2–100 chars), email (válido, único no banco), senha (mínimo 8 chars, pelo menos uma letra maiúscula e um número), data de nascimento (deve ser maior de 18 anos), plano (radio: básico, pro), interesses (checkbox múltiplo).
-
Implemente o padrão PRG: após cadastro com sucesso, redirecione para uma página de confirmação com mensagem flash via
$_SESSION. -
Adicione validação do lado do cliente (HTML5
required,pattern,min/max) e garanta que o servidor revalide tudo independentemente — nunca confie apenas na validação do cliente. -
Desafio: implemente rate limiting simples: armazene no banco (ou em arquivo) o IP e timestamp dos últimos envios. Se o mesmo IP enviar mais de 3 formulários em 5 minutos, retorne erro 429.
Referências
- PHP Manual — Variáveis de formulários: https://www.php.net/manual/pt_BR/tutorial.forms.php
- PHP Manual — filter_var: https://www.php.net/manual/pt_BR/function.filter-var.php
- OWASP — Input Validation Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html