PHP PRG (pós-redirecionamento-obtenção)

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:

PHP PRG - Problema de reenvio de formulário

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_FRACTIONsinalizador, 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 $validvariável como true. Caso contrário, adicionamos as mensagens de erro ao $errorsarray. Além disso, adicionamos o valor filtrado ao $inputsarray.

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 $_SESSIONdados:

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​​, $errorse $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.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *