Publishers of technology books, eBooks, and videos for creative people

Home > Articles > Web Design & Development > PHP/MySQL/Scripting

From the book Improving the Registration Form

Improving the Registration Form

The registration form created in Lesson 6 has serious flaws. There’s no control over the values entered in each input field. If you submit the form without filling in any of the fields, MySQL stops you, but the server behavior code leaves you with this unhelpful message and no way to get back to the form.

This is MySQL’s way of saying that first_name is a required field, but you need a better way of conveying that message to the user.

What’s more, a series of blank spaces is accepted as valid input, so you could end up with a completely blank record. There’s also the problem of duplicate usernames, not to mention setting a minimum length for the password. The registration form needs to check all the user input before attempting to insert it into the database and to redisplay the form with error messages if validation fails, as shown in the following diagram.

Additionally, there’s the question of forgotten passwords. In Lesson 8, you’ll learn how to send users an email to reset their password, so you need to add extra columns to the users table—one to store email addresses and the other for a security token.

There’s a lot to fix. Let’s start by updating the users table.

Adding a unique index to the users table

A unique index prevents duplicate values from being inserted in a database column. Adding an index to a column is quick and easy in phpMyAdmin.

  1. Open phpMyAdmin, and select the users table in the phpcs5 database.
  2. If you have any records with duplicate usernames, click the Delete icon next to the record you want to delete. Leave at least one record in the table, because you need it for testing later.
  3. Click the Structure tab at the top left of the screen to display the definition of the users table.
  4. Click the Unique icon in the Action section of the username row. When the page reloads, you should see confirmation that an index has been added to username. The SQL command used to create the index is displayed immediately below. Check that it says ADD UNIQUE.
  5. If you clicked the wrong icon and created a different type of index or a unique index on the wrong column, click the Details link at the bottom of the page to reveal the Indexes section, and delete the index you have just created; then repeat step 4 to add a unique index on username.

Leave phpMyAdmin open with the Structure tab selected to continue with the instructions in the next section.

Adding extra columns to the users table

It takes only a couple of minutes to add a column to a database table in phpMyAdmin. Changing the structure of a database is simple, but it should normally be done only in the development stage. Once you start filling the database with records, you risk losing data or having incomplete records.

  1. With the Structure tab of the users table selected in phpMyAdmin, locate “Add field(s)” toward the bottom of the screen. Type 2 in the text field, leave the “At End of Table” radio button selected, and click Go.
  2. This presents you with a matrix where you define the two new columns. Because there are only two, the options are listed vertically, which makes them easier to see.

  3. For the email column, type email in Field, set Type to VARCHAR, and Length/Values to 100.
  4. The token will be a randomly generated, fixed-length string. For the other column, type token in Field, set Type to CHAR, and Length/Values to 32. Also select the Null checkbox to make this column optional.

  5. Click Save. The revised table structure should look like this:

There is no need to update the existing record(s) in the users table. They can be deleted after you have tested the script later in this lesson.

Loading ZF class files

Before you can use ZF classes and objects, you need to include the definition files into each page. With such a large framework, it would be cumbersome to include each file individually, so ZF provides an autoloader. This loads only those class definitions that are needed for the current script. For it to work, you need to add the library folder to your include_path, where PHP looks for include files.

  1. Create a new PHP file, and save it as library.php in lesson07/workfiles/scripts.
  2. Switch to Code view, and delete all the HTML code. You should have a completely blank file.
  3. Add an opening PHP tag at the top of the file. Do not create a matching closing PHP tag.
  4. On the next line, assign the absolute path to the library folder to $library.
  5. The value depends on your operating system and where you saved ZF.

    • On Windows, it should look similar to this:
    • $library = ‘C:/php_library/ZendFramework/library’;
      

      You can use either forward slashes or backslashes in the path, but it’s more common to use forward slashes.

    • On Mac OS X, it should look something like this:
    • $library = ‘/Users/username/php_library/ZendFramework/library';
      

      Note that the path begins with a forward slash. Replace username with your own Mac username.

  6. The value of include_path is specified in php.ini, but you don’t always have access to php.ini on shared hosting, so you can use the set_include_path() function to change it on the fly. Add the following code on the next line:
  7. set_include_path(get_include_path() . PATH_SEPARATOR . $library);
    

    Rather than overwriting the existing value of include_path, you need to add $library to it. The existing value is retrieved by get_include_path(). Each path needs to be separated by a semicolon on Windows or a colon on Mac/Linux. To make the code portable between different operating systems, the constant PATH_SEPARATOR inserts the appropriate separator automatically. Everything is joined with the concatenation operator (a period or dot).

  8. To use the autoloader, you need to include the class file for Zend_Loader_Autoloader like this:
  9. require_once(‘Zend/Loader/Autoloader.php’);
    

    This is the only ZF file that you need to load explicitly. You don’t need to use a fully qualified path to the Zend folder, because the code in the previous step added its parent folder, library, to the PHP include_path.

  10. Now invoke the autoloader like this:
  11. $loader = Zend_Loader_Autoloader::getInstance();
    

    Technically speaking, you don’t need to assign the result to a variable, but it’s useful to do so to check that everything is working correctly.

  12. To test your script so far, add the following:
  13. if ($loader) {
      echo ‘OK’;
    } else {
      echo ‘We have a problem’;
    }
    
  14. Save library.php, and click Live View to test the page. If everything is OK, you should see OK onscreen. If you see “We have a problem,” read the error message(s). The most likely cause is a mistake in the path to the library folder. Also, check the spelling of all the functions and make sure PATH_SEPARATOR is all uppercase.
  15. Once everything is working, remove the conditional statement that you added in step 8. You can also remove the $loader variable from step 7. The code in your page should look like this (the value of $library depends on your setup):
  16. <?php
    $library = ‘C:/php_library/ZendFramework/library’;
    set_include_path(get_include_path() . PATH_SEPARATOR . $library);
    require_once(‘Zend/Loader/Autoloader.php’);
    Zend_Loader_Autoloader::getInstance();
    

    Do not add a closing PHP tag. See the sidebar, “Omitting the Closing PHP Tag,” for an explanation.

