Web Programming for Business - PHP Object-Oriented Programming with Oracle

4 Input Validation and Report Generation

Overview

Any form of data entering a website is potentially harmful. Input validation is therefore very important. Two techniques for input validation are presented. First, Perl Compatible Regular Expressions (PCRE) are introduced, explained, and implemented as a means to verify input form data. PCRE closely resembles Perl in terms of pattern-matching functionality. Second, PHP includes a set of built-in filter functions for input validation. The chapter concludes by introducing two small applications for safe report generation.

Learning Objectives

After completing this chapter, you will gain a fundamental understanding of input validation to mitigate potentially harmful form data from entering your website. You will also learn how to create safe report generation using drop-down menus. Understanding is enhanced through explanation and code examples. The following objectives summarize the skills the chapter will help you develop:

1.    Learn the definition of a regular expression.

2.    Learn the definition of PCRE.

3.    Learn how to use PCRE to validate form data.

4.    Learn how to build a PCRE form data validation application.

5.    Learn how to use PHP built-in validation functions.

6.    Learn how to build an application with PCRE and built-in functions.

7.    Learn the advantages and disadvantages of PCRE and built-in functions.

8.    Learn how to build a simple report generation application.

9.    Learn how to build a sophisticated report generation application.

10.Learn how to build a report generation application with PL/SQL cursors.

Regular Expressions

regular expression is a pattern describing a certain amount of text; abbreviated as ‘regex’. The idea is to test for a match between the regex and the form data input. A match is the piece of text that the pattern was found to correspond to by the regex processing software.

PCRE

Perl Compatible Regular Expressions (PCRE) is a set of PHP functions that implement regular expression pattern-matching using the same syntax and semantics as Perl 5. PCRE uses special symbols to facilitate pattern-matching. PCRE allows many special symbols, but I cover the ones that I use most often to enable form input validation.

To make it easier, I start by introducing a small set of special characters and then provide a corresponding example to facilitate learning. I continue by introducing another set of special characters and a corresponding example, and so on. After the symbols are introduced, I provide examples of regexes that I use for form input validation. I end the ‘regex’ sections with an application that validates form data.

Special Symbols

The ‘^’ indicates the start of a string. The ‘$’ indicates the end of a string. The ‘?’ indicates that the previous token is optional. A token is a tangible way to represent a piece of a regex. The ‘|’ is a Boolean OR. Braces ‘{}’ are used to match the number of occurrences. Parentheses ‘()’ are used to quantify a sequence of characters. Delimiters are used to mark the beginning and end of a pattern. A delimiter can be any nonalphanumeric, nonbackslash, nonwhitespace character.

I use the ‘#’ symbol as a delimiter because it is then easier for me to discern the regex. Often-used delimiters are ‘/’, ‘#’, and ‘~’. PCRE can quickly become complex, so let’s look at an example.

PHP file ‘pcre1.php’ includes five regexes (lines 4–8) that match against the string ‘hello world’ (line 3). The ‘preg_match()’ function (lines 9, 12, 15, 18, and 21) is used to perform a regex match. Each regex is compared to ‘$string’. The results are displayed as ‘Match’ or ‘No Match’.

 1 <?php

 2 // File pcre1.php

 3 $string = 'hello world';

 4 $regex1 = '#^hello#';

 5 $regex2 = '#world$#';

 6 $regex3 = '#^(he(l{2})o)#';

 7 $regex4 = '#(hello)|(world)#';

 8 $regex5 = '#^hello world$#';

 9 if(preg_match($regex1,$string))

10 { echo "Match<br />"; }

11 else { echo "No Match<br />"; }

12 if(preg_match($regex2,$string))

13 { echo "Match<br />"; }

14 else { echo "No Match<br />"; }

15 if(preg_match($regex3,$string))

16 { echo "Match<br />"; }

17 else { echo "No Match<br />"; }

18 if(preg_match($regex4,$string))

19 { echo "Match<br />"; }

20 else { echo "No Match<br />"; }

21 if(preg_match($regex5,$string))

22 { echo "Match"; }

23 else { echo "No Match"; }

24 ?>

Figure 4.1

Figure 4.1 Display of Matching ‘hello world’ with Regexes

Figure 4.1 illustrates that each regex matched string ‘hello world’. ‘$regex1’ (line 4) matches a string that starts with ‘hello’. ‘$regex2’ (line 5) matches a string that ends with ‘world’. ‘$regex3’ (line 6) matches a string that begins with ‘he’, followed by exactly two ‘l’s, and ends with ‘o’. ‘$regex4’ (line 7) matches either ‘hello’ or ‘world’. ‘$regex5’ (line 8) matches a string that begins with ‘hello’ and ends with ‘world’.

To include special symbols as part of a regex, escape them with a backslash ‘\’. So, to be taken literally, special characters ‘!@#$%^&*()_-+=:;"'<>,.?/’ must be escaped because they have special meaning. You must escape the backslash character itself if you need to use it as part of a regex.

Let’s look at another example that uses two additional special symbols – ‘[]’ and ‘\s’. Brackets ‘[]’ contain characters allowed in a single position of a string. The ‘\s’ indicates a space. PHP file ‘pcre2.php’ illustrates how these symbols can be used as part of a regex.

 1 <?php

 2 // File pcre2.php

 3 $string1 = 'My phone is: ';

 4 $string2 = '(290)444-3333';

 5 $string3 = 'My phone is: (290)444-3333';

 6 $regex1 = '#^[a-zA-Z]{2}\s[a-z]{5}\s[a-z]{2}\:\s#';

 7 $regex2 = '#^\([0-9]{3}\)[0-9]{3}\-[0-9]{4}#';

 8 $regex3 = '#^[a-zA-Z]{2}\s[a-z]{5}\s[a-z]{2}\:\s\([0-9]{3}\)[0-9]{3}\-[0-9]{4}#';

 9 if(preg_match($regex1,$string1))

10 { echo "Match<br />"; }

11 else { echo "No Match<br />"; }

12 if(preg_match($regex2,$string2))

13 { echo "Match<br />"; }

14 else { echo "No Match<br />"; }

15 if(preg_match($regex3,$string3))

16 { echo "Match<br />"; }

17 else { echo "No Match<br />"; }

18 ?>

Figure 4.2

Figure 4.2 Display of Matching Various Strings with Regexes

Figure 4.2 illustrates that each regex (lines 6–8) matched its corresponding string (lines 3–5). ‘$regex1’ (line 6) matches a string that starts with exactly two letters that can be upper- or lowercase, continues with exactly one space, followed by exactly five lowercase letters, then exactly one space, followed by exactly two lowercase letters, a single colon (notice that the colon was escaped with a backslash), and ends with exactly one space. ‘$regex2’ (line 7) matches a string that starts with a single left parenthesis, continues with exactly three digits between zero and nine, followed by a single right parenthesis, then exactly three digits from zero to nine, followed by a dash, and ends with exactly four digits between zero and nine. ‘$regex3’ (combines regexes from ‘$regex1’ and ‘$regex2’) (line 8) matches the full string (‘$string3’ combines ‘$string1’ and ‘$string2’).

In ‘pcre2.php’, you learned how to validate a US phone number with area code. If you want to validate a phone number with an optional area code, the regex must be modified.

PHP file ‘pcre3.php’ validates a phone number with or without an area code. I leave out validation of text to concentrate on phone number validation with optional area code.

 1 <?php

 2 // File pcre3.php

 3 $string1 = '(801)787-1234';

 4 $string2 = '787-1234';

 5 $regex = '#^\(?([0-9]{3})?\)?[0-9]{3}\-[0-9]{4}$#';

 6 if(preg_match($regex,$string1))

 7 { echo "Match<br />"; }

 8 else { echo "No Match<br />"; }

 9 if(preg_match($regex,$string2))

10 { echo "Match<br />"; }

11 else { echo "No Match<br />"; }

12 ?>

Figure 4.3

Figure 4.3 Display of Matching Phone Numbers with Regex

‘$regex’ (line 5) matches a pattern that optionally begins with an area code in parentheses, continues with three digits followed by the ‘-’ symbol, and ends with four digits. Notice the use of the ‘?’ symbol to indicate an optional pattern. At the beginning of the regex, the ‘(’ symbol is optional, the next three digits are optional, and the closing ‘)’ is optional (the ‘?’ symbol identifies the previous token as optional). I added a ‘$’ symbol at the end of the regex to ensure that the patterns ends with four digits. If you adopt this regex, you must inform the user to use the proper format.

An easier way to force phone number format is to build an input form that accepts up to ten digits and then use PHP to cleanse the data. The following example shows you how to build a simple form and validate the data. The example uses two files – one for the HTML form, ‘phone.php’, and another for processing form data, ‘verify_phone.php’.

PHP file ‘phone.php’ includes an HTML data form (lines 14–22) that accepts a phone number of up to ten digits by including the ‘maxlength’ attribute inside the ‘input’ tag (line 17). It also includes a submit button (line 19) that sends user input to PHP file ‘verify_phone.php’ for processing and a reset button (line 20) to clear the contents of the text box.

 1 <?php

 2 // File phone.php

 3 ?>

 4 <html><head>

 5 <style type="text/css">

 6 table.center {

 7 margin-left:auto;

 8 margin-right:auto;

 9 }

10 </style></head>

11 <body style="background-color:burlywood;">

