views:

546

answers:

6

Ever stumbled on a tutorial that you feel is of great value but not quite explained properly? That's my dilemma. I know THIS TUTORIAL has some value but I just can't get it.

  1. Where do you call each function?
  2. Which function should be called first and which next, and which third?
  3. Will all functions be called in all files in an application?
  4. Does anyone know of a better way cure the "Back Button Blues"?

I'm wondering if this will stir some good conversation that includes the author of the article. The part I'm particularly interested in is controlling the back button in order to prevent form duplicate entries into a database when the back button is pressed. Basically, you want to control the back button by calling the following three functions during the execution of the scripts in your application. In what order exactly to call the functions (see questions above) is not clear from the tutorial.

All forwards movement is performed by using my scriptNext function. This is called within the current script in order to activate the new script.

function scriptNext($script_id)
// proceed forwards to a new script
{
   if (empty($script_id)) {
      trigger_error("script id is not defined", E_USER_ERROR);
   } // if

   // get list of screens used in this session
   $page_stack = $_SESSION['page_stack'];
   if (in_array($script_id, $page_stack)) {
      // remove this item and any following items from the stack array
      do {
         $last = array_pop($page_stack);
      } while ($last != $script_id);
   } // if

   // add next script to end of array and update session data
   $page_stack[] = $script_id;
   $_SESSION['page_stack'] = $page_stack;

   // now pass control to the designated script
   $location = 'http://' .$_SERVER['HTTP_HOST'] .$script_id;
   header('Location: ' .$location); 
   exit;

} // scriptNext

When any script has finished its processing it terminates by calling my scriptPrevious function. This will drop the current script from the end of the stack array and reactivate the previous script in the array.

function scriptPrevious()
// go back to the previous script (as defined in PAGE_STACK)
{
   // get id of current script
   $script_id = $_SERVER['PHP_SELF'];

   // get list of screens used in this session
   $page_stack = $_SESSION['page_stack'];
   if (in_array($script_id, $page_stack)) {
      // remove this item and any following items from the stack array
      do {
         $last = array_pop($page_stack);
      } while ($last != $script_id);
      // update session data
      $_SESSION['page_stack'] = $page_stack;
   } // if

   if (count($page_stack) > 0) {
      $previous = array_pop($page_stack);
      // reactivate previous script
      $location = 'http://' .$_SERVER['HTTP_HOST'] .$previous;
   } else {
      // no previous scripts, so terminate session
      session_unset();
      session_destroy();
      // revert to default start page
      $location = 'http://' .$_SERVER['HTTP_HOST'] .'/index.php';
   } // if

   header('Location: ' .$location); 
   exit;

} // scriptPrevious

Whenever a script is activated, which can be either through the scriptNext or scriptPrevious functions, or because of the BACK button in the browser, it will call the following function to verify that it is the current script according to the contents of the program stack and take appropriate action if it is not.

function initSession()
// initialise session data
{
   // get program stack
   if (isset($_SESSION['page_stack'])) {
      // use existing stack
      $page_stack = $_SESSION['page_stack'];
   } else {
      // create new stack which starts with current script
      $page_stack[] = $_SERVER['PHP_SELF'];
      $_SESSION['page_stack'] = $page_stack;
   } // if

   // check that this script is at the end of the current stack
   $actual = $_SERVER['PHP_SELF'];
   $expected = $page_stack[count($page_stack)-1];
   if ($expected != $actual) {
      if (in_array($actual, $page_stack)) {// script is within current stack, so remove anything which follows
      while ($page_stack[count($page_stack)-1] != $actual ) {
            $null = array_pop($page_stack);
         } // while
         $_SESSION['page_stack'] = $page_stack;
      } // if
      // set script id to last entry in program stack
      $actual = $page_stack[count($page_stack)-1];
      $location = 'http://' .$_SERVER['HTTP_HOST'] .$actual;
      header('Location: ' .$location);
      exit;
   } // if

   ... // continue processing

} // initSession

The action taken depends on whether the current script exists within the program stack or not. There are three possibilities:

  • The current script is not in the $page_stack array, in which case it is not allowed to continue. Instead it is replaced by the script which is at the end of the array.
  • The current script is in the $page_stack array, but it is not the last entry. In this case all following entries in the array are removed.
  • The current script is the last entry in the $page_stack array. This is the expected situation. Drinks all round!
