我对PHP的Post/Redirect/Get感到困惑

7
在一个有关防止PHP表单重复提交的文章中,我读到了以下内容:
(不是直接引用)这可能是接收表单数据的页面,例如称为"form.php":
<form action="submit.php">
  <input type="text" name="user" required />
  <input type="password" name="pass" required />
  <input type="submit" value="Log in" />
</form>

处理POST数据的页面将被称为“submit.php”。如果登录成功,以下代码将运行:

header('Location: /login/form.php?success=true');

然而,用户是否可以直接导航到上述URL?此外,GET变量的目的是什么?我是否可以在form.php中编写一个脚本来检查用户是否已登录?
在submit.php中,我应该将已登录的用户名保存为$_SESSION ['username'],然后在form.php中检查isset()吗?另外,“success”作为URL并不好看,再次重定向用户是否经济实惠?我应该使用PHP header()还是Javascript window.location.href?如您所见,我有点困惑。
感谢任何帮助。

1
你说得对,没有POST数据就不会造成任何伤害。 - LonelyWebCrawler
2
在URL中使用?success并不会让它变得很丑陋。我经常使用这样的东西,因为大多数人都不会注意到它,而且非常容易。 - mellamokb
我假设查询字符串在那个例子中的目的是让 form.php 使用它来确定是否应该显示成功消息。如果您愿意,可以在会话中执行此类操作。重要的部分是有一个重定向,因此重新加载不会重新执行 POST,只会执行到成功页面的无害 GET。 - John Flatness
但为什么需要GET呢?对于成功的消息,我只需检查用户是否已登录即可! - LonelyWebCrawler
1
@user805556:这是一种适用于任何类型的表单的通用解决方案,例如添加评论表单。在这种情况下,检查消息是否已成功添加并不像if (isset($_SESSION...那么容易。 - zerkms
显示剩余2条评论
4个回答

7

然而,用户是否可以直接导航到上面的URL?

是的,他可以。这不会引起任何问题。

此外,GET变量的目的是什么?

设置一些标志,表示表单已经成功处理,并需要祝贺用户。

我能不能在form.php中编写一个脚本来检查用户是否已登录?

嗯,您可以按照您喜欢的方式保留您的代码。没有任何强制要求。

在submit.php中,我应该将已登录的用户名保存为$ _SESSION ['username'],然后在form.php中检查isset()吗?

如果您需要在当前会话中持久化它-则是的,请这样做。

另外,由于具有“success”的URL并不太好看,因此再次重定向用户是否经济实惠?

重定向到哪里?重定向是非常便宜的事情。

我应该使用PHP header()还是Javascript window.location.href?

您绝对应该在php中这样做,否则您将遇到您试图按照PRG方式避免的麻烦。


好的,这让我有点明白了。然而,由于我从不使用单独的登录页面,而是在每个页面上都有它们,所以我应该重定向到这个页面吗?“submit.php?page=whatever.php”,然后使用该变量重定向回该特定页面?另外,我如何在PHP中找出当前页面,以便我可以自动化我上面提出的过程?谢谢。 - LonelyWebCrawler
@user805556:只需将重定向URL作为任何其他参数传递,并在处理后重定向回它。当前URL(或更准确地说-当前URL的片段)可以在$ _SERVER中找到。 - zerkms

3

PRG或Post/Redirect/Get只是一种模式,您可以使用它来防止消息框。如何详细使用它(本文仅提供通用建议)取决于您的需求。

如果您想在cookie、会话或get变量中标记成功闪存消息,那完全取决于您。第二次重定向也不会有帮助,如果您尝试操作,您将了解到这一点。

唯一重要的部分是,在接收到POST请求后,您进行重定向。然后,用户仍然可以在历史记录中前后移动,而无需重新提交POST数据。

该模式运行良好,是一个很好的东西。就在两天前,我又做了一次,并且使用浏览器界面导航安装了逐步的Web应用程序安装程序。

关于您的重定向

这段代码是错误的:

header('Location:/login/form.php?success=true');

首先,冒号后面需要有一个空格:

header('Location: /login/form.php?success=true');

那么地址必须是绝对URI,必须包含完整的URL:

header('Location: http://example.com/login/form.php?success=true');

header()之后,您应该按照RFC提供一个消息体,许多所谓的“网络开发人员”甚至都不知道:

$url = 'http://example.com/login/form.php?success=true';
header(sprintf('Location: %s', $url));
printf('<a href="%s">Moved</a>.', $url);
exit;

不要忘记退出。当然,这几乎重新发明了轮子,而是安装PHP的http扩展并执行以下命令:
http_redirect('/login/form.php?success=true');

你可以在这里找到一个很棒的辅助工具:nifty helper
总结一下:重要的是你在提交后进行重定向。其他的,比如传递变量,完全取决于你想怎么做。

@user805556:请点击:sprintf - hakre
2
不需要使用绝对URI,甚至不需要使用根路径。 - ADTC

1
"

是的,您永远不应该依赖于GET变量(甚至是隐藏的POST变量)来表示,“当然,请让我进入,我是一个有效的用户!”。

个人建议从链接中剥离GET信息,仅依赖会话变量。如果使用PHP激活会话,请记得将'session_start();'放在代码的第一行。

对于submit.php:

"
<?php
session_start();
if ($_POST['user'] && $_POST['pass']) { // Make sure both variable are set
 if (your_method) {
 // Code to check if the user and pass are valid however you plan
  $_SESSION['user'] = $_POST['user'];
  $_SESSION['loggedin'] = time();
 }
}
header('Location: form.php'); // Either way, pass or fail, return to form.php
exit();
?>

然后在 form.php 中:
<?php
session_start();
$activeuser = false;
if ($_SESSION['user'] && $_SESSION['loggedin'] < (time()+600)) {
// Check if the user exists and the last access was with in 10 minutes.
 $_SESSION['loggedin'] = time(); // If so, keep them up to date!
 $activeuser = true;
}
if ($activeuser) {
 // whatever should show to someone logged in
} else {
 // Show log in form
}
?>

此外,您可能已经知道了,但默认的传输方法是GET,因此请确保在表单标记中指定method="post"。

通常最好使用header()进行重定向(如果需要),因为Javascript是客户端的,可以避免使用它,这可能会破坏您网站的功能意图。


1
POST/REDIRECT/GET 的主要思想,正如您所链接的文章所指出的那样,就是避免用户重新提交数据(大多数情况下)。一般来说,您不希望发生相同的 POST(具有完全相同的数据)两次 - 实际上,在某些情况下,它可能会执行某些操作(如收取信用卡费用)第二次,这将是不好的。
你在问题中问到的大部分都是实现细节(比如在重定向中发送 ?success 请求参数)。
实际上,通常会在成功时进行重定向。例如,如果用户的输入未通过验证,则不重定向,而应该重新显示表单,以及相关的错误消息。
以下是一个基本示例,全部都在一个脚本中。我试图只包括重要的内容,尽可能少地包含无关紧要的东西。

login.php

<?php
/**
 * ensure user supplied both username & password
 * @return mixed true or an array of error messages
 */ 
function validate_login_values($vars){
    $errors = array();
    if (empty($vars['username'])) $errors[] = 'You must supply a username, genius.';
    if (empty($vars['password'])) $errors[] = 'You must supply a password, dummy.';
    if (empty($errors)) return true;
    return $errors; // $errors must be an array.
}

if (! empty($_POST)){
    $validationResults = validate_login_values($_POST);
    if ($validationResults === true){
        // assume here that authenticate properly escapes it's arguments before sending them
        // to the database.
        if (authenticate($_POST['username'],$_POST['password'])){
            //GREAT SUCCESS!  The user is now logged in.  Redirect to home page
            header("Location: /");
            die();
        }
        $errors[] = 'Invalid username/password.  Try again, slim";

    }else{
        $errors = $validationResults; // validate_login_values created errors.
    }  
}
?>

<h1>Log In, Friend!</h1>]

<?php 
//display errors, if there were any
if (! empty($errors)): ?>
<div class="errors">Something went horribly wrong:
<ul><?php foreach($errors as $e) echo "<li>$e</li>"; ?></ul>
<div>
<?php endif; ?>

<form method="POST">
<!--   //username, password, and submit   -->
</form>

一个问题:当我使用header()重定向用户,然后使用echo输出内容时,这些内容会在哪里被输出?在顶部吗? - LonelyWebCrawler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接