Form and PHP validation on one page

See more about:

Where are we?

You know how to get data from a form, and validate it. You know how to use a validation function. Let’s improve the way that’s done. Again.

This lesson’s goals

By the end of this lesson, you should:

  • Know how to have the HTML for the form and the PHP validation code on the same page.
  • Know how to collect error messages in a single variable, rather than treating them all separately.

The form/processor pattern

Up to now, we’ve separated the page with the input form from the page that does validation and processing. Like this:

Pattern so far

Figure 1. Pattern so far

The .hmtl page has a <form>. The action attribute of the form has the name of the processing page. When the user clicks the submit button, the form data is sent to the processing page.

The processing page validates the data. If there is a problem, it shows a message to the user, and suggests going back to the order form again.

If all the form data is OK, the order is processed. This means telling the shipping department to send the order, processing payment, and other businessy things.

A new pattern

But there’s another way of doing things. The new pattern is to put both the form and the server-side PHP validation code on the same page.

The strangest thing about this is that the page with the form submits data to itself.

A new pattern

Figure 2. A new pattern

This is helpful when you do both client-side and server-side validation, and want to show both types of errors to the user in the same way. The new pattern lets you be more consistent in the way you show errors to users.

The key to this is that the page works in two modes:

  • New input mode: The user wants to fill in the form with some new data.
  • Process input mode: Check data the user has already entered.

The page uses if statements to act differently, depending on which mode it’s in.

What are these “modes” you speak of?

Let’s be sure we know what I mean by “modes” here. You’re used to thinking about modes that are built in to devices. For example, you might have an SUV that works in two-wheel or four-wheel drive. Two different modes. You use a switch (or whatever) to choose the mode you want.

The engineers who designed the vehicle built those modes into it.

Another example is the camera in your cell phone. It can work in still photo mode. Or it can work in video mode, where it shoots movies. You choose the modes you want.

The engineers who designed the cell phone built those modes into it.

But when you write PHP, you are the engineer. You get to create modes yourself. You decide:

  • What the modes are
  • What each one does
  • How each mode is activated

You do this by the way you write your code. There is no special “mode” statement in PHP. The modes are created by the way you put together if statements and other things.

Let’s see how all this works.

What the user sees

Here’s an empty order form, from order.php.

Empty order form

Figure 3. Empty order form

Suppose the user enters some bad data into order.php:

Bad data

Figure 4. Bad data

The form data is sent from order.php to order.php; the page sends the form data to itself. Here is what it shows:

Error messages

Figure 5. Error messages

Now, what if the user enters valid data?

Valid data

Figure 6. Valid data

Here is the result:

Order confirmation

Figure 7. Order confirmation

You can try it. Enter different combinations of good and bad data into the fields, and see what you get.

Some code

Here’s order.php.

<?php
if ( $_POST ) {
  //There is order data to validate.
  require 'validation-library.inc';
    //Warning: Extra characters at the end of validation-library.inc
    //will break this page!
  //Get the order amounts.
  $frisbees = $_POST['frisbees'];
  $giant_chew_ropes = $_POST['giant_chew_ropes'];
  //Validate input.
  $error_message_frisbees = check_number_ordered($frisbees, 15);
  $error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);
  //Jump if no errors.
  if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
    $destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";
    header("Location:$destination_url");
    exit();
  }
}?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Order Form</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <h1>Order Form</h1>
    <?php
    //Any error messages to display?
    if ( $error_message_frisbees != '' ) {
      print "<p>Frisbees: $error_message_frisbees</p>";
    }
    if ( $error_message_giant_chew_ropes != '' ) {
      print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
    }
    ?>
    <form method="post" action="order.php">
      <p>
        Frisbees:
        <input type="text" name="frisbees" size="3"
        <?php
        if ( $_POST['frisbees'] ) {
          print ' value="' . $_POST['frisbees'] . '"';
        }
        ?>
        >
        at $8.95 each
      </p>
      <p>
        Giant chew ropes:
        <input type="text" name="giant_chew_ropes" size="3"
        <?php
        if ( $_POST['giant_chew_ropes'] ) {
          print ' value="' . $_POST['giant_chew_ropes'] . '"';
        }
        ?>
        >
        at $12.95 each
      </p>
      <p>
        <button type="submit">Order</button>
      </p>
    </form>
  </body>
</html>

Figure 8. order.php

Line 2:

