Page 1 of 1

Simple multipart form

Posted: Thu Jan 04, 2007 2:42 pm
by matthijs
Below a (hopefully) simple piece of code to process a multi part form.
sessiontest.php

Code: Select all

<?php
$message  = '';
$clean    = array();
$html     = array();
$sql      = array();

session_start();

// process step 1
if( isset($_POST['posted']) && $_POST['posted'] == "1" && $_SESSION['formstep'] == 1 )
{
    // CHECK Token
    if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])
    {
        /* Valid Token */
        $message = 'Step 1 has been processed succesfully';
        // include (templates/formtemplate2.tpl.php);
        // do processing ...
    }

    // increase session step
    $_SESSION['formstep'] = 2;

}
// process step 2
elseif( isset($_POST['posted']) && $_POST['posted'] == "1" && $_SESSION['formstep'] == 2 ) 
{
    // CHECK Token
    if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])
    {
        /* Valid Token */
        $message = 'Step 2 has been processed succesfully';
        // include (templates/formtemplate3.tpl.php);
        // do processing ...
    }
    // set step to 3
    $_SESSION['formstep'] = 3;
}

// process step 3
elseif( isset($_POST['posted']) && $_POST['posted'] == "1" && $_SESSION['formstep'] == 3 ) 
{
    
    // CHECK Token
    if (isset($_SESSION['token']) && $_POST['token'] == $_SESSION['token'])
    {
        /* Valid Token */
        $message = 'Step 3 has been processed succesfully';
        // include (templates/thankyoupage.php);
        // do processing ...
    }
    // set back session step to 0
    $_SESSION['formstep'] = 0;    
}
else 
{
    $message = 'This is the first step of the form process';
    $_SESSION['formstep'] = 1;
    // include (templates/formtemplate1.tpl.php);
}

$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
$_SESSION['token_time'] = time();

?>
<html>
<body>
<p><?php echo 'This is step: ' . $_SESSION['formstep'];  // obviously this is just for demo's sake  ?></p>
<p><?php echo $message; // also ?></p>
<p><?php echo 'The secret token is: ' . $token ; // also ?>

<form action="sessiontest.php" method="post">
<fieldset>
<legend>Form</legend>

<input type="hidden" name="posted" value="1">
<input type="hidden" name="token" value="<?php echo $token ?>" />
 of course I will change the part below to include different forms 
<div>
    <label for="firstname">firstname:</label>
    <input type="text" value="<?php echo isset($html['firstname']) ? $html['firstname'] : ''; ?>" name="firstname" id="firstname" />
</div>
<div>
    <label for="lastname">lastname:</label>
    <input type="text" value="<?php echo isset($html['lastname']) ? $html['lastname'] : '' ?>" name="lastname" id="lastname" />
</div>

</fieldset>
<input type="submit" value="order">

</form>
</body>
</html>
This is something I just coded, thinking about how I could write a simple one-page multi part form. So, do you think:

a) after 14 hrs behind a screen you're wasted, go and do something else
b) load of crap, start over
c) you're too lazy ***, think for yourself,
d) well, I have a suggestion ...

My main question concerns the multi part aspect of this. I used sessions, seemed easy for me, but other methods are available.


[EDIT:] Ok, after a few hours sleep I already see that it is a load of crap with a lot of redundant code. I'll review and repost the improved code later today.

Posted: Fri Jan 05, 2007 11:09 am
by matthijs
Ok, I recoded the script a bit. I removed some redundant checks and concentrated the multi-form logic to a single loop and switch. Then for the display and processing of the seperate steps I include templates. It still feels a bit messy, but I think it is an improvement.

However, while coding this I discovered that it isn't as simple as I thought it would be, because besides the steps shown below, there are more steps to consider in between when some posted values of a form are not valid. So then you have to
1) show an error message
2) remember the data which was valid (db or session)
3) show form or part of it to allow users to try again

But that logic can all go in the separate templates.

order.php:

