Retaining Values In A Form Following PHP Postback And Clearing Form Values After Successful PHP Form Processing

Asked recently on Yahoo! Answers:

Re-post data to PHP form when invalid?
I was just wondering, if I have PHP validation on a field of a large form, say for example the e-mail isn’t an e-mail (I know how to check that) and it sets header location to ?error=1 it does all that fine, but the form is loaded as completely blank again. How can I re-post information via PHP without them clicking anything? Thanks!

The quick answer is, if you use header() to redirect a user to the same page, all the form variables for that page are destroyed.

As such, rather than using a querystring variable to echo out an error message, it makes more sense to build an error message string, and echo that on the same page, while retaining the form values.

if(isset($_POST['submit'])) {
	$ok = true;
	$message = "<p>There are problems with this form. Please correct the following errors:</p><ul>";

	if(trim($_POST['name']) == "") {
		$message .= "<li>You did not enter your name.</li>";
		$ok = false;
	}
	if(trim($_POST['email']) == "") {
		$message .= "<li>You did not enter your e-mail address.</li>";
		$ok = false;
	}
	if(trim($_POST['city']) == "") {
		$message .= "<li>You did not enter your city.</li>";
		$ok = false;
	}

	if(!$ok) {
		$message .= "</ul>";
	}
	else {
		$message = "";
		//your processing code goes here
	}

	echo $message;
}

That said, let’s investigate how one echoes form values back to the page on unsuccessful form validation, and clears the form if it was valid.

Retaining Form Values On PHP Postback

Echoing form variables back in the event a form did not properly process is easy in the technical sense, but time-consuming in the practical sense, because it means editing every form field to use the posted / gotten variables.

Basically, you use the $_POST superglobal to echo out the values sent to the server (or $_GET, based on your form’s method attribute; you could also use $_REQUEST if you are mixing $_POST and $_GET variables in your form). The benefit of this method is that if the form hasn’t been sent to the server by the client, the relevant $_POST fields haven’t been populated, so nothing will show on initial page load.

For example, if you have a standard text input, you add the value attribute, and make it the $_POST value for that field:

<input type="text" name="foo" value="<?php echo $_POST['email']; ?>" />

For a textarea:

<textarea name="foo" cols="50" rows="5"><?php echo $_POST['email']; ?></textarea>

For a radio button, we set the checked property to checked if the value of the radio button matches the value stored in the $_POST variable for that named field.

<input type="radio" name="foo" value="bar" <?php if($_POST['foo'] == "bar") { echo "checked=\"checked\""; } ?> />

A checkbox is a bit more involved, since checkboxes are treated as an array by PHP. Basically, we need to iterate all the values stored in the $_POST variable for that element, and set the checked property to checked if the box was originally checked. Note that because we are calling foreach, we need to ensure that the $_POST field we intend to iterate is set (thanks to bustin98 for pointing this out).

<input type="checkbox" name="foo[]" value="bar" <?php if(isset($_POST['foo'])) { foreach($_POST['foo'] as $tmp) { if($tmp == "bar") { echo "checked=\"checked\""; break; }}} ?>

A select list basically follows either the method used for radio buttons or checkboxes, depending on whether it is a single-select or multiple select, respectively. Except that with a select list, we actually add code to each option under the select, to see if it was selected.

On a single-select list, this is straightforward:

<select name="foo">
	<option value="bar" <?php if($_POST['foo'] == "bar") { echo "selected=\"selected\""; } ?>>bar</option>
	<option value="hello" <?php if($_POST['foo'] == "hello") { echo "selected=\"selected\""; } ?>>hello</option>
	<option value="world" <?php if($_POST['foo'] == "world") { echo "selected=\"selected\""; } ?>>world</option>
</select>

For a multiple select:

<select name="foo[]" multiple="multiple" size="3">
	<option value="bar" <?php if(isset($_POST['foo'])) { foreach($_POST['foo'] as $tmp) {  if($tmp == "bar") { echo "selected=\"selected\""; break; }}}?>>bar</option>
	<option value="hello" <?php if(isset($_POST['foo'])) { foreach($_POST['foo'] as $tmp) {  if($tmp == "hello") { echo "selected=\"selected\""; break; }}}?>>hello</option>
	<option value="world" <?php if(isset($_POST['foo'])) { foreach($_POST['foo'] as $tmp) {  if($tmp == "world") { echo "selected=\"selected\""; break; }}}?>>world</option>