if ( $_POST ) {

checks whether there is any form data coming in. If there is no post data, then $_POST will be empty, which, in PHP, is like bring false.

This is the mode check, if you want to think of it that way. If there is post data, we enter “check input” mode. Otherwise, we’re in “empty form” mode.

We created a validation function on the previous page. We’ll put it in a separate file, and bring it in like this (lines 4):

require 'validation-library.inc';

We’ll talk about the warning in lines 5 and 6 later.

Lines 8 and 9 get the form data into variables, as usual. Here’s one of the lines:

$frisbees = $_POST['frisbees'];

As before, the function check_number_ordered() checks the value passed in, and returns either an error message or a blank string (line 11):

$error_message_frisbees = check_number_ordered($frisbees, 15);

The next step is to check whether there are any errors. There were no errors if both the frisbees value and the giant ropes value were OK.

Here is the code:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
  $destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";
  header("Location:$destination_url");
  exit();
}

Part of Figure 8 (again). order.php

Line 14 has the test. Remember that the validation function check_number_ordered() returns an empty string. So that if both $error_message_frisbees and $error_message_giant_chew_ropes are empty strings, there were no errors.

If the test is true, we need to tell the browser to jump to the order processing page (order.php). To jump to another page, send the HTTP header location, like this:

header('location: Destination ');

So to jump to the order processing page (line 16):

header('location: order.php');

But we also need to send the order data to order.php. How?

By doing a pretend get.

Renata
Renata

Huh?

Remember that there are two ways to send form data:

<form method="get">

and

<form method="post">

It’s the get method we care about here.

The get method attaches form data to the URL, like this:

order.php?frisbees=2&giant_chew_ropes=4

The destination page – order.php – can retrieve the data like this:

$frisbees = $_GET['frisbees'];
$giant_chew_ropes = $_GET['giant_chew_ropes'];

OK, so how to send the data? We make a pretend get by attaching data to the URL ourselves, rather than having a form do it. Like this:

$destination_url = "process.php?frisbees=$frisbees&giant_chew_ropes=$giant_chew_ropes";

Remember that PHP replaces variables with their values in strings with double quotes (”). So if $frisbees had 2 in it, and $giant_chew_ropes had 4 in it, we would get:

$destination_url = "process.php?frisbees=2&giant_chew_ropes=4";

This is the same URL that a form with method="get" would produce. W00f!

Renata
Renata

Cool!

Once we have the URL, we can send it to the browser:

header("Location:$destination_url");

There is no point processing the rest of the page, so the next line is:

exit();

This tells the PHP interpreter to stop processing the page immediately, and return whatever it has so far to the Web server. All that it has is the header. The Web server returns that to the browser. The browser jumps to process.php, sending the order along for the ride.

Handling errors

If there are errors, we want this:

Error messages

Figure 5 (again). Error messages

We need to do three things:

  • Show error messages.
  • Show the form again.
  • Fill in the form’s values with what the user had typed.

Here’s how the error messages are shown.

<h1>Order Form</h1>
<?php
//Any error messages to display?
if ( $error_message_frisbees != '' ) {
  print "<p>Frisbees: $error_message_frisbees</p>";
}
if ( $error_message_giant_chew_ropes != '' ) {
  print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
}
?>

Part of Figure 8 (again). order.php

Now the second thing, showing the form. We use the usual HTML tags: <form>, <input>, etc.

The third thing we need to do is restore the data the user had typed into the fields. Whether the data was valid or not.

CC
CC

Why put data the user typed back into the input fields? Especially when some of it is invalid.

Two reasons.

  • There are two fields, and one of them might be correct. We don’t want the user to have to retype that data, just because the other field was bad.
  • The user might have typed several correct characters into a field, with just one of them wrong. It would be annoying if everything in the field just vanished.

So how to do it? You need to know about another attribute of the <input> tag: value. It lets you set the contents of a form field in HTML, without the user having to type anything. For example, here’s some HTML:

<input type="text" value="Dogs rock!">

This renders as:

<code>value</code> attribute

Figure 9. value attribute

You can try it. Notice that text appears in the field, without your typing anything.

Here is some code for our order form:

Frisbees:
<input type="text" name="frisbees" size="3"
<?php
if ( $_POST['frisbees'] ) {
  print ' value="' . $_POST['frisbees'] . '"';
}
?>
>

Part of Figure 8 (again). order.php

If $_POST['frisbees'] has, say, 7 in it, the code will create the HTML:

<input type="text" name="frisbees" size="3" value="7">

