Resumo : neste tutorial, você aprenderá como usar a técnica PHP PRG (Post-Redirect-Get) para evitar o problema de envio duplo de formulário.
O problema do envio duplo
Para alterar dados no servidor por meio de um formulário , você costuma usar o método post. Quando um formulário é enviado, você valida os dados , atualiza o banco de dados e exibe a saída.
No entanto, se você clicar no botão Atualizar (ou Recarregar) do navegador após o envio do formulário, o navegador enviará o formulário novamente.
Como o formulário usa o método POST, o navegador solicitará uma confirmação como esta:
Se você clicar no botão Continuar, o navegador enviará o formulário uma segunda vez. É um problema notório de envio duplo que pode causar muitos problemas.
Por exemplo, você pode ter registros duplicados no banco de dados. Se o formulário processar o pagamento, ele cobrará duas vezes do cliente.
O diagrama a seguir ilustra o problema de envio duplo:
Introdução à técnica PHP PRG (post-redirect-get)
A técnica PRG (post-redirect-get) ajuda a resolver o problema de envio duplo. O post-redirect-get funciona da seguinte maneira:
- Primeiro, o formulário é enviado usando o método HTTP POST.
- Segundo, o servidor faz alguma coisa, por exemplo, atualiza o banco de dados, processa um pagamento, envia um e-mail e redireciona o navegador para a página de resultados.
- Terceiro, o navegador faz a segunda solicitação à página de resultados usando o método HTTP GET.
- Finalmente, o servidor retorna a página de resultados e o navegador a exibe.
Se o usuário atualizar o navegador da web, o navegador solicitará a página de resultados usando o método HTTP GET.
Observe que a página de resultados e a página que contém o formulário podem ser iguais.
O diagrama a seguir ilustra como o PRG funciona:
Ilustrando o problema de envio duplo
Criaremos um formulário de doação simples que ilustra o problema do envio duplo:
<?php
const MIN_DONATION = 1;
$errors = [];
$inputs = [];
$valid = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// sanitize & validate amount
$amount = filter_input(INPUT_POST, 'amount', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$inputs['amount'] = $amount;
if ($amount !== false && $amount !== null) {
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, ['options' => ['min_range' => MIN_DONATION]]);
if ($amount === false) {
$errors['amount'] = 'The minimum donation is $1';
} else {
$valid = true;
}
} else {
$errors['amount'] = 'Please enter a donation amount';
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>PHP - without PRG</title>
</head>
<body>
<main>
<form action="index.php" method="post">
<h1>Donation</h1>
<?php if ($valid) : ?>
<div class="alert alert-success">
Thank you for your donation of $<?= $inputs['amount'] ?? '' ?>
</div>
<?php endif ?>
<div>
<label for="amount">Amount:</label>
<input type="text" name="amount" value="<?= $inputs['amount'] ?? '' ?>" id="amount" placeholder="Minimum donation $<?= MIN_DONATION ?>">
<small><?= $errors['amount'] ?? '' ?></small>
</div>
<button type="submit">Donate</button>
</form>
</main>
</body>
</html>
Linguagem de código: PHP ( php )
Como funciona.
O formulário contém um campo de entrada que aceita um valor positivo com valor maior que 1.
A MIN_DONATION
constante especifica o valor mínimo do campo de valor:
const MIN_DONATION = 1;
Linguagem de código: PHP ( php )
Quando o formulário é enviado pelo método post, higienizamos e validamos o valor utilizando as funções filter_input()
e filter_var()
.
O seguinte limpa o valor da quantidade de INPUT_POST
:
$amount = filter_input(INPUT_POST, 'amount', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
Linguagem de código: PHP ( php )
Observe que se você não especificar o FILTER_FLAG_ALLOW_FRACTION
sinalizador, a função removerá o ponto decimal ( .
) do valor inserido.
Se o valor não existir no INPUT_POST
, o filter_input()
retorno é null
. Se o valor não for um float válido, a filter_input()
função retornará false
.
O seguinte verifica se o valor é um float válido e maior ou igual a 1:
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, ['options' => ['min_range' => MIN_DONATION]]);
Linguagem de código: PHP ( php )
Se o valor for válido, definimos a $valid
variável como true
. Caso contrário, adicionamos as mensagens de erro ao $errors
array. Além disso, adicionamos o valor filtrado ao $inputs
array.
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// sanitize & validate amount
$amount = filter_input(INPUT_POST, 'amount', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$inputs['amount'] = $amount;
if ($amount !== false && $amount !== null) {
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, ['options' => ['min_range' => MIN_DONATION]]);
if ($amount === false) {
$errors['amount'] = 'The minimum donation is $1';
} else {
$valid = true;
}
} else {
$errors['amount'] = 'Please enter a donation amount';
}
}
Linguagem de código: PHP ( php )
No formulário, mostramos uma mensagem de sucesso se o campo valor for válido. Caso contrário, mostramos o valor inserido com uma mensagem de erro.
<form action="index.php" method="post">
<h1>Donation</h1>
<?php if ($valid) : ?>
<div class="alert alert-success">
Thank you for your donation of $<?= $inputs['amount'] ?? '' ?>
</div>
<?php endif ?>
<div>
<label for="amount">Amount:</label>
<input type="text" name="amount" value="<?= $inputs['amount'] ?? '' ?>" id="amount" placeholder="Minimum donation $<?= MIN_DONATION ?>">
<small><?= $errors['amount'] ?? '' ?></small>
</div>
<button type="submit">Donate</button>
</form>
Linguagem de código: HTML, XML ( xml )
Para demonstrar o problema do envio duplo, você pode inserir um valor válido e clicar no botão Doar. Depois de ver a mensagem de sucesso, você pode clicar no botão Atualizar no navegador da web.
Corrija o problema de envio duplo de formulário usando a técnica PRG
O seguinte ilustra como usar a técnica PRG para corrigir o problema de envio duplo:
<?php
session_start();
const MIN_DONATION = 1;
$errors = [];
$inputs = [];
$valid = false;
$request_method = strtoupper($_SERVER['REQUEST_METHOD']);
if ($request_method === 'POST') {
// sanitize & validate amount
$amount = filter_input(INPUT_POST, 'amount', FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$inputs['amount'] = $amount;
if ($amount !== false && $amount !== null) {
$amount = filter_var($amount, FILTER_VALIDATE_FLOAT, ['options' => ['min_range' => MIN_DONATION]]);
if ($amount === false) {
$errors['amount'] = 'The minimum donation is $1';
} else {
$valid = true;
}
} else {
$errors['amount'] = 'Please enter a donation amount';
}
// process the payment
// ...
// place variables to sessions
$_SESSION['valid'] = $valid;
$_SESSION['errors'] = $errors;
$_SESSION['inputs'] = $inputs;
// redirect to the page itself
header('Location: index2.php', true, 303);
exit;
} elseif ($request_method === 'GET') {
if (isset($_SESSION['valid'])) {
// get the valid state from the session
$valid = $_SESSION['valid'];
unset($_SESSION['valid']);
}
if (isset($_SESSION['errors'])) {
$errors = $_SESSION['errors'];
unset($_SESSION['errors']);
}
if (isset($_SESSION['inputs'])) {
$errors = $_SESSION['inputs'];
unset($_SESSION['inputs']);
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>PHP PRG</title>
</head>
<body>
<main>
<?php if ($valid) : ?>
<div class="alert alert-success">
Thank you for your donation of $<?= $inputs['amount'] ?? '' ?>
</div>
<?php endif ?>
<form action="index.php" method="post">
<h1>Donation</h1>
<div>
<label for="amount">Amount:</label>
<input type="text" name="amount" value="<?= $inputs['amount'] ?? '' ?>" id="amount" placeholder="Minimum donation $<?= MIN_DONATION ?>">
<small><?= $errors['amount'] ?? '' ?></small>
</div>
<button type="submit">Donate</button>
</form>
</main>
</body>
</html>
Linguagem de código: PHP ( php )
Como funciona.
Primeiro, chame o session_start()
para acessar os $_SESSION
dados:
session_start();
Linguagem de código: PHP ( php )
Segundo, higienize e valide a quantidade como no exemplo anterior; Além disso, adicione as variáveis $valid
, $errors
e $inputs
à sessão para poder acessá-las na solicitação subsequente.
// add variables to session
$_SESSION['valid'] = $valid;
$_SESSION['errors'] = $errors;
$_SESSION['inputs'] = $inputs;
Linguagem de código: PHP ( php )
Terceiro, redirecione o navegador para a mesma página com o código de status HTTP 303 usando a header()
função:
// redirect to the page itself
header('Location: index.php', true, 303);
exit;
Linguagem de código: PHP ( php )
Quarto, obtenha as variáveis da sessão se o método de solicitação HTTP for GET e remova-as da sessão imediatamente:
if (isset($_SESSION['valid'])) {
$valid = $_SESSION['valid'];
unset($_SESSION['valid']);
}
if (isset($_SESSION['errors'])) {
$errors = $_SESSION['errors'];
unset($_SESSION['errors']);
}
if (isset($_SESSION['inputs'])) {
$errors = $_SESSION['inputs'];
unset($_SESSION['inputs']);
}
Linguagem de código: PHP ( php )
O código restante é igual ao exemplo anterior.
Se você clicar no botão Atualizar após enviar o formulário, o navegador não enviará o formulário novamente. Em vez disso, o navegador carrega o formulário usando o método GET.
Resumo
- PRG significa Pós-Redirecionamento-Get.
- PRG ajuda a evitar o problema de envio duplo de formulário.