Connecting to the database with Zend_Db

In ZF, all communication with a database is done through a Zend_Db object. This is similar to setting up a MySQL connection for Dreamweaver’s server behaviors, but it has two significant advantages:

  • A Zend_Db object doesn’t connect to the database until it’s needed. This puts less strain on the database than a Dreamweaver MySQL connection, which always connects, even if the script doesn’t need it.
  • Zend_Db has several adapter subclasses that connect to different database systems. To connect to a different database, just create a Zend_Db object using the appropriate subclass. Normally, this involves a single line of code unless your SQL uses database-specific functions.

Since most pages require a database connection, it makes sense to instantiate the Zend_Db object in the same file that loads the ZF classes.

  1. To connect to a database, you need to supply the location of the database server, the username and password of the account you want to use, and the name of the database. You pass these details as an associative array (see “Creating an associative array” in Lesson 3) to the Zend_Db constructor, using the array keys ‘host’, ‘username’, ‘password’, and ‘dbname’.
  2. Create an array for the cs5write user account at the bottom of library.php like this:

    $write = array(‘host’     => ‘localhost’,
                   ‘username’ => ‘cs5write’,
                   ‘password’ => ‘Bow!e#CS5’,
                   ‘dbname’   => ‘phpcs5’);
  3. Create another array for the cs5read account directly below.
  4. $read  = array(‘host’     => ‘localhost’,
                   ‘username’ => ‘cs5read’,
                   ‘password’ => ‘5T@rmaN’,
                   ‘dbname’   => ‘phpcs5’);
    
  5. Your choice of Zend_Db adapter depends on the database you want to use and the PHP configuration of your remote server. If your remote server supports pdo_mysql, use this:
  6. $dbWrite = new Zend_Db_Adapter_Pdo_Mysql($write);
    

    If your remote server supports only mysqli, use this:

    $dbWrite = new Zend_Db_Adapter_Mysqli($write);
    
  7. Create another object for the cs5read account, using the appropriate adapter:
  8. $dbRead = new Zend_Db_Adapter_Pdo_Mysql($read);
    

    Or

    $dbRead = new Zend_Db_Adapter_Mysqli($read);
    
  9. Because a Zend_Db object doesn’t connect to the database until it’s needed, it’s a good idea to make a test connection to ensure your code is OK. Add these conditional statements at the end of library.php:
  10. if ($dbWrite->getConnection()) {
      echo ‘Write OK<br />’;
    }
    if ($dbRead->getConnection()) {
      echo ‘Read OK’;
    }
    

    When you type the -> after the object, code hints should show you the methods it can use. The getConnection() method has a self-explanatory name. If each connection is OK, the conditional statements display confirmation. If there’s a problem, you’ll see a fatal error similar to this:

    Don’t panic. The important information is in the second line, which says access was denied for cs5write and that a password was used. This normally means the password was wrong.

    Another possible cause is choosing the wrong adapter class. It’s easy to mix up Zend_Db_Adapter_Pdo_Mssql with Zend_Db_Adapter_Pdo_Mysql. The former is for Microsoft SQL Server. If you make this mistake, the error message is likely to tell you that the mssql driver is not installed. If it is installed, you might be trying to connect to the wrong database server.

    Check your code, paying particular attention to spelling and case sensitivity.

  11. After verifying that your connections are working, delete the code you added in step 5. It’s not needed any more.
  12. Leave library.php open to continue working with it in the next section.

Handling exceptions with try and catch

ZF is an object-oriented framework. If an error occurs in any part of the script, it throws an exception. Unlike an ordinary PHP error, which displays the error message at the point in the script where it occurs, an exception can be handled in a different part of the script. If you look closely at the first line of the fearsome error message in the preceding screen shot, you’ll see it refers to an “uncaught exception.” When you throw something, it needs to be caught.

To prevent this sort of unsightly error message, you should always wrap object-oriented code in try and catch blocks like this:

try {
  // main script
} catch (Exception $e) {
  echo $e->getMessage();
}

