Thursday, November 18, 2010

Redirect After Post in PHP

I'll start from some simple Redirect After Post pattern (known from many JEE frameworks) implementation in PHP. This post I wrote few years ago. Currently PHP frameworks support this method as well, but for people writing in plain PHP it seems frequently to be a problem.

The problem is that after sending form data in POST method, user can generate unwanted repeated data submission (a double submit problem). It can happen, when he refreshes a website, or clicks "back" and then "forward" button after posting. From server point of view this can cause unexpected problem - for example application can add second time the same entry to the database. 

Depending on browser in such instance a modal window with alert can be displayed, but users mostly don't understand it or simply don't read "popups".

There are at least two interesting solutions. First is to use Ajax to submit all POST-s to server, because it doesn't influences to "back" and "refresh" buttons. Second one is to use Redirect After Post pattern.

In this pattern after each POST server does redirect to some location using HTTP Location header. This redirect is done by GET, so refreshing the view won't be harmful, and no second POST will be performed in such situation. Very easy example of such concept could look as below:
if (count($_POST)) {
  process($_POST);
  header("Location: ".$_SERVER['PHP_SELF']);
}
But this solution is insufficient, if POST data should affect some attributes of view, that is rendered after the submission, or we'd like to change somehow application state. Typical problem is to display the form again with errors information, when validation doesn't pass. We can solve these problems on few ways:

  1. process() function can do redirect to any other view than PHP_SELF. But it won't solve problem of passing parameters.
  2. process() function can do redirect to some view and put required parameters in GET. But these values will be available to view by the user.
  3. You can store parameters in session and then restore them after redirect.

In third solution you should use so called flash key as additional protection. This flash key is a random string, passed as the only GET parameter to target view, to identify our "post session". It prevents situation, when the same user can do more posts concurrently, and the one post can override second one data.

Seamless injection

If you designed your application to work with Redirect After Post pattern, there's no problem. But there can be a problem to add this feature to existing application, using classic mechanics. Here is my idea of injecting it seamlessly in such cases:

if (count($_POST)) {
  // REDIRECT
  $a = array(
    'post' => $_POST,
    'get' => $_GET,
  );
  // Establish flash key
  $key = 'postredirect' . rand();
  $_SESSION[$key] = $a;
  // Redirect
  header("Location: ".$_SERVER['PHP_SELF']."?flashkey=$key");
  exit();
} else if (isset($_GET['flashkey']) 
    && isset($_SESSION[$_GET['flashkey']])) {
  // Back from redirect
  $key = $_GET['flashkey'];
  // Restore parameters
  $a = $_SESSION[$key];
  $_POST = $a['post'];
  $_GET = $a['get']; 
  // A session key is unnecessary
  unset($_SESSION[$key]);
}

// Here we have our code "thinking" that we are in POST, when we are in after POST redirection.

So we have simply passed parameters through session. Using this script in classic application we can use the pattern seamlessly. Of course we need to use this either in front controller or in each script interacting with post data (in case of badly designed app).

No comments:

Post a Comment