It occurred to me that there are many wrong ways to submit forms and I can’t recall that I have every seen the right way documented anywhere.
Here, SolidlyStated will show you a 3 step, rock-solid form submission process and why it is superior to the majority of the web. This process is for PHP, but the concept transfers to .NET, JAVA, and others.
The Problems We Will Solve
- No worries when refreshing the page
- No worries with using the browser back button
- No duplicate form submissions, ever
- Submitting to the correct script
- Using the right form method
- No encoding or length issues on submitted data
- No caching issues on submitted data
Misinformation Everywhere
I was dismayed by a Google search for “php proper form handling.” With bad suggestions like submitting the form page to itself because it is “easy,” it is time to set the record straight. Another “easy” way to go about things is by using GET as a form method. After all, we use GET variables with JavaScript a lot and it’s convenient to hand out URLs with GET parameters in them.
However, using GET for submitting forms is another bad suggestion. Luckily, you can also find good information, such as the “PRG Patten,” or Post/Redirect/Get. This programming pattern will solve half of the problems a developer encounters when creating forms. Next, I will go through each part of properly submitting a form using PHP.
Step 1: The Form Tag- Action and Method
<form action="process.php" method="post"> |
- Always use a form action attribute an do not leave it blank. A blank form action is not allowed in HTML5. An omitted form action is allowed but not advised. While it seems “easy” to post a form to itself and handle processing at the top, this opens you up to duplicate form submissions with page refreshing or other pitfalls.
- Don’t submit the form to itself. Semantically, we want to separate the pages that pull info FROM the server from those that submit data TO the server.
- Always use the
post
method and not theget
method. This is a totally separate article. In short, post is for updating the server and get is for fetching from the server. The get method presents issues with the amount of data you can submit and caching of data. - Use lowercase “post” and not uppercase “POST.” The attribute is case-insensitive, but all attributes should be written in lowercase. Check out the markup conventions here.
Step 2: Avoiding Duplicate Submissions From Back Button or Refresh
This is where we take advantage of the “PRG Pattern.” This is one of the smartest patterns in development. What we want to do is post data to the server, then redirect the page using the PHP header() function and the 303 HTTP status, then get the results for displaying.
Consider a web application with a form on the page myform.html
or myform.php
. You will post that form to some processing script, such as process.php. At the end of your form processing logic, put the following redirect:
header("myform.html",true,303); |
Using the 303 redirect, also known as “303 See Other,” allows your form to be processed and then sent to the page of your choosing without fear of the back button or F5 (page refresh) being used to submit the form a second time. We use 303 specifically here because 301 and 302 are for showing that content has been moved from its former home, which does not apply here. 303 is almost exclusively for doing form submissions with the PRG pattern and I have never used it in any other fashion.
Step 3: Avoiding Duplicate Submissions From JavaScript or Server
The PRG pattern is great, but doesn’t cover every possible scenario of duplicate form submissions. We will take one extra measure to ensure a duplicate form submission never happens.
This technique is to use an extra hidden field for a time stamp of the form submission. You keep a record of the last few form submissions to make sure that the browser is not attempting to submit data that was already sent before. Use the following hidden input to pass the time stamp as a field called “hash.”
<form name="login" method="post" action="process.php"> <input type="hidden" name="hash" id="hash" value="<?php echo microtime(); ?>" /> // ... your form data </form> |
I submit all my forms through a single processing class in PHP. Below is the contents of a checkHash() function that I perform on every form submission.
I pass a cleaned $_POST[“hash”] to it. By “cleaned” I mean I have ran mysqli_real_escape_string
on it, which is something you should be doing for every post value! Of course you need to use a session for this.
I would include versions of my completed processing class, but I am here to teach you and not spoon feed you. Make a function that contains the following code, and run it on every form submission.
This code keeps a rolling tally of the last FIVE submitted time stamps. If, somehow, there is a duplicate submission even after we have used the PRG Pattern from Step 2, you will catch it here and can redirect the page or take some other action.
// $h is the cleaned value of $_POST["hash"] if(isset($_SESSION["hash"]) && is_array($_SESSION["hash"])) { if( in_array($h,$_SESSION["hash"]) ) { // duplicate form submission /* REDIRECT SOMEWHERE HERE, PREFERABLY WITH SOME SORT OF MESSAGE! */ } else { // add this hash to the array if(sizeof($_SESSION["hash"]) > 4){ array_shift($_SESSION["hash"]); } array_push($_SESSION["hash"],$h); } } else { // create a hash array and add this hash $_SESSION["hash"] = array($h); } |
You’re Done!
By following the previous 3 steps on all your form submissions, you will have built a semantically sound process that avoids all the pitfalls of web form development (and all the possible issues outlined up top).
To help you remember the three-step process, let’s reduce it to 3 sentences:
- Always write a proper form tag.
- Always include a time stamp in the form.
- Always do a 303 redirect after processing.
Hello and thank you for your article.
Since i am not quite familiar with php, i wonder if you could be a little bit more specific about steps 2 and 3.
Specifically:
The “header(“myform.html”,true,303);” goes to the end of the php file or in the checkHash() function.
The checkHash function has no arguments? if it has, the argument is the variable $h?
How do i pass a cleaned $_POST[“hash”] and how do i run it on every form submission?
And finally where do i call the function?
Thank you in advance,
George Dimitropoulos
Hi George. Some of your questions are outside the scope of this article and it sounds like you might benefit from studying PHP for a little while. Cleaning and working with POST data in PHP can be researched with Google.
As the article says, the header() call goes at the end of your form processing logic, wherever you decide to put it. The checkHash function can be written any way you want it. You have access to the POST data and the concept is that you check to see if the posted timestamp has been used already. How you do that is up to you. Obviously you would check that before updating a database.
thanks
Hi,
Do you have the source code that we can run to test this? I would be very thankful to check this out.
Keep on posting!
Thanks