The main script goes between the curly braces of the try block, where PHP tries to run the code. If all is well, the code is executed normally, and the catch block is ignored. If an exception is thrown, the script inside the try block is abandoned, and the catch block runs instead.

Objects can define many different types of exceptions, so you can have different catch blocks to handle each type separately. The Exception in the parentheses after catch indicates it’s a generic catch block to handle all exceptions. The exception is assigned to the variable $e so you can access any messages it contains. At the moment, the catch block just uses echo and the getMessage() method to display the error message. When the script is ready to be deployed in a real site, you replace the code in the catch block with a more elegant way of handling the problem, such as displaying an error page.

You need to wrap most of the code in library.php in a try block, and add a catch block at the bottom of the page.

  1. Position the insertion point at the end of the following line, and press Enter/Return to insert a blank line:
  2. require_once(‘Zend/Loader/Autoloader.php’);
    
  3. On the new line, type try, followed by an opening curly brace.
  4. Select all the code on the following line to the bottom of the page, and click the Indent Code icon in the Coding toolbar to indent the code in the try block.
  5. Add a new line at the bottom of the page, and insert the closing brace of the try block, together with a catch block like this:
  6. } catch (Exception $e) {
      echo $e->getMessage();
    }
    
  7. Save library.php. You can compare your code with lesson07/completed/library.php.

Using Zend_Validate to check user input

The standard way of validating user input on the server is to create a series of conditional statements to test if a value meets certain criteria. For example, if you want to check whether a password contains between 8 and 15 characters, you can use the PHP function strlen(), which returns the length of a string, like this:

if (strlen($_POST[‘password’]) >= 8 && strlen($_POST[‘password’]) <= 15) {
  // it’s valid
}

This works, but it doesn’t check what characters are used in the password. Pressing the spacebar eight times passes this test. So, you need to add other conditional statements to make sure all criteria are met.

Zend_Validate works in a similar way but provides a set of commonly used validators. Each subclass has an easily recognizable name that makes your validation script much easier to read, and you don’t need to become an expert in PHP functions to ensure that user input matches your requirements. Table 7.1 lists the most commonly used subclasses. Each one is prefixed by Zend_Validate_, so Alnum becomes Zend_Validate_Alnum.

For example, to check that a string is 8–15 characters, use Zend_Validate_StringLength like this:

$val = new Zend_Validate_StringLength(8,15);

This instantiates a Zend_Validate_StringLength object, setting its minimum and maximum values to 8 and 15 respectively, and assigns it to $val.

To check whether $_POST[‘password’] contains between 8 and 15 characters, pass it as an argument to the isValid() method like this:

$val->isValid($_POST[‘password’])

If $_POST[‘password’] contains 8–15 characters, this returns TRUE. Otherwise, it returns FALSE.

Normally, if a validation test fails, you want to generate an error message. Do this by using a conditional statement with the logical Not operator (see “Using the logical Not operator” in Lesson 3) like this:

$val = new Zend_Validate_StringLength(8,15);
if (!$val->isValid($_POST['password'])) {
  $errors['password'] = 'Password should be 8-15 characters';
}

Adding the logical Not operator looks for a value that is not valid, so the error message is assigned to $errors[‘password’] only if $_POST[‘password’] is not 8–15 characters.

This validation test is fine as far as it goes, but it has the same problem as the earlier example: It checks only the number of characters. Pressing the spacebar 8–15 times still passes validation. You need to combine validators. One way is to use a series of conditional statements, but ZF offers another solution—chaining validators.

Table 7.1 Commonly Used Validation Classes

Class

Description

Alnum

Checks that the value contains only alphabetic and number characters. Whitespace characters are permitted if TRUE is passed as an argument to the constructor.

Alpha

Same as Alnum except numbers are not permitted.

Between

Accepts a value between minimum and maximum limits. Constructor requires two arguments, which can be numbers or strings, to set the limits. By setting an optional third argument to TRUE, the value cannot be equal to either the maximum or minimum.

CreditCard

Checks whether a value falls within the ranges of possible credit card numbers for most leading credit card issuers. Does not check whether the number is genuine.

Date

Checks not only that a date is in the ‘YYYY-MM-DD format, but also that it’s a valid date. For example, ‘2010-2-30’ fails because it’s not a real date, although it’s in the right format.

Digits

Accepts only digits. The decimal point and thousands separator are rejected.

EmailAddress

Validates an email address. Has the option to check whether the hostname actually accepts email, but this slows down performance. On Windows, this option requires PHP 5.3 or later.

Float

Accepts a floating point number. The maximum value is platform-dependent.

GreaterThan

Checks that a value is greater than a minimum. Constructor takes a single argument to set the minimum value.

Identical

Checks that a value is identical to the value passed as an argument to the constructor.

Int

Accepts an integer.

LessThan

Checks that a value is less than a maximum. Constructor takes a single argument to set the maximum value.

NotEmpty

Checks that a value is not empty. Various options can be set to configure what is regarded as an empty value, offering greater flexibility than the PHP empty() function.

PostCode

Checks that a value conforms to the pattern for a postal or zip code. The pattern is determined by passing a locale string to the constructor, for example, ‘en_US’ for the United States or ‘en_GB’ for the UK.