Code: Select all

<?php
ini_set('error_reporting', E_ALL | E_STRICT);
ini_set('display_errors', 1);
ini_set('log_errors', 'Off');

// setup to start with clean base
$message  = '';
$clean    = array();
$html     = array();
$sql      = array();

define ("INCLUDED", TRUE );

if ( !isset($_SESSION) ) { session_start(); }

if( isset($_POST['token']) && $_POST['token'] == $_SESSION['token'] )
{    
    switch($_SESSION['formstep'])
    {
        case '1':
            // do stuff for second pageview
            $_SESSION['formstep'] = 2; // increase step
            include_once ('template2.php');
        break;
        
        case '2':
            // do stuff for third pageview
            $_SESSION['formstep'] = 3; // increase step
            include_once ('template3.php');
        break;
        
        case '3';
            // do stuff for fourth pageview
            $_SESSION['formstep'] = 4; // increase step
            include_once ('template4.php');
        break;
        
        default:
            // No valid formstep is available, therefore redirect to start page
            session_destroy(); 
            header("Location: http://www.mysite/order/test.php");
            exit();
        break;
    }

}
else
{
    $_SESSION['formstep'] = 1;
    include_once('template1.php');
}

?>
template1.php:

Code: Select all

<?php
// make sure template is not accessed directly
if (!defined( "INCLUDED" )) { die; }

// generate token
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
$_SESSION['token_time'] = time();  // useful for when I would like to check the time diff. not used at the moment

// show form
?>
<form action="<?php echo basename($_SERVER['SCRIPT_FILENAME']); ?>" method="post">
<fieldset>
<legend>Form</legend>

<input type="hidden" name="posted" value="1">
<input type="hidden" name="token" value="<?php echo $token ?>" />

<div>
    <label for="firstname">firstname:</label>
    <input type="text" value="<?php echo isset($html['firstname']) ? $html['firstname'] : ''; ?>" name="firstname" id="firstname" />
</div>
<div>
    <label for="lastname">lastname:</label>
    <input type="text" value="<?php echo isset($html['lastname']) ? $html['lastname'] : '' ?>" name="lastname" id="lastname" />
</div>

</fieldset>
<input type="submit" value="order">

</form>
There's still some work to do (the actual data processing) but this seems to work. Well, any suggestions are welcome. I guess that if you want a real generic, reusable multi-step form processor, you can't do without a good set of classes instead of this procedural mess .. :)

Posted: Fri Jan 05, 2007 11:14 am
by aaronhall
One thing: I think I would prefer to have the formstep stored as a hidden input in the form itself in case someone hits that back button and gets a cached version of the previous page.

Posted: Sat Jan 06, 2007 4:36 am
by matthijs
Thanks for your comment. It's a good suggestion, as the code worked perfect, except when hitting the back button ... . In trying to find a solution for that issue I came to the conclusion that the whole logic needs some rethinking. Again .. :)

So, I'll use the famous words of the governor himself, "I'll be back".

Posted: Sat Jan 06, 2007 5:07 am
by Ollie Saunders
This is exactly the kind of thing that made me write a forms library. Writing forms in this style really really sucks and makes you want to kill yourself.

Posted: Sat Jan 06, 2007 6:03 am
by matthijs
ole wrote:This is exactly the kind of thing that made me write a forms library. Writing forms in this style really really sucks and makes you want to kill yourself.
:D

Don't worry, I'm still alive. Kind of. Only my desk has some huge cracks in it, my girlfriend fled away and the goldfish are looking really frightened and shaky.

No just kiddin :wink:

Posted: Sat Jan 06, 2007 7:27 am
by Kieran Huggins
I abhor all threats against goldfish... except the yummy ones. Please feel free to rip off my client side library and get your girlfriend back:

See it in action: http://www.urbanemagazine.ca/subscribe/

Download the js: http://www.urbanemagazine.ca/_behaviors/formVal.js

It's not perfect, but it sure cuts down on the new desks.