</select>

Clearing Form Values

So how do you clear form values in PHP if the form was properly processed or you want the user to start over? Just unset the $_POST or $_GET variable, again depending on your form’s method.

//clear all $_POST variables
unset($_POST);

You can see these principles in action here: http://demo.dougv.com/php_echo_form_variables/index.php

Passing Form Variables To Another Page

bustin98 notes in his comment that on some occasions, you may want to pass form variables to another page for processing; for example, you may have a multi-domain e-mail form handler.

Validating input is just as easily accomplished via the methodology I note here in that scenario. You can have the handler do the validation — that’s probably most practical when the forms using it always have the same values. Thanks the the browser’s back button, if there’s something wrong with the form, the user can just go back and correct his mistakes.

I provide an example of porting the validation code to the form handler here: http://demo.dougv.com/php_echo_form_variables/form.php

The Overkill Approach: curl And A Validation Include Page

If you want to be absolutely sure the form is valid before posting it to the remote form, you can do that quite easily via curl and an include file. Basically, you place the form validation code into an include file, including the script that posts results to the handler. Then, you include that code into all the forms you want to use.

First, let’s create the validation include page, which is just PHP. It uses the same validation routine we previously employed; but this time, it adds in code to send a curl request to the handler for the page if all input is valid.

if(isset($_POST['submit'])) {
	$ok = true;
	$message = "<p><strong>There are errors with this form.</strong> Please correct the following:</p>\n<ul>\n";

	if(trim($_POST['yname']) == "") {
		$message .= "<li>You did not provide your name.</li>\n";
		$ok = false;
	}
	if(!preg_match('/^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$/', $_POST['email'])) {
		$message .= "<li>You did not enter a properly formatted e-mail address.</li>\n";
		$ok = false;
	}
	if(trim($_POST['fruit']) == "") {
		$message .= "<li>You did not select a fruit.</li>\n";
		$ok = false;
	}
	if(empty($_POST['animal'])) {
		$message .= "<li>You did not select an animal.</li>\n";
		$ok = false;
	}
	if(trim($_POST['color']) == "") {
		$message .= "<li>You did not select a color.</li>\n";
		$ok = false;
	}
	if(empty($_POST['tool'])) {
		$message .= "<li>You did not select a tool.</li>\n";
		$ok = false;
	}

	if(!$ok) {
		$message .= "</ul>\n";
		echo $message;
	}
	else {
		foreach($_POST as $key => $value) {
			$fields .= "$key=$value&";
		}
		rtrim($fields, "&");

		unset($_POST);

		$ch = curl_init();
		curl_setopt($ch,CURLOPT_URL, "http://demo.dougv.com/php_echo_form_variables/handler2.php");
		curl_setopt($ch,CURLOPT_POST, count($_POST));
		curl_setopt($ch,CURLOPT_POSTFIELDS, $fields);
		$result = curl_e xec($ch); //remove the space in curl_e xec
		curl_close($ch);
	}
}

Next, we need to create the handler page. Note that in this handler page, I expect to receive only good values; I am not re-validating. I could, but for purposes of this demo, I won’t.

if(isset($_POST['submit'])) {
	$message = "<p>Here is what you chose for form variables:<br />" . print_r($_POST, true) . "</p>\n";
}
else {
	$message = "<p><strong>You have reached this page in error.</strong></p>\n";
}

echo $message;

To use this, we simply include it on the page with our form. I prefer to use require_once(), which ensures any functions / variables declared in the included file will not be overridden with local page code. The file should be included wherever you want the result message to appear; I include it just before the form.

<?php require_once('include.php'); ?>

You can see a working demo of this approach here: http://demo.dougv.com/php_echo_form_variables/validate.php

It’s my considered opinion that using curl in this way is pointlessly inelegant. God made the back button for a reason. You can simply ship off the validation code to the common handler page and achieve the exact same result as here, with half the effort and resources.

Equally as inelegant and wasteful would be going through the bother of transferring all $_POST variables to a $_SESSION variable, as bustin98 suggests, with the added “benefit” that $_SESSION variables require extra server overhead and are easily forged / poisoned.

But since this seems to be what bustin98 was after, I’ve posted it.

Code on github: https://github.com/dougvdotcom/php_echo_form_variables