Regex

Validates against a regular expression passed as an argument to the constructor.

StringLength

Checks the length of a string. The constructor accepts one, two, or three arguments. The first sets the minimum length, the second optionally sets the maximum length, and the third optionally specifies the encoding. Alternatively, these values can be presented as an associative array using the keys ‘min’, ‘max’, and ‘encoding’.

Chaining validators to set multiple criteria

To test for more than one criterion, create a generic Zend_Validate object, and use its addValidator() method to add each new test. You can instantiate each validator separately, and then pass it as an argument to addValidator() like this:

$val = new Zend_Validate();
$val1 = new Zend_Validate_StringLength(8, 15);
$val2 = new Zend_Validate_Alnum();
$val->addValidator($val1);
$val->addValidator($val2);

However, it’s simpler to instantiate each validator directly as the argument to addValidator() like this:

$val = new Zend_Validate();
$val->addValidator(new Zend_Validate_StringLength(8, 15));
$val->addValidator(new Zend_Validate_Alnum());

You can even chain the addValidator() methods one after the other like this:

$val = new Zend_Validate();
$val->addValidator(new Zend_Validate_StringLength(8, 15))
    ->addValidator(new Zend_Validate_Alnum());

Notice that there is no semicolon at the end of the second line, and the second -> operator isn’t prefixed by the $val object. Indenting it like this makes the code easier to read, but you could place it immediately after the closing parenthesis at the end of the first addValidator() method.

All three sets of code perform the same task: $val tests for a string 8–15 characters long that contains only letters and numbers, with no spaces.

Armed with this knowledge, you can validate the input of the registration form.

Building the validation script (1)