Oh, and I have yet to implement the submission suppression and the autocompleting fields - work in progress.

Posted: Sat Jan 06, 2007 8:18 am
by matthijs
Thanks, I'll have a look at your js. That's something I wanted to add as a second layer when the server side is complete.

I just build a complete new version which seems to work a lot better. It's too much code to post here, but it's basically like:

Code: Select all

startup stuff

if first form has been posted
  validate first form
    if first formdata is valid
      - save valid data to session
      - set session[complete] to 1
      - show second form
    if first form not valid
      - show first form with errors

elseif second form has been posted
  check if session[complete] is 1. if so, continu, if not do ..
  validate second form
    if second form is valid
     - save valid data to session
     - set session[complete] to 2
     - show third form
    if second form not valid
      - show second form with errors etc

elseif third form has been posted
  check if session[complete] is 2, if so continu, if not do ..
  validate third form data
    if third form data is valid
      - save all data to db or email data etc
      - show final message, redirect to thank page etc

else show first form
This seems to work well, even when you go back to previous pages etc. Because the session dataspace remembers which steps have been completed and which data has been saved as valid it doesn't matter if someone by accident enters the wrong page (with the back button for example).

What I do next is start removing duplicate pieces of code and certain general pieces of logic and place them in classes / functions.

Then I'll end up with some reusable classes. Maybe it's not exactly the true TDD/agile way, but a nice exercise anyway. Maybe i should do a TDD/xp trial after/next to it and see were I end up.

Posted: Sun Jan 07, 2007 12:36 pm
by Ollie Saunders
Please feel free to rip off my client side library and get your girlfriend back:
When first time I read that as "Please feel free to rip my client side library off your girlfriend's back".
It's not perfect, but it sure cuts down on the new desks.
Looks very nice actually. I love what you did with the CSS except maybe the notes section that some may not realise you can actually write in because it looks just like an empty fieldset.

Posted: Sun Jan 07, 2007 1:48 pm
by Christopher
Nice library Kieran!

matthijs, what you really want is an Application Controller. It is a monstrous beast, but in the long run you will want to abstract you multi-"page" form is to implement a state machine controlled by rules.

Posted: Sun Jan 07, 2007 2:23 pm
by Kieran Huggins
ole wrote:Please feel free to rip my client side library off your girlfriend's back
:rofl:
ole wrote:Looks very nice actually. I love what you did with the CSS except maybe the notes section that some may not realise you can actually write in because it looks just like an empty fieldset.
Thanks - it looked crappy with two concurrent boxes... I never did feel satisfied with the current solution :-(

The library assumes a little regex knowledge, but it's not that scary, I promise! If you take a look at the example source you can steal a few pre-built ones.

Posted: Sun Jan 07, 2007 2:48 pm
by matthijs
ole wrote:Looks very nice actually. I love what you did with the CSS except maybe the notes section that some may not realise you can actually write in because it looks just like an empty fieldset.
Indeed, looks very nice but I agree that the way the inputs are styled makes me hesitate just a little bit before I understand they are input boxes. I don't know, that's just as a side note.
arborint wrote:matthijs, what you really want is an Application Controller. It is a monstrous beast, but in the long run you will want to abstract you multi-"page" form is to implement a state machine controlled by rules.
Yes, you are right. Even for the most simple (multistep) form, coding it like I did is probably just as monstrous as a bunch of classes in a Application Controller. Drinking a full bottle of wine gives me less of an headache then going through that again.

But as I have not that much experience with coding stuff, it was a nice experience. While messing with the code and the logic, and seeing the duplications and ugly stuff, it seems like certain patterns are begging to come out of it.

When you look at a long piece of procedural code like that you start thinking, I wish it was like this:

Code: Select all

<?php
$multiform = new Multiform();
$multiform->addStep('order');
$multiform->addStep('userdata');
//etc. just an example
?>
Thanks for the suggestion, I'll search for some state machine threads and info.