+8  A: 

That is a good discussion but more to the point you should be looking into Post Redirect Get (PRG) also known as "Get after Post."

http://www.theserverside.com/patterns/thread.tss?thread_id=20936

BobbyShaftoe
A: 

The part I'm particularly interested in is controlling the back button in order to prevent form duplicate entries into a database when the back button is pressed.

Your premise is wrong. There is no such thing as "Back Button Blues", if you design your application as a web application. If you design your application without any server side state, you will never run into this problem in the first case. This minimalistic approach to web applications works remarkably well, and is usually known as REST.

troelskn
A: 

@ troelskn

If you design your application without any server side state ....

It is not possible to design an effective application which does not have state, otherwise all you have is a collection of individual pages which do not communicate with each other. As maintaining state on the client is fraught with issues there is no effective alternative but to maintain state on the server.

Hi, glad you came in. I solved my problem with post/redirect/get pattern as suggested here but could you please elaborate on the tutorial. I would very much like to understand it better.
play
I disagree with you, but based on experience, I'm fairly sure we won't be able to discuss that in a meaningful way. Suffice to say that there are other viewpoints than yours, and let people seek out the answers them selves.
troelskn
A: 

@Marston.

I solved the problem with post/redirect/get but I believe the tutorial has some merit and perhaps Tony Marston can elaborate on it. And how it could be used to solve not necessarily my particular problem but perhaps something similar. Or how is it better than post/redirect/get if the functions can in fact be used in solving my particular problem. I think this will be a good addition to the community here.

play
+2  A: 

If you do not understand my article then you should take a close look at figure 1 which depicts a typical scenario where a user passes through a series of screens – logon, menu, list, search, add and update. When I describe a movement of FORWARDS I mean that the current screen is suspended while a new screen is activated. This happens when the user presses a link in the current screen. When I describe a movement as BACKWARDS I mean that the user terminates the current screen (by pressing the QUIT or SUBMIT button) and returns to the previous screen, which resumes processing from where it left off. This may include incorporating any changes made in the screen which has just been terminated.

This is where maintaining a page stack which is independent of the browser history is crucial – the page stack is maintained by the application and is used to verify all requests. These may be valid as far as the browser is concerned, but may be identified by the application as invalid and dealt with accordingly.

The page stack is maintained by two functions:

  • scriptNext() is used to process a FORWARDS movement, which adds a new entry at the end of the stack and activates the new entry.
  • scriptPrevious() is used to process a BACKWARDS movement, which removes the last entry from the stack and re-activates the previous entry.

Now take the situation in the example where the user has navigated to page 4 of the LIST screen, gone into the ADD screen, then returned to page 5 of the LIST screen. The last action in the ADD screen was to press the SUBMIT button which used the POST method to send details to the server which were added to the database, after which it terminated automatically and returned to the LIST screen.

If you therefore press the BACK button while in page 5 of the LIST screen the browser history will generate a request for the last action on the ADD screen, which was a POST. This is a valid request as far as the browser is concerned, but is not as far as the application is concerned. How can the application decide that the request is invalid? By checking with its page stack. When the ADD screen was terminated its entry was deleted from the page stack, therefore any request for a screen which is not in the page stack can always be treated as invalid. In this case the invalid request can be redirected to the last entry in the stack.

The answers to your questions should therefore be obvious:

  • Q: Where do you call each function?
  • A: You call the scriptNext() function when the user chooses to navigate forwards to a new screen, and call the scriptPrevious() function when the user terminates the current screen.
  • Q: Which function should be called first and which next, and which third?
  • A: Each function is called in response to an action chosen by the user, so only one function is used at a time.
  • Q: Will all functions be called in all files in an application?
  • A: All functions should be available in all files in an application, but only called when chosen by the user.

It you wish to see these ideas in action then you can download my sample application.

I think I have a better understanding. Fascinating concept. Thank you for your work and response.
play
A: 
if ($_POST) {
    process_input($_POST);
    header("Location: $_SERVER[HTTP_REFERER]");
    exit;
}