The user registration form from Lesson 6 has been modified to add a text input field for the email address and some hints for the user. The style sheet has also been changed to make room for error messages.

  1. Copy add_user.php from lesson07/start to lesson07/workfiles.
  2. It’s more efficient to use an external file for the validation code so you can reuse the code for other projects. Choose File > New, and create a new PHP page. Save it as user_registration.php in lesson07/workfiles/scripts.
  3. In the file you just created, switch to Code view, delete the HTML code inserted by Dreamweaver, and add an opening PHP tag at the top of the page. This page will contain only PHP, so it shouldn’t have a closing PHP tag.
  4. After the opening PHP tag, initialize an array to store error messages:
  5. $errors = array();
    
  6. When the form is first loaded, there’s nothing to process, so the $_POST array is empty. An empty array is treated as FALSE (see “What PHP regards as false” in Lesson 3), so you can use this to ensure that the validation script is run only when the form is submitted. Add a conditional statement like this:
  7. if ($_POST) {
      // run the validation script
    }
    
  8. The validation script needs access to the ZF files. Include library.php by adding it between the curly braces of the conditional statement:
  9. require_once(‘library.php’);
    
  10. Add try and catch blocks inside the conditional statement created in the previous step:
  11. if ($_POST) {
      // run the validation script
      require_once(‘library.php’);
      try {
        // main script goes here
      } catch (Exception $e) {
        echo $e->getMessage();
      }
    }
    
  12. The first input field you need to validate is first_name. Personal names are alphabetic, so Zend_Validate_Alpha seems like a good choice. Add the following code inside the try block:
  13. try {
      // main script goes here
      $val = new Zend_Validate_Alpha(TRUE);
      if (!$val->isValid($_POST['first_name'])) {
        $errors['first_name'] = 'Required field, no numbers';
      }
    } catch (Exception $e) {
    

    By passing TRUE as an argument, this permits spaces.

  14. Before going any further, it’s a good idea to test the script so far. Save user_registration.php, and switch to add_user.php.
  15. Include user_registration.php by inserting space above the DOCTYPE declaration and adding the following code:

    <?php
    require_once(‘scripts/user_registration.php’);
    ?>
    
  16. To display error messages next to each input field, you need to add a pair of <span> tags with a PHP conditional statement in between.
  17. Locate the following line in Code view:

    <input type=”text” name=”first_name” id=”first_name” />
    
  18. Add the following code after the <input> tag:
  19.   <input type=”text” name=”first_name” id=”first_name” />
      <span>
      <?php
      if ($_POST && isset($errors['first_name'])) {
        echo $errors['first_name'];
      }
      ?>
      </span>
    </p>
    

    The conditional statement begins by checking $_POST. If the form has been submitted, it equates to TRUE, so the next test is applied. The isset() function checks the existence of a variable. $errors[‘first_name’] is created only if the validation test fails, so $errors[‘first_name’] is displayed if the form has been submitted and the first_name field failed validation.

    The <span> tags remain empty if there isn’t an error, so it might seem more logical to include them inside the conditional statement. They have been left outside to act as a hook for a custom server behavior that you’ll create later in this lesson to insert error messages for the other fields.

  20. Save add_user.php, and click Live View or press F12/Opt+F12 to test it. Start by leaving the “First name” field blank. Submit the form, and remember to hold down the Ctrl/Cmd key if you’re in Live View. If all your code is OK, you should see an error message next to the “First name” field.
  21. Now try typing your own name and resubmitting the form. The error message disappears.
  22. Type some numbers and resubmit. The error message reappears.
  23. Click inside the field, and press the spacebar several times before resubmitting the form. The error message disappears. You still have the problem of an empty field.
  24. Unfortunately, the NotEmpty validation class doesn’t have an option to handle this. Also, personal names sometimes include a hyphen or apostrophe. The best solution is to use a regular expression—a pattern for matching text.

  25. Turn off Live View, if necessary, and switch back to user_registration.php. Change the validator from Alpha to Regex like this:
  26. $val = new Zend_Validate_Regex('/^[a-z]+[-\'a-z ]+$/i');
    

    This regular expression—or regex, for short—makes sure the value begins with at least one letter and is followed by at least one more letter, hyphen, apostrophe, or space.

    This is fine for English. If you need to accept accented letters or names written in a different script, such as Japanese or Chinese, use the following:

    $val = new Zend_Validate_Regex('/^\p{L}+[-\'\p{L} ]+$/u');
    

    This line performs the same task, but also accepts Unicode letter characters.

  27. Save user_registration.php, and test the “First name” field again. It now accepts names with spaces, hyphens, and apostrophes but rejects numbers and values that don’t begin with a letter.
  28. The second regex accepts names like Françoise, Дмитрий,and β.

    You can compare your code with lesson07/completed/add_user01.php and lesson07/completed/scripts/user_registration01.php.

Building the validation script (2)

The rest of the script follows a similar pattern: You need a validator for each input field and need to add a message to the $errors array if the value fails the test. Sometimes a validator can be reused, but if it’s no longer appropriate, you can overwrite it by assigning a new one to the same variable.

  1. The surname input field can use the same validator as first_name, so add the following code immediately after the first_name test in user_registration.php:
  2.   if (!$val->isValid($_POST[‘first_name’])) {
        $errors[‘first_name’] = ‘Required field, no numbers’;
      }
      if (!$val->isValid($_POST['surname'])) {
        $errors['surname'] = 'Required field, no numbers';
      }
    } catch (Exception $e) {
    
  3. The next input field to validate is username. A username should consist of letters and numbers only, and should be 6–15 characters long. This requires two tests, so you need to create a Zend_Validate object, and chain the validators. You don’t need the existing validator, so you can overwrite it.
  4. Add the following code immediately after the code you entered in the previous step (and still inside the try block):

    $val = new Zend_Validate();
    $length = new Zend_Validate_StringLength(6,15);
    $val->addValidator($length);
    $val->addValidator(new Zend_Validate_Alnum());
    if (!$val->isValid($_POST[‘username’])) {
      $errors[‘username’] = ‘Use 6-15 letters or numbers only’;
    }
    

    This starts by creating a generic Zend_Validate object ready for chaining. Next, a StringLength validator—with a minimum of 6 characters and a maximum of 15—is created and assigned to $length.

    In the third line, the addValidator() method chains $length to $val.

    Then the Alnum validator is chained to it. By not passing an argument to Alnum, no whitespaces are allowed.

    Why use different ways of chaining the validators? Surely the StringLength validator could have been passed directly as an argument to addValidator() in the same way as Alnum, right? It could, but the password field needs to be a minimum of 8 characters. Assigning the StringLength validator to its own variable lets you change the minimum value ready for reuse.

  5. On the line immediately after the code you just inserted, type $length->. As soon as you type the -> operator, Dreamweaver code hints display the methods available to a StringLength validator. Type s. The code hints display a number of methods that begin with “set.”
  6. Use your arrow keys to scroll down to setMin($min) and press Enter/Return or double-click. Set the value to 8, and type a closing parenthesis and semicolon. The finished line should look like this:
  7. $length->setMin(8);
    

    This resets the minimum number of characters required by the StringLength validator to 8. The maximum remains unchanged at 15.

  8. Now that you have changed the minimum required by the StringLength validator, you can create the validation test for the password input field. It’s almost exactly the same as for username. Add this immediately after the line you just entered:
  9. $val = new Zend_Validate();
    $val->addValidator($length);
    $val->addValidator(new Zend_Validate_Alnum());
    if (!$val->isValid($_POST[‘password’])) {
      $errors[‘username’] = ‘Use 8-15 letters or numbers only’;
    }
    

    This allows any combination of letters and numbers. For a more robust password, use the Regex validator. There’s a regex for a strong password that requires a mixture of uppercase and lowercase characters and numbers at http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=297. In the comments on the same page, there’s an even stronger one that requires at least one special character.

  10. To check that both password values are identical, use the Identical validator. This code goes immediately after the code in the preceding step:
  11. $val = new Zend_Validate_Identical($_POST[‘password’]);
    if (!$val->isValid($_POST[‘conf_password’])) {
      $errors[‘conf_password’] = “Passwords don’t match”;
    }
    

    You want to check that $_POST[‘conf_password’] is identical to $_POST[‘password’], so $_POST[‘password’] is passed as the argument to the Identical validator.

  12. The final test is for the email. Add this after code in the previous step:
  13. $val = new Zend_Validate_EmailAddress();
    if (!$val->isValid($_POST[‘email’])) {
      $errors[‘email’] = ‘Use a valid email address’;
    }
    
  14. Save user_registration.php. You’ll come back to it later to add the code that inserts the user’s details in the database. If you want to check your code so far, compare it with lesson07/completed/scripts/user_registration02.php.

Preserving input when validation fails

Ever submitted an online form only to be told there’s an error, and all your input has been wiped out? Not much fun is it? Good validation and form design preserves the user’s input if there’s an error. It’s quite simple to do. The input is stored in the $_POST or $_GET array, depending on the method used to submit the form (most of the time, it’s POST). All that’s necessary is to assign the appropriate element of the $_POST or $_GET array to the value attribute of the input field.

  1. In add_user.php, locate the first <input> tag in Code view. It looks like this:
  2. <input type=”text” name=”first_name” id=”first_name” />
    
  3. The <input> tag doesn’t have a value attribute, so you need to add one, and use PHP to assign its content like this:
  4. <input type=”text” name=”first_name” id=”first_name”
    value="<?php if ($_POST && $errors) {
      echo htmlentities($_POST['first_name'], ENT_COMPAT, 'UTF-8');
    }?>" />
    

    The PHP code block inside the quotation marks of the value attribute is controlled by a conditional statement that checks the $_POST and $errors arrays. The $_POST array is empty unless a form has been submitted, so the code inside the curly braces is ignored when the page first loads.

    The $errors array is declared at the top of user_registration.php, so it always exists, but elements get added to it only if the validation script finds any problems with the user input. Consequently, $errors will equate to TRUE only if the form has been submitted and at least one error has been found.

    If both tests equate to TRUE, the code passes $_POST[‘first_name’] to a function called htmlentities() and uses echo to display the result. You could use echo on its own, but displaying raw user input in a web page is a security risk. The htmlentities() function sanitizes the input by converting characters that have a special meaning in HTML into HTML entities. For example, the angle brackets in <script> are converted to &lt; and &gt;, which prevent an unauthorized script from running in your web page.

    Often, htmlentities() takes just one argument—the string you want to convert. The second and third arguments have been added because htmlentities() uses Latin1 (iso-8859-1) as its default encoding. ENT_COMPAT is a constant that specifies preserving single quotation marks and converting only double ones. The third argument specifies UTF-8 (Unicode) as the encoding. This preserves accented or nonalphabetic characters.

  5. Save add_user.php, type anything in the “First name” field, and submit the form. The validation script detects errors in the other fields, so $_POST and $errors are no longer empty arrays. The value you entered is redisplayed.
  6. You can compare your code with lesson07/completed/add_user02.php.

