PHP

Formulários HTML e PHP Já leu

9 min de leitura

Formulários HTML e PHP
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á

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

  1. 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).

  2. Implemente o padrão PRG: após cadastro com sucesso, redirecione para uma página de confirmação com mensagem flash via $_SESSION.

  3. 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.

  4. 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
Comentários

Mais em PHP

Autenticação JWT
Autenticação JWT

Autenticação é o processo de verificar quem é o cliente. Em APIs REST statele...

Tratamento de Erros e Exceções
Tratamento de Erros e Exceções

Todo programa encontra situa&ccedil;&otilde;es inesperadas: um arquivo que n&...

Variáveis, Tipos de Dados e Operadores
Variáveis, Tipos de Dados e Operadores

Todo programa de computador, em sua ess&ecirc;ncia, faz tr&ecirc;s coisas: re...