That’s the end of the page. W00f!

Improving error message collection

Here’s the error checking and reporting code.

//Validate input.
$error_message_frisbees = check_number_ordered($frisbees, 15);
$error_message_giant_chew_ropes = check_number_ordered($giant_chew_ropes, 10);
//Jump if no errors.
if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {
...
//Any error messages to display?
if ( $error_message_frisbees != '' ) {
  print "<p>Frisbees: $error_message_frisbees</p>";
}
if ( $error_message_giant_chew_ropes != '' ) {
  print "<p>Giant chew ropes: $error_message_giant_chew_ropes</p>";
}

Part of Figure 8 (again). order.php

In this code, we have a different error message variable for each product. That’s fine, because we have only two products.

What if we had more than two products? For each one, we’d need to create a new variable. And Line 14 would get more complex. And we’d need to remember to add another if statement to report the error.

Here’s a better way of doing it.

//Validate input.
$messages = ''; //Variable to accumulate messages.
$error_message = check_number_ordered($frisbees, 15);
if ( $error_message != '' ) {
  $messages .= "<p>Frisbees: $error_message</p>";
}
$error_message = check_number_ordered($giant_chew_ropes, 10);
if ( $error_message != '' ) {
  $messages .= "<p>Giant chew ropes: $error_message</p>";
}
//Jump if no errors.
if ( $messages == '' ) {
...
<h1>Order Form</h1>
<?php
//Any error messages to display?
if ( $messages != '' ) {
  print $messages;
}
?>

Figure 10. Improved error reporting

Line 11 creates a variable called $messages. It’s used to collect error messages. When a new error is detected, some text is added to this variable.

Line 12 checks the frisbees:

$error_message = check_number_ordered($frisbees, 15);

Remember that check_number_ordered() returns an empty string if $frisbees is OK, otherwise it returns an error message.

Here’s the next bit of code:

if ( $error_message != '' ) {
   $messages .= "<p>Frisbees: $error_message</p>";
}

.= is a shortcut operator. Remember that . means string concatenation, so:

'this' . 'that'

is:

thisthat

The line:

$x .= $y;

is a shortcut for:

$x = $x . $y;

So .= appends something to an existing variable.

This line:

$messages .= "<p>Frisbees: $error_message</p>";

creates an HTML paragraph tag with an error message in it, and adds it to whatever is already in $messages.

The next few lines are:

$error_message = check_number_ordered($giant_chew_ropes, 10);
if ( $error_message != '' ) {
   $messages .= "<p>Giant chew ropes: $error_message</p>";
}

If there is a problem with $giant_chew_ropes, an error message gets added to whatever is already in $messages.

If more products are added to the page, we can just repeat these few lines as needed. So if, for example, we started selling dog collars, we would have:

$error_message = check_number_ordered($dog_collars, 8);
if ( $error_message != '' ) {
   $messages .= "<p>Dog collars: $error_message</p>";
}

There is no need to create another variable for a dog collar error message. If there is an error, just add the new message to the other messages.

After checking each individual form field, we need an if statement to decide whether to jump to the order processing page. The browser should jump only if there were no errors.

Here’s what we had in the original code:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '') {

Each time we add a new product, we’d have to change this line. For example, if we had five products:

if ( $error_message_frisbees == '' && $error_message_giant_chew_ropes == '' && $error_message_dog_collars == '' && $error_message_squeaky_balls == '' && $error_message_hide_bone == '' ) {

Ack! What a pain!

But with the new approach, we don’t need to do that. All error messages are added to the variable $messages. So if we want to check whether there have been any errors, we can just check that one variable (line 21 in Figure 10):

if ( $messages == '' ) {

Ten products? Twenty? It doesn’t matter. This line will never change.

When it comes to reporting errors to the user, we have this code:

if ( $messages != '' ) {
   print $messages;
}

Again, it doesn’t matter how many products there are, or how many potential error messages. Our code has collected all the error messages in $messages.

This is another example of how Webers think about productivity. Web pages change. Products are added and removed. This approach – accumulating error messages in a variable – makes it easier to make those changes.

Summary

In this lesson, you learned:

  • How to have the HTML for the form and the PHP validation code on the same page.
  • How to collect error messages in a single variable, rather than treating them all separately.

What now?

Here’s the error message display again:

Error messages

Figure 5 (again). Error messages

That’s, well, yucky. Let’s improve the user’s experience.


How to...

Lessons

User login

Log in problems? Try here


Dogs