Dealing with unwanted backslashes

In add_user.php, test the “First name” field with a name that contains an apostrophe, such as O’Toole. If magic quotes (see Lesson 2) are turned on, the apostrophe is preceded by a backslash like this when it’s redisplayed:

If you see a backslash in front of the apostrophe, add the following code after the catch block at the bottom of scripts/library.php (you can copy and paste it from lesson07/completed/scripts/library_magic_quotes.php):

if (get_magic_quotes_gpc()) {
  $process = array(&$_GET, &$_POST, &$_COOKIE);
  while (list($key, $val) = each($process)) {
    foreach ($val as $k => $v) {
      unset($process[$key][$k]);
      if (is_array($v)) {
        $process[$key][stripslashes($k)] = $v;
        $process[] = &$process[$key][stripslashes($k)];
      } else {
        $process[$key][stripslashes($k)] = stripslashes($v);
      }
    }
  }
  unset($process);
}

When you test the page again, the backslash should have been removed.

Creating your own server behaviors

Now you need to fix the redisplay of user input and error messages for the remaining input fields. Doing it all by hand is tedious. But take a look at what’s happened in add_user.php. In Design view, Dreamweaver has added what looks like a dynamic text object in the “First name” input field. The Server Behaviors panel also lists a Dynamic Attribute.