12 <div style="text-align:center;">

13 <h1 style="color:indigo;">Enter Phone Number</h1>

14 <form method="post" action="verify_phone.php">

15 <table class="center">

16 <tr><td>Phone:</td>

17 <td><input type="text" name="phone" maxlength="10"></td>

18 </tr>

19 <td><input type="submit" value="Enter"></td>

20 <td><input type="reset" value="Reset"></td>

21 </tr>

22 </table></form></div>

23 </html>

PHP file ‘verify_phone.php’ checks the length of the phone number (line 4). Based on length, the phone number is translated into a phone number without area code (lines 6 and 7) or with area code (lines 12–14) or an error message is generated (lines 17 and 18).

 1 <?php

 2 // File verify_phone.php

 3 $phone = $_POST["phone"];

 4 if(strlen($phone)==7)

 5 {

 6 $phone = substr($phone,0,3) .

 7 '-' . substr($phone,3,4);

 8 echo 'Translated phone number is: ' . $phone;

 9 }

10 elseif(strlen($phone)==10)

11 {

12 $p1 = '(' . substr($phone,0,3) . ')' .

13 substr($phone,3,3);

14 $phone = $p1 . '-' . substr($phone,6,4);

15 echo 'Translated phone number is: ' . $phone;

16 }

17 else { echo 'Enter a valid phone number

18 of 7 or 10 digits'; }

19 ?>

Let’s try a seven-digit number and see what happens. Load ‘phone.php’ into a browser. Figure 4.4 shows the seven-digit number before submission of the form. Be sure to enter only digits without a ‘-’. Figure 4.5 shows what happens after submission of the seven-digit number.

Figure 4.4

Figure 4.4 Display of Form with Valid Seven-Digit String

Figure 4.5

Figure 4.5 Display of Translated Seven-Digit String

Let’s try a ten-digit number and see what happens. Figure 4.6 shows the ten-digit number before submission of the form. Be sure to enter only digits. Figure 4.7 shows what happens after submission of the ten-digit number.

Figure 4.6

Figure 4.6 Display of Form with Valid Ten-Digit String

Figure 4.7

Figure 4.7 Display of Translated Ten-Digit String

Let’s try a four-digit number and see what happens. Figure 4.8 shows the four-digit number before submission of the form. Figure 4.9 shows what happens after submission of the four-digit number.

Figure 4.8

Figure 4.8 Display of Form with Invalid Four-Digit String

Figure 4.9

Figure 4.9 Display of Message with Acceptable Strings

The seven- and ten-digit numbers were processed properly by the ‘verify_phone.php’ file. The four-digit number generated an error message.

However, if the user inputs data other than numbers, trouble ensues. A solution is to create a regex that works with the form. Since the form accepts data up to a maximum length of ten, the regex can be simplified to search for numbers only.

Object-Oriented Validation

To encapsulate the phone regex, I create a class with a method. I can then create an instance of the class to validate phone numbers. I always test a new class before using it in an application. The next example tests the new class.

The example uses two PHP files – ‘validate_regex.php’ and ‘call_validate_regex.php’. PHP file ‘validate_regex.php’ holds the class. PHP file ‘call_validate_regex.php’ tests the class.

 1 <?php

 2 // File validate_regex.php

 3 class validate_regex

 4 {

 5 function get_valid_phone($phone)

 6 {

 7 $regex = '#^[0-9]{7}$|^[0-9]{10}$#';

 8 return preg_match($regex,trim($phone));

 9 }

10 }

11 ?>

Class ‘validate_regex’ (lines 3–10) includes the get_valid_phone()’ method (lines 5–9) that returns true if the number is either seven or ten digits and returns false otherwise. Specifically, the regex (line 7) checks if the input is a seven- or ten-digit number. Notice the use of the ‘^’ symbol at the beginning of the regex and the ‘$’ symbol at the end of the regex to ensure compliance. Also notice the ‘preg_match()’ function (line 8), which performs a regex match. The function matches the regex (line 7) with the (trimmed) phone number coming in as a parameter to the method (line 5).

 1 <?php

 2 // File call_validate_regex.php

 3 require_once 'validate_regex.php';

 4 $phones = array(

 5 '1234567', 'abc', '123', '8017972456',

 6 '7972456', '1m2j4k1', '88822234',

 7 '6186872121', '1112223334444',

 8 );

 9 $validate = new validate_regex();

10 foreach($phones as $phone)

11 {

12 if($validate->get_valid_phone($phone))

13 { echo "Valid Phone: " . $phone; }

14 else

15 { echo "Invalid Phone: " . $phone; }

16 echo "<br />";

17 }

18 ?>

PHP file ‘call_validate_regex.php’ instantiates the class (line 9) and validates a set of phone numbers. Using array ‘$phones’ (lines 4–8) to hold a set of phone numbers is convenient because a ‘foreach’ loop (lines 10–17) can then be used to easily traverse the array to test each phone number for compliance with the ‘get_valid_phone()’ method (line 12).

Load ‘call_validate_regex.php’ into a browser. Figure 4.10 shows the results. Strings of seven or ten digits are the only ones shown as valid.

Figure 4.10

Figure 4.10 Output from ‘validate_regex’ Class Showing Valid and Invalid Phone Numbers

Since the ‘validate_regex’ class is tested, it can now be used in the form built earlier. For this example, I use the ‘phone.php’, ‘validate_regex.php’, and ‘verify_phone.php’ files. The form logic in ‘phone.php’ remains the same. The class in ‘validate_regex.php’ remains the same. I include the logic for ‘phone.php’ again here for your convenience.

 1 <?php

 2 // File phone.php

 3 ?>

 4 <html><head>

 5 <style type="text/css">

 6 table.center {

 7 margin-left:auto;

 8 margin-right:auto;

 9 }

10 </style></head>

11 <body style="background-color:burlywood;">

12 <div style="text-align:center;">

13 <h1 style="color:indigo;">Enter Phone Number</h1>

14 <form method="post" action="verify_phone.php">

15 <table class="center">

16 <tr><td>Phone:</td>

17 <td><input type="text" name="phone" maxlength="10"></td>

18 </tr>

19 <td><input type="submit" value="Enter"></td>

20 <td><input type="reset" value="Reset"></td>

21 </tr>

22 </table></form></div>

23 </html>

I had to modify the logic in ‘verify_phone.php’. First, I included ‘validate_regex.php’ (line 3) to enable use of the class. Second, I created a new instance of the class (line 4). Third, I invoked method ‘get_valid_phone()’ (line 6) to test if the number contains seven or ten digits. The remaining logic is the same as before. The phone number still has to be checked to determine whether it has either seven or ten digits so it can be formatted properly (lines 8–14). If the phone number is not valid, an error message is generated, as before (line 7). Actually, the logic is a bit simpler with the use of the class instance.

 1 <?php

 2 // File verify_phone.php

 3 require_once 'validate_regex.php';

 4 $validate = new validate_regex();

 5 $phone = $_POST["phone"];

 6 if($validate->get_valid_phone($phone))

 7 {

 8 if(strlen($phone)==7)

 9 { $phone = substr($phone,0,3) . '-' . substr($phone,3,4); }

10 elseif(strlen($phone)==10)

11 {

12 $p1 = '(' . substr($phone,0,3) . ')' . substr($phone,3,3);

13 $phone = $p1 . '-' . substr($phone,6,4);

14 }

15 echo 'Translated phone number is: ' . $phone;

16 }

17 else { echo 'Enter a valid phone number of 7 or 10 digits'; }

18 ?>

Load ‘phone.php’ into a browser. Figure 4.11 shows the form prior to submission with a seven-digit phone number. Figure 4.12 shows what happens after submission of the seven-digit number. Figure 4.13 shows the form prior to submission with a ten-digit phone number. Figure 4.14 shows what happens after submission of the ten-digit number. Figure 4.15 shows the form prior to submission with an invalid entry. Figure 4.16 shows what happens after submission of the invalid entry.

Figure 4.11

Figure 4.11 Display of Form with Valid Seven-Digit String

Figure 4.12

Figure 4.12 Display of Translated Seven-Digit String Using ‘validate_regex’ Class

Figure 4.13

Figure 4.13 Display of Form with Valid Ten-Digit String

Figure 4.14

Figure 4.14 Display of Translated Ten-Digit String Using ‘validate_regex’ Class

Figure 4.15

Figure 4.15 Display of Form with Invalid Four-Digit String

Figure 4.16

Figure 4.16 Display of Message with Acceptable Strings Using ‘validate_regex’ Class

Now, let’s create a simple regex to validate email. We need two additional symbols – ‘+’ matches one or more of a pattern, and ‘.’ matches any single character. Modify PHP file ‘validate_regex.php’ to include a new method for email validation (lines 10–14). The regex (line 12) tests if the email is valid.

 1 <?php

 2 // File validate_regex.php

 3 class validate_regex

 4 {

 5 function get_valid_phone($phone)

 6 {

 7 $regex = '#^[0-9]{7}$|^[0-9]{10}$#';

 8 return preg_match($regex,trim($phone));

 9 }

10 function get_valid_email($email)

11 {

12 $regex = '#^[0-9A-Za-z._%+-]{2,}@[0-9A-Za-z.-]+\.[A-Za-z]{2,6}$#';

13 return preg_match($regex,trim($email));

14 }

15 }

16 ?>

The email regex must begin with two or more of the following (number, case insensitive letter, period, underscore, percent, plus or minus). It must continue with the ‘@’ symbol, one or more of the following (number, case insensitive letter, period or minus), a period, and end with two to six case insensitive letters.

Again, I always test a new method independently of the form application. So, modify PHP file ‘call_validate_regex.php’ to validate emails.

 1 <?php

 2 // File call_validate_regex.php

 3 require_once 'validate_regex.php';

 4 $emails = array(

 5 'my.name@gmail.com',

 6 'another@gmail.co.uk',

 7 'best@yahoo',

 8 'hellomsn.net',

 9 'long@123.org',

10 '123.me.you@ymail.com',

11 'miss@yahoo.',

12 'Miss@Yahoo.com',

13 '.@yahoo.com'

14 );

15 $validate = new validate_regex();

16 foreach($emails as $email)

17 {

18 if($validate->get_valid_email($email))

19 { echo "Valid Email: " . $email; }

20 else

21 { echo "Invalid Email: " . $email; }

22 echo "<br />";

23 }

24 ?>

The ‘$emails’ array (lines 4–14) includes various emails to test. The class is instantiated in line 15. A ‘foreach’ loop (lines 16–23) iterates the email array. Each email is tested with ‘get_valid_email()’ (line 18) and the appropriate message is returned (lines 19 and 21). Load the modified ‘call_validate.php’ to see the results shown in Figure 4.17.

Figure 4.17

Figure 4.17 Output from ‘validate_regex’ Class Showing Valid and Invalid Email Addresses

To finalize the ‘validate’ class, I create four new methods with a regex to validate first name, last name, address, and ZIP code. Modify PHP file ‘validate_regex.php’ as follows:

 1 <?php

 2 // File validate_regex.php

 3 class validate_regex

 4 {

 5 function get_valid_first($first)

 6 {

 7 $regex = '#^[a-zA-Z]{2,10}$#';

 8 return preg_match($regex,trim($first));

 9 }

10 function get_valid_last($last)

11 {

12 $regex = '#^[a-zA-Z]{1}\'?[a-zA-Z]{2,20}$#';

13 return preg_match($regex,trim($last));

14 }

15 function get_valid_address($address)

16 {

17 $regex = '#^[a-zA-Z-0-9]{1,}\s[a-zA-Z0-9]{1,}+#';

18 return preg_match($regex,trim($address));

19 }

20 function get_valid_zip($zip)

21 {

22 $regex = '#^([0-9]{5})(-[0-9]{4})?$#';

23 return preg_match($regex,trim($zip));

24 }

25 function get_valid_phone($phone)

26 {

27 $regex = '#^[0-9]{7}$|^[0-9]{10}$#';

28 return preg_match($regex,trim($phone));

29 }

30 function get_valid_email($email)

31 {

32 $regex = '#^[0-9A-Za-z._%+-]{2,}@[0-9A-Za-z.-]+\.[A-Za-z]{2,6}$#';

33 return preg_match($regex,trim($email));

34 }

35 }

36 ?>

The regex for the first name must start with two (or more) case insensitive letters (line 7). The length of the first name is limited to ten letters, but you can change this value. The regex for the last name (line 12) can optionally start with one case insensitive letter followed by an apostrophe and continue with up to 20 letters. The regex for address (line 17) must start with one or more case insensitive letters or numbers followed by a space and continue with one or more letters or numbers. The address regex is very general because addresses can take on so many combinations. The regex for the ZIP code (line 22) can take only two forms – five digits or optionally five digits, a ‘-’, and end with four digits. Modify ‘call_validate_regex.php’ to test each method.

Begin by validating first names (line 10) (Figure 4.18).

 1 <?php

 2 // File call_validate_regex.php

 3 require_once 'validate_regex.php';

 4 $firsts = array(

 5 'Dave', '1234_jim?', 'J', 'McN\'Nay',

 6 'Susan', 'Ahmed', '.John.');

 7 $validate = new validate_regex();

 8 foreach($firsts as $first)

 9 {

10 if($validate->get_valid_first($first))

11 { echo "Valid First Name: " . $first; }

12 else

13 { echo "Invalid First Name: " . $first; }

14 echo "<br />";

15 }

16 ?>

Figure 4.18

Figure 4.18 Output from ‘validate_regex’ Class Showing Valid and Invalid First Names

Continue by validating last names (line 10) (Figure 4.19).

 1 <?php

 2 // File call_validate_regex.php

 3 require_once 'validate_regex.php';

 4 $lasts = array (

 5 'Jones', '_1234?', 'O\'Reilly',

 6 'McN\'Nay', 'McKay');

 7 $validate = new validate_regex();

 8 foreach($lasts as $last)

 9 {

10 if($validate->get_valid_last($last))

11 { echo "Valid Last Name: " . $last; }

12 else

13 { echo "Invalid Last Name: " . $last; }

14 echo "<br />";

15 }

16 ?>

Figure 4.19

Figure 4.19 Output from ‘validate_regex’ Class Showing Valid and Invalid Last Names

Continue by validating addresses (line 10) (Figure 4.20).

 1 <?php

 2 // File call_validate_regex.php

 3 require_once 'validate_regex.php';

 4 $addrs = array(

 5 '665 W. North # 5', '_1234?',

 6 '987 North Pine, Ste. 7', '_622 444 7');

 7 $validate = new validate_regex();

 8 foreach($addrs as $address)

 9 {

10 if($validate->get_valid_address($address))

11 { echo "Valid Address: " . $address; }

12 else

13 { echo "Invalid Address: " . $address; }

14 echo "<br />";

15 }

16 ?>

Figure 4.20

Figure 4.20 Output from ‘validate_regex’ Class Showing Valid and Invalid Addresses

Finish by validating ZIP codes (line 10) (Figure 4.21).

 1 <?php

 2 // File call_validate_regex.php

 3 require_once 'validate_regex.php';

 4 $zips = array(

 5 '84321', '84321-3515', '8sjj3',

 6 'Sssss-8888');

 7 $validate = new validate_regex();

 8 foreach($zips as $zip)

 9 {

10 if($validate->get_valid_zip($zip))

11 { echo "Valid Zip Code: " . $zip; }

12 else

13 { echo "Invalid Zip Code: " . $zip; }

14 echo "<br />";

15 }

16 ?>

Figure 4.21

Figure 4.21 Output from ‘validate_regex’ Class Showing Valid and Invalid ZIP Codes

I deliberately built the ‘validate_regex’ class by beginning with one method and testing it. I continued by adding methods and testing them, one by one, until all methods had been tested. This technique is how I build all classes. In my many years of programming, I have found this methodology to work perfectly. That is, it drastically reduces coding errors, produces modular code, and adheres to OOP principles.

PCRE Form Validation Application

The ‘validate_regex’ class is now complete (‘validate_regex.php’) and tested (‘call_validate_regex.php’ for each method). So, we have the tools to build a simple PCRE form data validation application.

The application uses three PHP files – ‘validate_regex.php’ (just completed in the previous section), ‘form.php’, and ‘validate_form.php’. The input form is in ‘form.php’ and the validation of form data is handled in ‘validate_form.php’.

 1 <?php

 2 // File form.php

 3 ?>

 4 <html><head>

 5 <style type="text/css">

 6 table.center{margin-left:auto;margin-right:auto;}

 7 </style></head>

 8 <body style="background-color:lightgreen;">

 9 <div style="text-align:center;">

10 <h1 style="color:indigo;">Enter Input Data</h1>

11 <form method="post" action="validate_form.php">

12 <table class="center">

13 <tr><td>First Name:</td>

14 <td><input type="text" name="first" maxlength="10"></td>

15 </tr>

16 <tr><td>Last Name:</td>

17 <td><input type="text" name="last" maxlength="20"></td>

18 </tr>

19 <tr><td>Address:</td>

20 <td><input type="text" name="address" maxlength="30"></td>

21 </tr>

22 <tr><td>Zip Code:</td>

23 <td><input type="text" name="zip" maxlength="10"></td>

24 </tr>

25 <tr><td>Phone:</td>

26 <td><input type="text" name="phone" maxlength="10"></td>

27 </tr>

28 <tr><td>Email:</td>

29 <td><input type="text" name="email" maxlength="30"></td>

30 </tr>

31 <tr>

32 <td><input type="submit" value="Enter"></td>

33 <td><input type="reset" value="Reset"></td>

34 </tr>

35 </table></form></div>

36 </html>

PHP file ‘form.php’ includes an input tag for each type of data (lines 14, 17, 20, 23, 26, and 29). The final two input tags (lines 32 and 33) are for form submission and form reset. The form attributes ‘method’ and ‘action’ (line 11) are ‘post’ and ‘validate_form.php’ respectively. Upon submission, the browser ‘posts’ data to the redirection file ‘validate_form.php’.

 1 <?php

 2 // File validate_form.php

 3 require_once 'validate_regex.php';

 4 $validate = new validate_regex();

 5 $first = $_POST["first"];

 6 $last = $_POST["last"];

 7 $address = $_POST["address"];

 8 $zip = $_POST["zip"];

 9 $phone = $_POST["phone"];

10 $email = $_POST["email"];

11 $good_data = array();

12 $bad_data = array();

13 $i = 0;

14 $j = 0;

15 if($validate->get_valid_first($first))

16 { $good_data[$i] = $first;

17 $i++; }

18 else

19 { $bad_data[$j] = 'Enter a valid first name';

20 $j++; }

21 if($validate->get_valid_last($last))

22 { $good_data[$i] = $last;

23 $i++; }

24 else

25 { $bad_data[$j] = 'Enter a valid last name';

26 $j++; }

27 if($validate->get_valid_address($address))

28 { $good_data[$i] = $address;

29 $i++; }

30 else

31 { $bad_data[$j] = 'Enter a valid address';

32 $j++; }

33 if($validate->get_valid_zip($zip))

34 { $good_data[$i] = $zip;

35 $i++; }

36 else

37 { $bad_data[$j] = 'Enter a valid zip code';

38 $j++; }

39 if($validate->get_valid_phone($phone))

40 {

41 if(strlen($phone)==7)

42 { $phone = substr($phone,0,3) . '-' . substr($phone,3,4); }

43 elseif(strlen($phone)==10)

44 { $p1 = '(' . substr($phone,0,3) . ')' . substr($phone,3,3);

45 $phone = $p1 . '-' . substr($phone,6,4); }

46 $good_data[$i] = $phone;

47 $i++;

48 }

49 else

50 { $bad_data[$j] =

51 'Enter a valid phone number of 7 or 10 digits';

52 $j++; }

53 if($validate->get_valid_email($email))

54 { $good_data[$i] = $email;

55 $i++; }

56 else

57 { $bad_data[$j] = 'Enter a valid email';

58 $j++; }

59 echo '<h3>Good Data Report</h3>';

60 foreach($good_data as $good)

61 { echo $good . '<br />'; }

62 echo '<h3>Bad Data Report</h3>';

63 foreach($bad_data as $bad)

64 { echo $bad . '<br />'; }

65 ?>

PHP file ‘validate_form.php’ posts input data from the form (lines 5–10). A new instance of ‘validate_regex’ is created in line 4. Two arrays are created and initialized (lines 11 and 12). One array ($good_data) stores the validated form data and the other ($bad_data) stores the error messages. These arrays are used to create a report for valid and invalid form data. Each input data item is validated by invoking the corresponding method from class ‘validate_regex’ (lines 15, 21, 27, 33, 39, and 53).Valid data are stored in the ‘$good_data’ array indexed with ‘$i’ (lines 16, 22, 28, 34, 46, and 54). Invalid data are stored in the ‘$bad_data’ array indexed with ‘$j’ (lines 19, 25, 31, 37, 50, and 57). Data are reported by traversing both arrays (lines 60–64).

Load ‘form.php’ and enter some data. Figure 4.22 shows the form before submission.

Figure 4.22

Figure 4.22 Display of Form with Various Information Included

Click ‘Enter’. The results are shown in Figure 4.23. The report shows that the first name, last name, and phone number are correct, but the address, ZIP code, and email are not correct.

Figure 4.23

Figure 4.23 Display of Good and Bad Data from Form

Built-in Validation Functions

PHP has built-in filter functions that validate form data. One such function is ‘filter_var()’, which filters a single piece of data. The next example illustrates how to use this function to filter email data. Load PHP file ‘filter_email.php’ to validate emails using built-in function ‘filter_var()’ (Figure 4.24).

 1 <?php

 2 // File filter_email.php

 3 $emails = array(

 4 'my.name@gmail.com', 'another@gmail.co.uk',

 5 'best@yahoo', 'hellomsn.net', 'long@123.org',

 6 '123.me.you@ymail.com', 'miss@yahoo.',

 7 'Miss@Yahoo.com', '.@yahoo.com');

 8 foreach($emails as $email)

 9 {

10 if(filter_var($email,FILTER_VALIDATE_EMAIL))

11 { echo "Valid Email: " . $email; }

12 else

13 { echo "Invalid Email: " . $email; }

14 echo "<br />";

15 }

16 ?>

Figure 4.24

Figure 4.24 Display of Valid and Invalid Emails Using Built-In Validate Functions

The ‘filter_var()’ function uses the ‘FILTER_VALIDATE_EMAIL’ filter (line 10). The results shown in Figure 4.24 are exactly the same as those in Figure 4.17, which used a regex for email validation.

PHP file ‘filter_age.php’ uses ‘filter_var()’ to validate numbers between 18 and 65 (inclusive) with filter ‘FILTER_VALIDATE_INT’ (line 10) and the ‘$options’ array (lines 5 and 6). The ‘filter_var()’ function has added flexibility by allowing an associative array of options to be included as a parameter. In this example, I specified the minimum and maximum age ranges ('min_range'=>18 & 'max_range'=>65) (line 6) as options.

 1 <?php

 2 // File filter_age.php

 3 $ages = array(

 4 '17', '0', '18', '65', '-10', '66');

 5 $options = array(

 6 'options'=>array('min_range'=>18, 'max_range'=>65));

 7 foreach($ages as $age)

 8 {

 9 $filtered = filter_var($age,

10 FILTER_VALIDATE_INT, $options);

11 if($filtered)

12 { echo "Valid Age: " . $age; }

13 else

14 { echo "Invalid Age: " . $age; }

15 echo "<br />";

16 }

17 ?>

Figure 4.25

Figure 4.25 Display of Valid and Invalid Ages Using Built-In Validate Functions

As shown in Figure 4.25, only 18 and 65 are valid ages. I use this filter to allow people between the ages of 18 and 65 inclusive (lines 9 and 10) to access a web page. Of course, you can vary this range to your requirements.

PHP file ‘filter_url.php’ validates a URL. I prefer to use a built-in function to validate a URL because the regex required is very complex.

 1 <?php

 2 // File filter_url.php

 3 $urls = array(

 4 'http://www.whodunit.com', 'www.whodunnit.com',

 5 'https://www.hello.org', 'ww.whodunnit.com');

 6 foreach($urls as $url)

 7 {

 8 if(filter_var($url,

 9 FILTER_VALIDATE_URL,FILTER_FLAG_HOST_REQUIRED))

10 { echo "Valid URL: " . $url; }

11 else

12 { echo "Invalid URL: " . $url; }

13 echo "<br />";

14 }

15 ?>

Figure 4.26

Figure 4.26 Display of Valid and Invalid URLs Using Built-In Validate Functions

The results are shown in Figure 4.26. The ‘FILTER_VALIDATE_URL’ filter (line 9) validates a URL. The flag ‘FLAG_HOST_REQUIRED’ (line 9) restricts a valid URL to one that includes the host name – either ‘http://’ or ‘https://’. The second URL is invalid because it does not contain a host string. The fourth URL is invalid because it is not properly formed.

Multiple Variable Validation Functions

While ‘filter_var()’ function filters a single variable, ‘filter_var_array()’ filters multiple variables. This function is useful for retrieving many values without repetitively calling ‘filter_var()’. However, I am not sure it is worth the trouble because of the added programming complexity. PHP file ‘filter_multiple.php’ validates email, age, and URL using the ‘filter_var_array()’ function. Figure 4.27 shows the results.

 1 <?php

 2 // File filter_multiple.php

 3 $data = array(

 4 'email'=>'david.paper@usu.edu', 'age'=>'21',

 5 'url'=>'https://www.okay.com');

 6 $args = array(

 7 'email'=>FILTER_VALIDATE_EMAIL,

 8 'age'=>array('filter'=>FILTER_VALIDATE_INT,

 9 'options'=>array('min_range'=>18,'max_range'=>65)),

10 'url'=>array('filter'=>FILTER_VALIDATE_URL,

11 'flags'=>FILTER_FLAG_HOST_REQUIRED));

12 $inputs = filter_var_array($data,$args);

13 if($inputs['email'])

14 { echo "Valid Email: " . $data['email']; }

15 else

16 { echo "Invalid Email: " . $data['email']; }

17 echo "<br />";

18 if($inputs['age'])

19 { echo "Valid Age: " . $data['age']; }

20 else

21 { echo "Invalid Age: " . $data['age']; }

22 echo "<br />";

23 if($inputs['url'])

24 { echo "Valid URL: " . $data['url']; }

25 else

26 { echo "Invalid URL: " . $data['url']; }

27 ?>

Figure 4.27

Figure 4.27 Display of Valid and Invalid Entries Using Built-In Validate Functions

Lines 3–5 provide the data to be filtered in the ‘$data’ array. Lines 6–11 provide the filters for each piece of data from ‘$data’ in array ‘$args’. Email is filtered with ‘FILTER_VALIDATE_EMAIL’ (line 7). Age is filtered with ‘FILTER_VALIDATE_INT’ (line 8) and the age range provided in ‘$options’ (line 9). URL is filtered with ‘FILTER_VALIDATE_URL’ (line 10) and ‘FILTER_FLAG_HOST_REQUIRED’ (line 11). The ‘filter_var_array()’ function (line 12) uses ‘$data’ and ‘$args’ as parameters. The rest of the code (lines 13–26) display the resulting messages about the data being filtered.

The advantage of using built-in filter functions is ease of use. The disadvantage is that they are severely limited in flexibility. That is, they can only be used specifically as prescribed. They cannot be adjusted. Also, you cannot see how they filter the data. Regexes, in contrast, are not easy to build, but they can be built to meet your needs. You can build regexes to match virtually any kind of text string or number.

Form Validation Application with PCRE and Built-In Functions

We now have all the knowledge we need to build a robust and sophisticated application by pulling together PCRE and built-in function validation. The application uses four PHP files ‘validate_app_regex.php’, ‘app_form.php’, ‘validate_app_form.php’, and ‘success.php’.

I modified ‘validate_regex’ by adding two new validation methods – one for age (lines 36–41) and one for URL (lines 42–47). Both of these methods use built-in PHP functions for validation. I also changed the name of the class to ‘validate_app_regex’.

 1 <?php

 2 // File validate_app_regex.php

 3 class validate_app_regex

 4 {

 5 function get_valid_first($first)

 6 {

 7 $regex = '#^[a-zA-Z]{2,10}$#';

 8 return preg_match($regex,trim($first));

 9 }

10 function get_valid_last($last)

11 {

12 $regex = '#^[a-zA-Z]{1}\'?[a-zA-Z]{2,20}$#';

13 return preg_match($regex,trim($last));

14 }

15 function get_valid_address($address)

16 {

17 $regex = '#^[a-zA-Z-0-9]{1,}\s[a-zA-Z0-9]{1,}+#';

18 return preg_match($regex,trim($address));

19 }

20 function get_valid_zip($zip)

21 {

22 $regex = '#^([0-9]{5})(-[0-9]{4})?$#';

23 return preg_match($regex,trim($zip));

24 }

25 function get_valid_phone($phone)

26 {

27 $regex = '#^[0-9]{7}$|^[0-9]{10}$#';

28 return preg_match($regex,trim($phone));

29 }

30 function get_valid_email($email)

31 {

32 $regex =

33 '#^[0-9A-Za-z._%+-]{2,}@[0-9A-Za-z.-]+\.[A-Za-z]{2,6}$#';

34 return preg_match($regex,trim($email));

35 }

36 function get_valid_age($age)

37 {

38 $filtered = filter_var($age,FILTER_VALIDATE_INT,

39 array('options'=>array('min_range'=>18, 'max_range'=>65)));

40 return $filtered;

41 }

42 function get_valid_url($url)

43 {

44 $filtered = filter_var

45 ($url,FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);

46 return $filtered;

47 }

48 }

49 ?>

The ‘app_form.php’ file holds the form used for data entry. Since we are building a robust application, the form requires quite a bit of code.

 1 <?php

 2 // File app_form.php

 3 session_start();

 4 ?>

 5 <html><head>

 6 <style type="text/css">

 7 table.center {

 8 margin-left:auto;

 9 margin-right:auto;

10 }

11 </style></head>

12 <body style="background-color:lightgreen;">

13 <div style="text-align:center;">

14 <h1 style="color:indigo;">Enter Input Data</h1>

15 <form method="post" action="validate_app_form.php">

16 <table class="center">

17 <tr><td>First Name:</td>

18 <td><input type="text" name="first" maxlength="10"

19 value=<?php if(isset($_SESSION['first']))

20 { echo $_SESSION['first']; } "/> >

21 </td>

22 </tr>

23 <tr><td>Last Name:</td>

24 <td><input type="text" name="last" maxlength="20"

25 value=<?php if(isset($_SESSION['last']))

26 { echo $_SESSION['last']; } "/> >

27 </td>

28 </tr>

29 <tr><td>Address:</td>

30 <td><input type="text" name="address" maxlength="30"

31 value=<?php if(isset($_SESSION['address']))

32 { echo $_SESSION['address']; } "/> >

33 </td>

34 </tr>

35 <tr><td>Zip Code:</td>

36 <td><input type="text" name="zip" maxlength="10"

37 value=<?php if(isset($_SESSION['zip']))

38 { echo $_SESSION['zip']; } "/> >

39 </td>

40 </tr>

41 <tr><td>Phone:</td>

42 <td><input type="text" name="phone" maxlength="10"

43 value=<?php if(isset($_SESSION['phone']))

44 { echo $_SESSION['phone']; } "/> > </td>

45 </tr>

46 <tr><td>Email:</td>

47 <td><input type="text" name="email" maxlength="30"

48 value=<?php if(isset($_SESSION['email']))

49 { echo $_SESSION['email']; } "/> >

50 </td>

51 </tr>

52 <tr><td>Age:</td>

53 <td><input type="text" name="age" maxlength="3"

54 value=<?php if(isset($_SESSION['age']))

55 { echo $_SESSION['age']; } "/> >

56 </td>

57 </tr>

58 <tr><td>URL:</td>

59 <td><input type="text" name="url"

60 value=<?php if(isset($_SESSION['url']))

61 { echo $_SESSION['url']; } "/> >

62 </td>

63 </tr>

64 <tr>

65 <td><input type="submit" value="Enter"></td>

66 <td><input type="reset" value="Reset"></td>

67 </tr>

68 </table></form></div>

69 <table class="center">

70 <tr><td>

71 <?php

72 if(isset($_SESSION['err_first']))

73 { echo $_SESSION['err_first'] . '<br />'; }

74 if(isset($_SESSION['err_last']))

75 { echo $_SESSION['err_last'] . '<br />'; }

76 if(isset($_SESSION['err_address']))

77 { echo $_SESSION['err_address'] . '<br />'; }

78 if(isset($_SESSION['err_zip']))

79 { echo $_SESSION['err_zip'] . '<br />'; }

80 if(isset($_SESSION['err_phone']))

81 { echo $_SESSION['err_phone'] . '<br />'; }

82 if(isset($_SESSION['err_email']))

83 { echo $_SESSION['err_email'] . '<br />'; }

84 if(isset($_SESSION['err_age']))

85 { echo $_SESSION['err_age'] . '<br />'; }

86 if(isset($_SESSION['err_url']))

87 { echo $_SESSION['err_url']; }

88 ?>

89 </td></tr>

90 </table>

91 </html>

The form begins by starting a new session with ‘session_start()’ (line 3) because we need to check and set session variables. The form ‘method’ is ‘post’ and ‘action’ is a redirect to ‘validate_app_form.php’ (line 15). The input tags allow text data to be entered on a web page. All input tags (for data) follow the same logic. A session variable is checked. If set, its value is displayed inside the tag. The ability to display data inside an ‘input’ tag is very convenient. In this case, I ensure that what was typed doesn’t have to be retyped when the page is reloaded.

The first tag is for first name (lines 18–20). The ‘value’ attribute (lines 19 and 20) includes a bit of PHP code that checks if the session variable ‘first’ is set (line 19). If set, the value of session variable ‘first’ is placed inside the input tag (line 20). The second input tag is for last name (lines 24–26). The ‘value’ attribute (lines 25 and 26) uses PHP code to display session variable ‘last’ if set. The third input tag is for address (lines 30–32). The ‘value’ attribute (lines 31 and 32) uses PHP code to display session variable ‘address’ if set. The fourth input tag is for ZIP code (lines 36–38). The ‘value’ attribute (lines 37 and 38) uses PHP code to display session variable ‘zip’ if set. The fifth input tag is for phone (lines 42–44). The ‘value’ attribute (lines 43 and 44) uses PHP code to display session variable ‘phone’ if set. The sixth input tag is for email (lines 47–49). The ‘value’ attribute (lines 48 and 49) uses PHP code to display session variable ‘email’ if set. The seventh input tag is for age (lines 53–55). The ‘value’ attribute (lines 54 and 55) uses PHP code to display session variable ‘age’ if set. The eighth input tag is for URL (lines 59–61). The ‘value’ attribute (lines 60 and 61) uses PHP code to display session variable ‘url’ if set.

The final two input tags are for ‘submit’ (line 65) and ‘reset’ (line 66) buttons. The form ends in line 68.

A new table begins (line 69) that includes PHP code (lines 71–88) to check for error messages set in ‘validate_app_form.php’ (I will show and explain this code momentarily). Each error message is checked. If set, the error message is displayed. For instance, ‘err_first’ is checked (line 72). If set, it is displayed (line 73).

 1 <?php

 2 // File validate_app_form.php

 3 session_start();

 4 require_once 'validate_app_regex.php';

 5 $validate = new validate_app_regex();

 6 $first = $_POST["first"];

 7 $last = $_POST["last"];

 8 $address = $_POST["address"];

 9 $zip = $_POST["zip"];

10 $phone = $_POST["phone"];

11 $email = $_POST["email"];

12 $age = $_POST["age"];

13 $url = $_POST["url"];

14 if($validate->get_valid_first($first))

15 { $_SESSION['first'] = $first;

16 unset($_SESSION['err_first']); }

17 else

18 { $_SESSION['err_first'] = 'Enter a valid first name'; }

19 if($validate->get_valid_last($last))

20 { $_SESSION['last'] = $last;

21 unset($_SESSION['err_last']); }

22 else

23 { $_SESSION['err_last'] = 'Enter a valid last name'; }

24 if($validate->get_valid_address($address))

25 { $_SESSION['address'] = '"' . $address . '"';

26 unset($_SESSION['err_address']); }

27 else

28 { $_SESSION['err_address'] = 'Enter a valid address'; }

29 if($validate->get_valid_zip($zip))

30 { $_SESSION['zip'] = $zip;

31 unset($_SESSION['err_zip']); }

32 else

33 { $_SESSION['err_zip'] = 'Enter a valid zip code'; }

34 if($validate->get_valid_phone($phone))

35 {

36 if(strlen($phone)==7)

37 { $phones = substr($phone,0,3) . '-' . substr($phone,3,4); }

38 elseif(strlen($phone)==10)

39 { $p1 = '(' . substr($phone,0,3) . ')' . substr($phone,3,3);

40 $phones = $p1 . '-' . substr($phone,6,4); }

41 $_SESSION['phone'] = $phone;

42 unset($_SESSION['err_phone']);

43 }

44 else

45 { $_SESSION['err_phone'] =

46 'Enter a valid phone number of 7 or 10 digits'; }

47 if($validate->get_valid_email($email))

48 { $_SESSION['email'] = $email;

49 unset($_SESSION['err_email']); }

50 else

51 { $_SESSION['err_email'] = 'Enter a valid email'; }

52 if($validate->get_valid_age($age))

53 { $_SESSION['age'] = $age;

54 unset($_SESSION['err_age']); }

55 else

56 { $_SESSION['err_age'] = 'Enter a valid age'; }

57 if($validate->get_valid_url($url))

58 { $_SESSION['url'] = $url;

59 unset($_SESSION['err_url']); }

60 else

61 { $_SESSION['err_url'] = 'Enter a valid url'; }

62 if( isSet($_SESSION['err_first'])| isSet($_SESSION['err_last'])|

63 isSet($_SESSION['err_address'])| isSet($_SESSION['err_zip'])|

64 isSet($_SESSION['err_phone'])| isSet($_SESSION['err_email'])|

65 isSet($_SESSION['err_age'])| isSet($_SESSION['err_url']) )

66 { header('Location: app_form.php'); }

67 else

68 { header('Location: success.php'); }

69 ?>

The ‘validate_app_form.php’ file holds the validation logic. Logic begins by starting a new session (line 3). A new instance of ‘validate_app_regex’ is created (line 5). Lines 6–13 assign ‘post’ values from the form to their respective variables. The first ‘if’ (lines 14–16) uses the ‘get_valid_first()’ to validate ‘$first’. If valid, session variable ‘$_SESSION['first']’ is created (line 15). I also unset the error message so that it won’t be displayed on the form (line 16). If ‘$first’ is invalid, error message ‘$_SESSION['err_first']’ is set. For each input value from the form, the logic is the same. That is, each input value is validated, and either the session variable for that value is set (valid) or the error message for that value is set (invalid) (lines 19–61). Lines 62–65 check if any error message has been set. If so, logic is redirected to form ‘app_form.php’ (line 66). If no errors are set, all form values are valid and logic is redirected to ‘success.php’ (line 68).

Although this file has a lot of code, the logic is rather simple. All form values are ‘posted’ and set to variables. Next, variables are validated with their corresponding methods from the ‘validate_app_regex’ class. Finally, if any error messages have been set, logic is redirected to the form ‘app_form.php’. Otherwise, logic is redirected to ‘success.php’.

 1 <?php

 2 // File success.php

 3 session_start();

 4 $first = $_SESSION['first'];

 5 $last = $_SESSION['last'];

 6 $address = $_SESSION['address'];

 7 $zip = $_SESSION['zip'];

 8 $phone = $_SESSION['phone'];

 9 $email = $_SESSION['email'];

10 $age = $_SESSION['age'];

11 $url = $_SESSION['url'];

12 ?>

13 <html>

14 <body style="background-color:lightgreen;">

15 <div style="text-align:center;">

16 <h1 style="color:indigo;">Success</h1>

17 <?php

18 echo $first . '<br />';

19 echo $last . '<br />';

20 echo $address . '<br />';

21 echo $zip . '<br />';

22 echo $phone . '<br />';

23 echo $email . '<br />';

24 echo $age . '<br />';

25 echo $url;

26 ?>

27 </div></html>

The ‘success.php’ file holds simple logic to indicate that the form was completed properly. It starts a new session (line 3), assigns session variable values to PHP variables (lines 4–11) and displays the values (lines 18–25). Session variables are very useful because once set they are available until the session is closed.

Whew! That’s a lot of code. Let’s test the application. Load PHP file ‘app_form.php’ into a browser. Enter ‘David’ in the ‘First Name’ text box, ‘Paper’ in the ‘Last Name’ text box, ‘84321’ in the ‘ZIP code’ text box, and ‘21’ in the ‘Age’ text box. Press the ‘Enter’ button. Figure 4.28shows the results.

Figure 4.28

Figure 4.28 Display of Form Application with Some Information Included

The entries you made are valid because they appear in their respective text boxes. Four messages appear after the form indicating data that must be entered. The text boxes with invalid data are blank. Enter ‘666 E 100 S’ in the ‘Address’ text box, ‘7872121’ in the ‘Phone’ textbox, ‘john.doe@usu.edu’ in the ‘Email’ text box, and ‘https://www.usu.edu’ in the ‘URL’ text box and press the ‘Enter’ button. Figure 4.29 shows the results. The data you entered is valid and is displayed on this page.

Figure 4.29

Figure 4.29 Display with Successful Entries

Advantages and Disadvantages of PCRE and Built-In Functions

The main advantage of PCRE is its flexibility. PCRE enables you to build a regex to validate any pattern. The main disadvantage of PCRE is that it requires a sharp learning curve to master. PCRE is also embraced because it is based on Perl. Perl is widely used in industry, so Perl-related knowledge can only increase your marketability in the world of technology. Another advantage is that someone with PCRE or Perl skills can look at your regex and readily see its value.

The main advantage of built-in validation functions is their ease of use. To use them, no PCRE knowledge is required. The main disadvantage is that built-in functions are inflexible. You can only validate data that fits with the available functions.

Report Generation Application

It is possible to obtain directives from users without having to validate. An example is report generation with drop-down menus. With such menus, users can safely generate a variety of reports.

To build a report generation application, start by creating sequences and tables, continue by populating the tables, and ‘commit’ the work. An Oracle sequence is a database object that allows a user to generate unique integers. A typical application uses a sequence to automatically generate primary key values. The ‘COMMIT’ statement makes permanent any changes made to the database during the current transaction.

Open an ‘Oracle SQL Developer’ connection. In the ‘SQL Worksheet’, create the ‘customer_sequence’ sequence with the following SQL. The ‘Script Output’ window should indicate that the sequence was created.

CREATE SEQUENCE customer_sequence START WITH 1000 INCREMENT BY 1;

Create the ‘customers’ table in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that the table was created.

CREATE TABLE customers (

id NUMBER(4),

first_name VARCHAR2(20),

last_name VARCHAR2(20),

address VARCHAR2(30),

phone CHAR(10)

);

Populate the ‘customers’ table in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that ten rows were inserted into the table.

INSERT INTO customers (id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Dan D.', 'Lion', '500 E 500 N # 8', '4357972222');

INSERT INTO customers (id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Hugh', 'Mungus', '321 Bear Lane', '8017778888');

INSERT INTO customers (id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Will E.', 'Makeit', '3515 Old Main Hill', '4357523322');

INSERT INTO customers (id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Betty', 'Whoant', '665 E 200 N # 4', '4357973344');

INSERT INTO customers(id, first_name, last_name, address, phone)

VALUES(customer_sequence.NEXTVAL, 'Hugh R.', 'Cool', '1800 E 1800 N', '4357877878');

INSERT INTO customers(id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Sally', 'Forth', '400 W 600 S # 15', '4357879000');

INSERT INTO customers(id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Rose E.', 'Vine', '1400 Preston Ave.', '8019843333');

INSERT INTO customers(id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Earl E.', 'Morning', '900 Jade Blvd. E', '8018774545');

INSERT INTO customers(id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Dewey', 'Doit', '100 N State Street', '8012222222');

INSERT INTO customers(id, first_name, last_name, address, phone)

VALUES (customer_sequence.NEXTVAL, 'Sam R.', 'Rye', '100 N Temple E', '8013453434');

Sequences use the ‘NEXTVAL’ method to increment automatically. Repeat the process by creating the ‘order_sequence’ sequence. The ‘Script Output’ window should indicate that the sequence was created.

CREATE SEQUENCE order_sequence START WITH 2000 INCREMENT BY 1;

Create the ‘orders’ table in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that the table was created.

CREATE TABLE orders (

ord_no NUMBER(5),

ord_date DATE,

total_price NUMBER(5,2),

id NUMBER(4)

);

Populate the ‘orders’ table in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that two rows were inserted into the table.

INSERT INTO orders (ord_no, ord_date, total_price, id)

VALUES (order_sequence.NEXTVAL, '11-NOV-02', 200, '1000');

INSERT INTO orders (ord_no, ord_date, total_price, id)

VALUES (order_sequence.NEXTVAL, '15-NOV-02', 315, '1001');

Create the ‘product_sequence’ sequence in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that the sequence was created.

CREATE SEQUENCE product_sequence START WITH 3000 INCREMENT BY 1;

Create the ‘products’ table in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that the table was created.

CREATE TABLE products (

pno NUMBER(5),

pdesc VARCHAR2(40),

price NUMBER(5,2),

onhand NUMBER(3),

weight NUMBER(3)

);

Populate the ‘products’ table in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that four rows were inserted into the table.

INSERT INTO products(pno, pdesc, price, onhand, weight)

VALUES (product_sequence.NEXTVAL, 'Stanley Hammer HX200', 10, 100, 1);

INSERT INTO products(pno, pdesc, price, onhand, weight)

VALUES (product_sequence.NEXTVAL, 'Skil Drill Pack DR900', 20, 100, 2);

INSERT INTO products(pno, pdesc, price, onhand, weight)

VALUES (product_sequence.NEXTVAL, 'B and D Jigsaw JZ500', 50, 200, 2);

INSERT INTO products(pno, pdesc, price, onhand, weight)

VALUES (product_sequence.NEXTVAL, 'B and D Table Saw TS300', 100, 10, 5);

Create the ‘ordprod_sequence’ sequence in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that the sequence was created.

CREATE SEQUENCE ordprod_sequence START WITH 4000 INCREMENT BY 1;

Create the ‘ordprods’ table in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that the table was created.

CREATE TABLE ordprods (

bridgeid NUMBER(5),

quantity NUMBER(3),

subtotal NUMBER(5,2),

shipdate DATE,

status VARCHAR2(2),

ord_no NUMBER(5),

pno NUMBER(5)

);

Populate the ‘ordprods’ table in the ‘SQL Worksheet’. The ‘Script Output’ window should indicate that five rows were inserted into the table.

INSERT INTO ordprods (bridgeid, quantity, subtotal, shipdate, status, ord_no, pno)

VALUES (ordprod_sequence.NEXTVAL, 2, 20, '25-OCT-02', 'SP', 2000, 3000);

INSERT INTO ordprods (bridgeid, quantity, subtotal, shipdate, status, ord_no, pno)

VALUES (ordprod_sequence.NEXTVAL, 1, 20, '25-OCT-02', 'SP', 2000, 3001);

INSERT INTO ordprods (bridgeid, quantity, subtotal, shipdate, status, ord_no, pno)

VALUES (ordprod_sequence.NEXTVAL, 3, 150, '28-OCT-02', 'SP', 2000, 3002);

INSERT INTO ordprods (bridgeid, quantity, subtotal, shipdate, status, ord_no, pno)

VALUES (ordprod_sequence.NEXTVAL, 2, 200, '28-OCT-02', 'SP', 2001, 3003);

INSERT INTO ordprods (bridgeid, quantity, subtotal, shipdate, status, ord_no, pno)

VALUES (ordprod_sequence.NEXTVAL, 2, 100, '28-OCT-02', 'SP', 2001, 3002);

Make all changes permanent by executing a ‘COMMIT;’. The ‘Script Output’ window should indicate that all changes are ‘committed’.

COMMIT;

Now that the data are in the database, three PHP files are used – ‘dbGeneral.php’, ‘drop.php’, and ‘see_it.php’ – to build a simple report generator to retrieve the full name of a customer.

PHP file ‘dbGeneral.php’ was introduced in Chapter 3, but I include it here for your convenience. Be sure to add your username (line 19), password (line 20), and server host (line 21) supplied by your tutor or IT expert in the ‘setParms()’ method (lines 17–22).

 1 <?php

 2 // File dbGeneral.php

 3 class dbGeneral

 4 {

 5 private $_schema;

 6 private $_password;

 7 private $_host;

 8 private $_query;

 9 private $_conn;

10 public $result;

11 function __construct($sql)

12 {

13 $this->_query = $sql;

14 $this->setParms();

15 $this->connDB();

16 }

17 function setParms()

18 {

19 $this->_schema = '';

20 $this->_password = '';

21 $this->_host = '';

22 }

23 function connDB()

24 {

25 if(!$this->_conn = oci_connect

26 ($this->_schema,$this->_password,$this->_host))

27 { echo 'error connecting'; }

28 }

29 function parse()

30 {

31 if(!$parse = oci_parse($this->_conn,$this->_query))

32 { echo 'error parsing'; }

33 else

34 { $this->result = $parse; }

35 }

36 function bind($bind,$choice,$length)

37 {

38 oci_bind_by_name($this->result,$bind,$choice,$length);

39 }

40 function exe()

41 {

42 oci_execute($this->result);

43 }

44 }

45 ?>

PHP file ‘drop.php’ creates a dynamic drop-down menu based on the data with a JavaScript event handler that submits the form once the user makes a selection.

 1 <?php

 2 // File drop.php

 3 require_once 'dbGeneral.php';

 4 $query = "SELECT * FROM customers";

 5 $connect = new dbGeneral($query);

 6 $connect->parse();

 7 $stmt = $connect->result;

 8 $connect->exe();

 9 ?>

10 <html><body><div style="text-align:center;">

11 <form name="form_drop" method="post"

12 action="see_it.php">

13 <select name="choice"

14 onchange="document.form_drop.submit()">

15 <option value=999>CHOOSE BELOW</option>

16 <?php

17 while($row = oci_fetch_assoc($stmt))

18 {

19 $id = $row['ID'];

20 $val = $row['LAST_NAME'];

21 echo "<option value='$id'>$id";

22 echo "    $val</option>\n";

23 }

24 ?>

25 </select>

26 </form>

27 </div></body></html>

The logic begins by including ‘dbGeneral’ (line 3). A query is set (line 4). Next, a new instance of ‘dbGeneral’ is created (line 5). The query is parsed and executed (lines 6–8). The form (lines 11–26) uses method ‘post’ to send results to ‘see_it.php’. A drop-down menu is created (lines 13–23). Drop-down selections (options) are dynamically generated in the ‘while’ loop based on the query (lines 17–23). A JavaScript event handler (line 14) submits the choice once it is selected from the drop-down menu.

 1 <?php

 2 // File see_it.php

 3 require_once 'dbGeneral.php';

 4 $choice = $_POST['choice'];

 5 $query = "SELECT * FROM customers ";

 6 $query .= "WHERE id=:choice";

 7 $connect = new dbGeneral($query);

 8 $connect->parse();

 9 $connect->bind(':choice', $choice, 4);

10 $stmt = $connect->result;

11 $connect->exe();

12 $row = oci_fetch_assoc($stmt);

13 $first = $row['FIRST_NAME'];

14 $last = $row['LAST_NAME'];

15 echo "<div style='text-align:center;'>";

16 echo "$first $last<br /></div>";

17 ?>

18 <html>

19 <p><div style="text-align:center;">

20 <form method="post" action="drop.php">

21 <input type="submit" value="back">

22 </form>

23 </div></html>

PHP file ‘see_it.php’ displays the drop-down box and the full name of the choice made by the user. The user’s choice from the drop-down menu is posted (line 4). A query is set (lines 5 and 6). A new instance of ‘dbGeneral’ is created (line 7). The query is parsed and executed (lines 8–11). The bind variable is reconciled (line 9). Be sure to set the appropriate length of the bind variable value, which in this case is 4 (line 9). The result set is placed in ‘$row’ (line 12). Values of first and last name are retrieved (lines 13 and 14) and displayed (line 16). A button is created that, when pressed, returns processing to ‘drop.php’ (lines 20–22).

Load ‘drop.php’ into a browser. Figure 4.30 shows the drop-down box. Click the down arrow and choose ‘1002 Mungus’. Figure 4.31 shows the results. Click the ‘back’ button to return to the drop-down box web page.

Figure 4.30

Figure 4.30 Display of Dynamically Derived Drop-Down Menu

Figure 4.31

Figure 4.31 Display of Choice Made with ‘back’ Button

Sophisticated Report Generation Application

Let’s build an even more sophisticated report generator. Three PHP files are used – ‘dbGeneral.php’, ‘rpt.php’, and ‘getTbl.php’ – to build a report generator for customer, order, product, and ordprod data. PHP file ‘dbGeneral.php’ is the same. PHP file ‘rpt.php’ consists of a fixed HTML drop-down menu with a JavaScript event handler that submits the form once a choice has been made.

 1 <?php

 2 // File rpt.php

 3 ?>

 4 <html><body bgcolor = BURLYWOOD>

 5 <body><div style="text-align:center;">

 6 <form method="POST" action="getTbl.php">

 7 <font color=BLUE size=+1>Select a Report:  </font>

 8 <select name="rpt" onchange="submit()"; >

 9 <option value=999 selected="SELECTED">CHOOSE BELOW</option>

10 <option value=1>Customer Report</option>

11 <option value=2>Order Report</option>

12 <option value=3>Product Report</option>

13 <option value=4>Ordprod Report</option>

14 </select>

15 </form>

16 <p></p></div></body></html>

The drop-down menu provides fixed options for reports (lines 9–13). The JavaScript event handler (line 8) submits the form to ‘getTbl.php’ (line 6) once a choice is made.

 1 <?php

 2 // File getTbl.php

 3 ?>

 4 <html><head><style type="text/css">

 5 table.center {

 6 margin-left:auto;

 7 margin-right:auto;

 8 }

 9 th.c1 { background-color: #FFB6C1; color: black; }

10 tr.d1 td { background-color: #DCDCDC; color: black; }

11 tr.d2 td { background-color: #FFFFFF; color: black; }

12 </style></head><body style="background-color:burlywood;">

13 <?php

14 require_once 'dbGeneral.php';

15 $rpt = $_POST["rpt"];

16 if ($rpt == 1)

17 { $query = "SELECT * FROM customers ORDER BY id"; }

18 else if ($rpt == 2)

19 { $query = "SELECT * FROM orders ORDER BY ord_no"; }

20 else if ($rpt == 3)

21 { $query = "SELECT * FROM products ORDER BY pno"; }

22 else if ($rpt == 4)

23 { $query = "SELECT * FROM ordprods ORDER BY bridgeid"; }

24 $connect = new dbGeneral($query);

25 $connect->parse();

26 $stmt = $connect->result;

27 $connect->exe();

28 $ncols = OCINumCols($stmt);

29 echo "<table class='center' border='1'><tr>";

30 for ($i = 1; $i <= $ncols; ++$i)

31 {

32 echo "<th class='c1'>";

33 echo OCIColumnName($stmt, $i);

34 echo "</th>";

35 }

36 echo "</tr>";

37 $j = 0;

38 while($row = oci_fetch_assoc($stmt))

39 {

40 if ($j % 2) {echo "<tr class='d1'>";}

41 else {echo "<tr class='d2'>";}

42 for ($i = 1; $i <= $ncols; ++$i)

43 {

44 echo "<td>";

45 $columnName = OCIColumnName($stmt, $i);

46 echo $row[$columnName];

47 echo "</td>";

48 }

49 ++$j;

50 echo "</tr>";

51 }

52 echo "</table><p><div style='text-align:center;'>";

53 echo "<form action='rpt.php'>";

54 echo "<input type='submit' value='back' /></form></div>";

55 ?>

56 </body></html>

PHP file ‘getTbl.php’ gets the appropriate data based on the choice made by the user and displays the report. If the choice is ‘1’, the ‘CUSTOMER’ table is queried (lines 16 and 17). If the choice is ‘2’, the ‘ORDERS’ table is queried (lines 18 and 19). If the choice is ‘3’, the ‘PRODUCTS’ table is queried (lines 20 and 21). If the choice is ‘4’, the ‘ORDPRODS’ table is queried (lines 22 and 23). Oracle API ‘OCINumCols’ (line 28) returns the number of columns from the chosen database table. Oracle API ‘OCIColumnName’ (line 33) returns the column name from the chosen database table. Lines 30–35 display the field names from the chosen table. Lines 38–51 display the data from the chosen table. The modulus ‘%’ (line 40) is used to alternate colors in the table. CSS (lines 9–11) was created to adjust the colors. Finally, a form is created (lines 53 and 54), which includes a ‘submit’ button (line 54) to redirect to ‘rpt.php’ when pressed. Load ‘rpt.php’ in a browser. Figure 4.32 shows the drop-down box. Click the down arrow and choose ‘Product Report’. Figure 4.33 shows the results. Use the ‘back’ button to return to the drop-down box web page.

Figure 4.32

Figure 4.32 Display of Report Drop-Down Menu

Figure 4.33

Figure 4.33 Display of Chosen Report with ‘back’ Button

Report Generation Application Using Ref Cursors

An alternative way to create a report generator is to use PL/SQL ‘Ref Cursors’. A ‘Ref Cursor’ is a special PL/SQL data type that creates a cursor variable at run time. One advantage is the ability of a ‘Ref Cursor’ to pass result sets. A second advantage is speed because PL/SQL is precompiled and is thereby faster than interpreting an SQL statement. A third advantage is security because ‘Ref Cursors’ are protected by the Oracle system.

The first step is to create a PL/SQL ‘Ref Cursor’. Open an ‘Oracle SQL Developer’ connection. In an ‘SQL Worksheet’, create the ‘Ref Cursor’ procedure ‘tbl_ref’ with the following PL/SQL. Run the script. ‘Script Output’ should indicate that the procedure was compiled.

1 CREATE OR REPLACE PROCEDURE tbl_ref

2 (p_refcur OUT SYS_REFCURSOR)

3 IS

4 BEGIN

5 OPEN p_refcur FOR SELECT * FROM customers;

6 END;

The parameter ‘p_refcur’ is defined as ‘OUT’ (line 2), which means that its value is returned to the calling environment. The type definition is ‘SYS_REFCURSOR’ (line 2), which means that it will hold a cursor (data set). An ‘OPEN’ is issued (line 5), which places the results of the SELECT statement into ‘p_refcur’.

The second step is to modify the ‘dbGeneral’ class by adding a ‘bind_refcursor()’ method. Don’t forget to add your information in the ‘setParms()’ method.

 1 <?php

 2 // File dbGeneral.php

 3 class dbGeneral

 4 {

 5 private $_schema;

 6 private $_password;

 7 private $_host;

 8 private $_query;

 9 private $_conn;

10 public $result;

11 function __construct($sql)

12 {

13 $this->_query = $sql;

14 $this->setParms();

15 $this->connDB();

16 }

17 function setParms()

18 {

19 $this->_schema = '';

20 $this->_password = '';

21 $this->_host = '';

22 }

23 function connDB()

24 {

25 if(!$this->_conn = oci_connect

26 ($this->_schema,$this->_password,$this->_host))

27 { echo 'error connecting'; }

28 }

29 function parse()

30 {

31 if(!$parse = oci_parse($this->_conn,$this->_query))

32 { echo 'error parsing'; }

33 else

34 { $this->result = $parse; }

35 }

36 function bind($bind,$choice,$length)

37 {

38 oci_bind_by_name($this->result,$bind,$choice,$length);

39 }

40 function exe()

41 {

42 oci_execute($this->result);

43 }

44 function bind_refcursor()

45 {

46 $curs = oci_new_cursor($this->_conn);

47 $this->parse();

48 oci_bind_by_name

49 ($this->result,':data',$curs,-1,OCI_B_CURSOR);

50 $this->exe();

51 oci_execute($curs);

52 $this->result = $curs;

53 }

54 }

55 ?>

The ‘bind_refcursor()’ method (lines 44–53) handles the ref cursor reconciliation process. This process is beyond the scope of this book, so I won’t cover it here. Just use the method as it is presented.

The third step is to create PHP file ‘drop_ref.php’, which creates a drop-down box using data retrieved from the ‘Ref Cursor’.

 1 <?php

 2 // File drop_ref.php

 3 require_once 'dbGeneral.php';

 4 $query = "BEGIN tbl_ref(:data); END;";

 5 $connect = new dbGeneral($query);

 6 $connect->bind_refcursor();

 7 $stmt = $connect->result;

 8 ?>

 9 <html><body><div style="text-align:center;">

10 <form name="form_drop" method="post"

11 action="see_it_ref.php">

12 <select name="choice"

13 onchange="document.form_drop.submit()">

14 <option value=999>CHOOSE BELOW</option>

15 <?php

16 while($row = oci_fetch_assoc($stmt))

17 { $id = $row['ID'];

18 $val = $row['LAST_NAME'];

19 echo "<option value='$id'>$id";

20 echo "    $val</option>\n"; }

21 ?>

22 </select>

23 </form>

24 </div></body></html>

The query (line 4) calls the PL/SQL procedure ‘tbl_ref’ with a ‘BEGIN–END’ block. The ‘bind_refcursor()’ method (line 6) binds the ‘Ref Cursor’ to the PHP program, which makes the result set available. The form ‘posts’ data to ‘see_it_ref.php’ (lines 10 and 11). The drop-down menu (lines 12–20) is dynamically created from the result set generated from the ref cursor.

The fourth step is to create PHP file ‘see_it_ref.php’, which displays the results.

 1 <?php

 2 // File see_it_ref.php

 3 require_once 'dbGeneral.php';

 4 $choice = $_POST['choice'];

 5 $query = "SELECT * FROM customers WHERE id=:choice";

 6 $connect = new dbGeneral($query);

 7 $connect->parse();

 8 $connect->bind(':choice', $choice, 4);

 9 $stmt = $connect->result;

10 $connect->exe();

11 $row = oci_fetch_assoc($stmt);

12 $first = $row['FIRST_NAME'];

13 $last = $row['LAST_NAME'];

14 echo "<div style='text-align:center; '>";

15 echo "$first $last<br /></div>";

16 ?>

17 <html>

18 <p><div style="text-align:center;">

19 <form method="post" action="drop_ref.php">

20 <input type="submit" value="back">

21 </form>

22 </div></html>

The query (line 5) is created based on the choice from the drop-down menu (line 4). A new instance of ‘dbGeneral’ is created (line 6). The query is parsed and executed (lines 7–10). The result set is reconciled (line 11). Finally, the results are displayed (lines 12 and 13).

The final step is to load ‘drop_ref.php’ in a browser. Figure 4.34 shows the drop-down box. Click the down arrow and choose ‘1002 Mungus’. Figure 4.35 shows the results.

Figure 4.34

Figure 4.34 Display of Dynamically Derived Drop-Down Menu Using REF CURSOR

Figure 4.35

Figure 4.35 Display of Choice Made with ‘back’ Button

The results are the same as the simple report generation application. The only difference is how the data is retrieved. In the original report generation application, an SQL query is used to build the result set. In this application, a PL/SQL ‘Ref Cursor’ is used to build the result set. The ‘Ref Cursor’ application is more complicated, but more efficient and secure because the PL/SQL procedure is protected inside the Oracle database. Go through each of the applications in this chapter line by line to ensure that you understand what the code is accomplishing.

Summary

The goal of this chapter was to help you gain a fundamental understanding of input validation to mitigate potentially harmful data from entering a website. Another goal was to learn how to create a safe report generation application. The process I use in this chapter to build applications is the one I use when I have to tackle a ‘real-world’ programming project. Of course, you don’t have to follow my methodology, but I have tested it several times in the past and it has never failed me.