I distribute code under the GNU GPL. See Copyright & Attribution for details.

14 Comments

  1. The problem with this is you’ll get error messages if the specified $_POST key does not exist for whatever reason. And you need to display the form on the same page that processes the form. Isn’t it better pactice to have forms submitted to a seperate page rather than to themselves? If you have a site that contains multiple forms, its more streamlined to have one page to deal with if there are changes in the backend rather than wasting time going to each page that has been affected.

    Ultimately I think this solution is for short-term ‘quick’ fix and does nothing for looking to the future.

  2. Actually, the fix is quite simple to the problem you note: simply checking whether the variable is set via isset() will prevent foreach() from throwing an error due to trying to iterate a null array.

    <input type="checkbox" name="foo[]" value="bar" <?php if(isset($_POST['foo'])) { foreach($_POST['foo'] as $tmp) { if($tmp == "bar") { echo "checked=\"checked\""; break; }}} ?>
    

    I should have tested this code before posting it; for that I apologize. I have since created a demo page that is proof of concept and corrected this post.

    There is not necessarily a benefit to posting results to a different page than the one containing the form. It may be that you want a common handler for multiple forms on multiple sites, such as a multi-domain e-mail handling form. In such cases, you can use the same form checking technique I note here on that common form. You can also check inputs on your form, then use curl to post validated results to the common form, reducing load on the common handler. Otherwise, posting to the same form generally requires less coding and therefore, is more elegant.

    I completely disagree with your assessment that “this solution is for short-term ‘quick’ fix and does nothing for looking to the future.” I have been programming PHP for 12 years. I assure you, this method works fine and is considerably more reliable and efficient than what you were trying to do. And again, as I noted in your Yahoo! Answers question, if you use header() to redirect someone to another, or the same, page, you destroy the $_POST superglobal values for that page.

    You suggested using $_SESSION variables, which is far less elegant and considerably more wasteful than this approach.

  3. This is indeed a very informative post, and in no way a ‘short-term fix’. The fact that the form is in the same file as the processing code is an advantage.

  4. There is one value to posting form variables to a different processing page that I did not note: The ability to prevent reposting the form via the refresh button.

    If you use the same page to accept and process form input, there is no practical way in PHP alone to prevent the same form from processing again if the user hits the refresh button — even if you call unset() on the $_POST array.

    The surest way to reset form variables, therefore, on successful processing is to post the form to a different page, and have it check and sanitize variables. On error, it would simply report the error and urge the user to use the Back button to correct mistakes; on success, it wouldredirect the user to another page.

    However, even this is not concrete, as the user can simply use his back button to reach the processing code page and refresh his browser there.

    In that sense, there’s some value to bustin98’s approach. He uses session state to preserve variables and selectively process code.

    That’s similar to the ASP.NET approach, which leverages session state to determine if a page has been posted back to a server and to selectively process form variables. However, ASP.NET uses a considerably different mechanism for storing and hashing session data than PHP uses, which combined make ASP.NET sessions considerably more efficient and secure than PHP sessions.

    In the end, if it is vital to accept data only once, there are a number of better options than PHP sessions. Tokens are one approach; binaries, another, with lots of middle ground between them. But I remain convinced that incurring the overhead of a PHP session to do what pretty much every browser already provides via the Back button is pointlessly inelegant and a significant security risk.

  5. I still don’t see any advantage to posting to a separate page. As mentioned, using a token can prevent reposting and the code for the token check can be in the same page. Using session scope for form variables pollutes the session scope and leads to hard to find errors. It’s best to use session scope sparingly (as in to store a single token).

  6. Oh mannnnnnnnnnnnnnnnnnnnnnn !!!!!

    since one week I’ m looking to clear all the form input after submit the form. 10 min ago I was about giving up then I found the answer here in your website .
    5 stars to you.

  7. Great tutorial Doug! I have a question though… what if you have two forms? I tried the code for keeping the value for one form (in this case, a background color changer), your code works! But when I submitted the value of another form… the page refreshes and the kept value for the background color changer disappeared.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  • Check out the Commenting Guidelines before commenting, please!
  • Want to share code? Please put it into a GitHub Gist, CodePen or pastebin and link to that in your comment.
  • Just have a line or two of markup? Wrap them in an appropriate SyntaxHighlighter Evolved shortcode for your programming language, please!