Does this mean Dreamweaver has a server behavior that you can you use here? No, but it does have the Server Behavior Builder, which lets you create your own.

  1. With add_user.php open in the Document window, open the Server Behaviors panel by clicking its tab or choosing Window > Server Behaviors (Ctrl+F9/Cmd+F9). Click the plus button at the top left of the panel, and choose New Server Behavior.
  2. In the New Server Behavior dialog box, make sure “Document type” is set to PHP MySQL, and type Redisplay on Error in the Name field. Leave the checkbox deselected, and click OK.
  3. This opens a large dialog box where you define the new server behavior.

  4. Click the plus button labeled “Code blocks to insert” to open the Create a New Code Block dialog box. Accept the name suggested by Dreamweaver, and click OK. This lists the code block in the top pane and inserts some placeholder text in the “Code block” section.
  5. Replace the placeholder text with the PHP code that you added to the <input> tag’s value attribute in step 2 of “Preserving input when validation fails.” Here it is again:
  6. <?php if ($_POST && $errors) {
      echo htmlentities($_POST[‘first_name’], ENT_COMPAT, ‘UTF-8’);
    }?>
    
  7. If you leave the code like this, the server behavior would always use $_POST[‘first_name’]. To make it editable, you need to replace first_name with a parameter.
  8. Delete first_name but not the surrounding quotation marks. Make sure your insertion point is between the quotation marks of $_POST[‘’], and click the Insert Parameter in Code Block button.

  9. In the “Parameter name” field, type Field Name, and click OK. The code in the “Code block” section should now look like this:
  10. <?php if ($_POST && $errors) {
      echo htmlentities($_POST[‘@@Field Name@@'], ENT_COMPAT, 'UTF-8');
    }?>
    

    The @@ surrounding the parameter name tell Dreamweaver to replace the value when you use the server behavior.

  11. You now need to tell the server behavior where to insert the code when you use it. You want to use it in the value attribute of an <input> tag.
  12. Set “Insert code” to Relative to a Specific Tag. An option called Tag appears.

  13. Select “input” from the Tag menu.
  14. Set “Relative position” to “As the Value of an Attribute.” This opens up yet another option labeled Attribute.
  15. Select “value” from the Attribute menu. The settings in the Server Behavior Builder dialog box should now look like this:
  16. Click Next at the top right of the dialog box to open the dialog box that defines the options that will appear in the new server behavior’s dialog box.
  17. The values suggested by Dreamweaver are fine. Just click OK to complete the creation of the Redisplay on Error server behavior.
  18. Create another custom server behavior to display the error messages. The process is the same, so the following instructions provide only the main points.
  19. Click the plus button in the Server Behaviors panel, and choose New Server Behavior. Call it Display Error Message.

  20. Create a new code block, and accept the default name. Replace the placeholder text with the following code:
  21. <?php if ($_POST && isset($errors[‘@@Field@@’])) {
      echo $errors[‘@@Field@@’];
    } ?>
    

    This is the same as the code used to display the error message next to the “First name” field except both instances of first_name have been replaced by the parameter @@Field@@.

  22. The error message is displayed inside a <span> tag, so use the following settings at the bottom of the panel:
  23. Click Next, accept the default settings in the next dialog box, and click OK.

Finishing the registration form

The custom server behaviors make it easy to preserve user input and display error messages. As long as you use an array called $errors to store error messages, they can be used in any page, not just this one.

  1. With add_user.php open in Design view, select the “Family name” field in the registration form.
  2. Click the plus button in the Server Behaviors panel, and choose “Redisplay on Error” to open the dialog box for your new server behavior.
  3. Type surname in the Field Name field. The correct value should already be selected for “input tag.”
  4. Click OK. Dreamweaver inserts a dynamic text object in the field. It also lists “Redisplay on Error” in the Server Behaviors panel. Dreamweaver should also recognize the code in the “First name” field and list that as a “Redisplay on Error” server behavior.
  5. Repeat steps 1–4 with the Username and “Email address” fields, typing username and email respectively in Field Name.
  6. You can’t apply the server behavior to the password fields. There’s no point in doing so anyway, because password fields don’t display user input.
  7. Save add_user.php, and test the new server behavior by typing values in all fields except the password fields and submitting the form. The lack of passwords causes validation to fail and redisplays the values you entered.
  8. The error messages are displayed in <span> tags. In Design view, position the insertion point immediately to the right of the “Family name” input field, right-click, and choose Insert HTML.
  9. This displays a small window for you to add an HTML tag. Type sp to highlight span, and press Enter/Return to insert an empty pair of <span> tags.

  10. Open Split view to make sure the insertion point is between the opening and closing <span> tags. If it isn’t, move it there by clicking between them.
  11. Click the plus button in the Server Behaviors panel, and choose Display Error Message to open the dialog box for the other new server behavior.
  12. Type surname in Field. The value of “span tag” should be “span [1].” The span doesn’t have an id attribute, so Dreamweaver uses an array to identify it, counting from zero. So, this is the second <span> in the page.
  13. Repeat steps 8–11 for the remaining input fields. As long as the insertion point is between the opening and closing <span> tags, the Display Error Message dialog box selects the correct value for “span tag.” The value you type in Field should match the name attribute of each input field, namely: username, password, conf_password, and email.
  14. Save add_user.php, and test the page by typing a few letters in the “Confirm password” field only. When you submit the form, confirm that error messages are displayed next to each field.
  15. This completes add_user.php. Compare your code with lesson07/completed/add_user.php if the page doesn’t work as expected.

Using a variable in a SELECT query

The final part of the form input that needs to be validated before you can register the user in the database is the username. At the beginning of this lesson, you added a unique index to the username column, so the database rejects any attempt to insert the same value more than once. To avoid this, you need to check the database and display an error message if the username already exists.

Zend_Db offers several different ways of querying a database. In this case, the simplest way is to write your own SQL query. All you’re interested in is whether the username exists in the database, so selecting user_id is sufficient. To find the user_id for the username “hitchhiker,” the basic SQL query looks like this:

SELECT user_id FROM users WHERE username = ‘hitchhiker’

SQL is designed to emulate human language, so the meaning should be obvious. By convention, the SQL commands are written in uppercase. The names of columns and tables are not enclosed in quotation marks, but string values are. So, this selects the values in the user_id column from the users table, where the value in the username column equals “hitchhiker.”

For this validation script, you want to match the value that comes from $_POST[‘username’], but you have no idea what this contains. It could be an attempt to hack into your database. So, you need to sanitize the value that’s inserted into the SQL. One way of doing this with Zend_Db is to use the quoteInto() method, which takes two arguments:

  • The first argument is the SQL statement with a question mark used as a placeholder for the variable containing the user input.
  • The second argument is the variable you want to use.

This sanitizes the value and wraps it in the necessary quotation marks.

To execute a SELECT query, you pass the SQL query to the fetchAll() method, which returns an array containing all the results.

Checking for duplicate usernames

Now that you know the basics of querying the database, you can fix the validation script so that it checks for a duplicate username.

  1. The section in user_registration.php that validates the length and characters in the username ends with the following conditional statement (it should be around lines 19–21):
  2. if (!$val->isValid($_POST[‘username’])) {
      $errors[‘username’] = ‘Use 6-15 letters or numbers only’;
    }
    

    If the username fails this validation test, there’s no need to check the database; if the username passes, you need to make sure it’s not a duplicate. This is an either/or situation, so it calls for an else block to be added to the original conditional statement like this:

    if (!$val->isValid($_POST[‘username’])) {
      $errors[‘username’] = ‘Use 6-15 letters or numbers only’;
    } else {
      // check the database for duplicate username
    }
    

    All the code in the following steps goes inside the else block.

  3. Use the quoteInto() method to build the SQL statement with a question mark placeholder, and pass the variable to it as the second argument:
  4. $sql = $dbRead->quoteInto(‘SELECT user_id FROM users WHERE username = ?’,
    Â$_POST[‘username’]);
    

    $dbRead is one of the Zend_Db objects you created in library.php earlier in this lesson to connect to the database.

  5. Execute the query by passing $sql to fetchAll(), and capture the result like this:
  6. $result = $dbRead->fetchAll($sql);
    
  7. The fetchAll() method returns an array of results. PHP treats an array that contains any elements as TRUE. If a match is found, the username is a duplicate, so you can create an error message like this:
  8. if ($result) {
      $errors[‘username’] = $_POST[‘username’] . ‘ is already in use’;
    }
    

    If no match is found, $result is empty, which PHP treats as FALSE, so the code inside the braces is ignored.

  9. Save user_registration.php, and test add_user.php by entering a username that already exists in the database. If your database table is empty, you can add a record by selecting the users table in phpMyAdmin and clicking the Insert tab at the top of the page. When you submit the form with a duplicate username, you’ll see the error message.

You can compare your code with lesson07/completed/scripts/user_registration03.php.

Inserting the user details with Zend_Db

Inserting a record in a database with Zend_Db is very easy. You don’t need any SQL. Just create an associative array using the column names as the keys, and pass the table name and array as arguments to the insert() method.

You can now finish the user registration process:

  1. If the user input passes all the validation tests, the $errors array is empty. You can use this to control whether to insert the data in the users table. Add this conditional statement at the end of the validation sequence, just above the catch block:
  2.   if (!$val->isValid($_POST[‘email’])) {
        $errors[‘email’] = ‘Use a valid email address’;
      }
      if (!$errors) {
        // insert the details in the database
      }
    } catch (Exception $e) {
    

    PHP implicitly treats an empty array as FALSE. The logical Not operator (an exclamation point) reverses a Boolean value, so this effectively means “if there are no errors.” If the $errors array is empty, the condition equates to TRUE, and the details will be inserted into the database.

    All the remaining code goes inside this conditional statement.

  3. Create an associative array of the column names and values. You don’t need an array element for user_id, because MySQL automatically inserts the next available number for the primary key. The array should look like this:
  4. $data = array(‘first_name’  => $_POST[‘first_name’],
                  ‘family_name’ => $_POST[‘surname’],
                  ‘username’    => $_POST[‘username’],
                  ‘password’    => sha1($_POST[‘password’]));
    

    Zend_Db automatically sanitizes the values before inserting them into the database. The password is the only value that receives special treatment. It’s passed to the sha1() function for encryption.

  5. To insert the data, you need to use the Zend_Db object with read/write privileges. Add this line after the array you have just created:
  6. $dbWrite->insert(‘users’, $data);
    

    The first argument to the insert() method is a string containing the name of the table you want to use, and the second argument is the data array.

  7. That’s all there is to inserting the data. After the data is inserted, redirect the user to the login page with the header() function like this:
  8. header(‘Location: login.php’);
    
  9. Save user_registration.php, and copy lesson07/start/login.php to your lesson07/workfiles folder.
  10. Test add_user.php to enter a new user in the database. If there are no errors, you will be taken to login.php. You can compare your code with lesson07/completed/scripts/user_registration.php.

Peachpit Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from Peachpit and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about Peachpit products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites; develop new products and services; conduct educational research; and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email ask@peachpit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by Adobe Press. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.peachpit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020