- The live usage example is hosted here: http://rttfantasy.com/validatetest3.php.
- I've also created a live example using the GET method so it's easier to play with the input, hosted here: http://rttfantasy.com/validatetest4.php.
- The class itself (with much nicer whitespace) is here: http://rttfantasy.com/validateclass.phps.
The procedural methods check(), checkInputArrays(), and the trivial errorPrefix() don't need much attention. The methods I want the most feedback on are the actual validation methods: checkInteger(), checkString(), checkFloat(), and checkEmail().
After talking with a friend, I think the biggest weakness is the regex themselves. Can anyone give feedback on the possibility of breaking them?
Class Explanation:
The comments are decent, but I'll try to explain more in depth what the class should do:
The constructor is blank, since some of the public methods are useful on their own.
The check() method is the main feature of the class. It takes 5 arrays for parameters: 'nameArray' should be names of the values to be checked, 'valueArray' is the values themselves, 'typeArray' is the type of value (string, integer, float, or email), and 'minArray'/'maxArray' store the min and max length (for strings) or values (for int/float).
check() method returns true if all validation checks passed, false otherwise.
check() method performs simple validation on the input arrays themselves, then validates the values provided. It populates three arrays in the object, all keyed by names from 'nameArray':
The 'validated' array: filled with boolean 'false' if the name's value failed validation, 'true' if it passed.
The 'error' array: filled a zero-length string if the name's value passed validation, or with error messages from validation.
The 'result' array: filled with the sanitized values, or a zero-length string if the name's value failed validation.
NOTE: the 'error' and 'result' arrays will also contain the key '_validated'. In 'result', the value for this key will be false if the input array validation failed, true if passed; In 'error', the value for this key will be a zero-length string if the input array validation passed.
The methods errorPrefix(), checkInputArrays(), checkInteger(), checkString(), checkFloat(), and checkEmail() are all used within check().
Conclusion
I hope that a few of you will take the time to poke at this code and give me some comments or advice. Criticism is very welcome. Thank you in advance.
I hereby release this code into the public domain, and disclaim any copyright or liability. It is provided "As Is" with no warranty of any kind.
Code:
Code: Select all
<?php
// CLASS DEFINITION
/*** Validator class
* What we need this class to do:
* Take unsanitized values, validate them, sanitize them,
* and return errors or the sanitized value
*/
class Validator
{
// boolean error exists array
public $validated = array();
// error array
public $error = array();
// sanitized result values array
public $result = array();
// constructor
public function __construct()
{
}
/* This method loads the values and checks them
* It will set $this->result['_validated'] == 0 if the input arrays did not pass simple validation, 1 if it did,
* and set $this->error['_validated'] to a descriptive string about the simple array validation check, or any invalid type string
* It will populate $this->result with sanitized results, keyed by names from $nameArray,
* and will populate $this->error with any errors that result from validation, keyed by names from $nameArray.
* It will populate $this->validated with 1 for each validation that passed, 0 for failed, keyed by names from $nameArray.
* It will return false if validation errors occurred, true if everything passed.
*
* For validation, it requires the methods checkInputArrays(), checkInteger(), checkString(), checkFloat(), and checkEmail()
* For error formatting, it requires the method errorPrefix()
*
* method expects 5 arrays, indexed by integers:
* array 1: name of variable from calling script
* array 2: value to be validated
* array 3: type (integer, string, float, email) that the variable should be
* array 4: minimum length (str) /value (int,float)
* array 5: maximum length (str) /value (int,float)
*/
public function check($nameArray, $valueArray, $typeArray, $minArray, $maxArray)
{
// clear the error and result arrays
$this->error = array('_validated'=>'');
$this->result = array('_validated'=>false);
// check input arrays, cancel execution of validation routine if problems are found
try {
$this->checkInputArrays($nameArray, $valueArray, $typeArray, $minArray, $maxArray);
// loop through $nameArray, validating each id
foreach ($nameArray as $id=>$name) {
// initialize result and error array entries, keyed by name of input variable
$this->result[$name] = '';
$this->error[$name] = '';
// find which type the variable should be, and check the values with the particular method
switch ($typeArray[$id]) {
case 'integer':
try {
$integer = $this->checkInteger($valueArray[$id], $minArray[$id], $maxArray[$id]);
$this->result[$name] = $integer;
$this->validated[$name] = true;
} catch (Exception $e) {
$this->error[$name] = $this->errorPrefix($name) . $e->getMessage();
$this->validated[$name] = false;
}
break;
case 'string':
try {
$string = $this->checkString($valueArray[$id], $minArray[$id], $maxArray[$id]);
$this->result[$name] = $string;
$this->validated[$name] = true;
} catch (Exception $e) {
$this->error[$name] = $this->errorPrefix($name) . $e->getMessage();
$this->validated[$name] = false;
}
break;
case 'float':
try {
$float = $this->checkFloat($valueArray[$id], $minArray[$id], $maxArray[$id]);
$this->result[$name] = $float;
$this->validated[$name] = true;
} catch (Exception $e) {
$this->error[$name] = $this->errorPrefix($name) . $e->getMessage();
$this->validated[$name] = false;
}
break;
case 'email':
try {
$email = $this->checkEmail($valueArray[$id]);
$this->result[$name] = $email;
$this->validated[$name] = true;
} catch (Exception $e) {
$this->error[$name] = $this->errorPrefix($name) . $e->getMessage();
$this->validated[$name] = false;
}
break;
default:
$this->result['_validated'] = false;
$this->validated[$name] = false;
$this->error['_validated'] = $this->errorPrefix($name) . 'Invalid type specified';
}
}
// clear memory used by foreach loop
unset($id);
unset($name);
$this->result['_validated'] = true;
// see if there were any validation failures, return false it happens
foreach ($this->validated as $value) {
if (!$value) {
return false;
}
}
unset($value);
// return true if all the values passed validation
return true;
} catch (Exception $e) {
$this->error['_validated'] = $e->getMessage();
return false;
}
}
/* This method takes a variable name, and returns the formatted prefix for the error
* Used in the check() method
*/
private function errorPrefix($varName)
{
return 'For var \'' . $varName . '\', error was: ';
}
/* This method checks the five arrays given to the check() method and validates them
* Throws exceptions if checks fail
* Returns true if checks pass, false otherwise
*/
public function checkInputArrays($nameArray, $valueArray, $typeArray, $minArray, $maxArray)
{
// check that the input values are arrays
if ( is_array($nameArray)
&& is_array($valueArray)
&& is_array($typeArray)
&& is_array($minArray)
&& is_array($maxArray)) {
// check that the size of $nameArray is 1 or more, and that the size of each array matches $nameArray
$nameCount = count($nameArray);
if ( $nameCount > 0
&& $nameCount == count($valueArray)
&& $nameCount == count($typeArray)
&& $nameCount == count($minArray)
&& $nameCount == count($maxArray)) {
$ok = true;
foreach ($nameArray as $id=>$name) {
if (is_string($name)) {
if (is_string($typeArray[$id])) {
// if type is email, we don't care about the min or max
if ( is_float($minArray[$id])
|| is_int($minArray[$id])
|| $typeArray[$id] == 'email'
) {
// BEWARE, FLIPPED LOGIC:
if (!(
is_float($minArray[$id])
|| is_int($maxArray[$id])
|| $typeArray[$id] == 'email'
)) {
throw new Exception('Max field for name \'' . $name . '\' is not a float or integer');
$ok = false;
break;
}
} else {
throw new Exception('Min field for name \'' . $name . '\' is not a float or integer');
$ok = false;
break;
}
} else {
throw new Exception('Type field for name \'' . $name . '\' is not a string');
$ok = false;
break;
}
} else {
throw new Exception('Name field in array index \'' . $id . '\' is not a string');
$ok = false;
break;
}
}
// clear memory used by foreach loop
unset($id);
unset($value);
// if everything's ok, return true
if ($ok) {
return true;
}
} else {
throw new Exception('Array length mismatch or empty name array');
return false;
}
} else {
throw new Exception('One or more parameters is not an array');
return false;
}
}
/* This method checks $input to make sure it is an integer,
* is not less than $min, and is not more than $max
*/
public function checkInteger($input, $min, $max)
{
if ($min <= $max) {
if ($input == strval(intval($input))) {
$input = intval($input);
if ($input >= $min) {
if ($input <= $max) {
return $input;
} else {
throw new Exception('More than Max');
}
} else {
throw new Exception('Less than Min');
}
} else {
throw new Exception('Not an Integer');
}
} else {
throw new Exception('Min/Max values not sane');
}
}
/* This method checks $input to make sure it is a string,
* is not shorter than $min characters, and is not more than $max characters,
* and that it contains only the allowed characters:
* a-z, A-Z, 0-9, space, period, comma, !, ?, @, single quote, double quote, parens, slash, hyphen
*/
public function checkString($input, $min, $max)
{
if ($min <= $max) {
if (strlen($input) >= $min) {
if (strlen($input) <= $max) {
if (preg_match('/^[a-zA-Z0-9 \.,\!\?@\'"\(\)\/\-]+$/', $input)) {
return $input;
} else {
throw new Exception('Contains illegal characters');
}
} else {
throw new Exception('Longer than Max');
}
} else {
throw new Exception('Shorter than Min');
}
} else {
throw new Exception('Min/Max values not sane');
}
}
/* This method checks $input to make sure it is a float,
* is not less than $min, and is not more than $max
*/
public function checkFloat($input, $min, $max)
{
if ($min <= $max) {
if ($input == strval(floatval($input))) {
$input = floatval($input);
if ($input >= $min) {
if ($input <= $max) {
return $input;
} else {
throw new Exception('More than Max');
}
} else {
throw new Exception('Less than Min');
}
} else {
throw new Exception('Not a Float');
}
} else {
throw new Exception('Min/Max values not sane');
}
}
/* This method checks $input to make sure it is a proper email address
*/
public function checkEmail($input)
{
$input = trim(strtolower($input));
if (preg_match('/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $input)) {
return $input;
} else {
throw new Exception('Invalid email address');
}
}
}
?>
Code: Select all
<?php
error_reporting(E_ALL);
require_once 'validateclass.php';
$sanitized = array('email' => '', 'hometown' => '', 'age' => '', 'height' => '');
$errorDisplay = '';
$results = '';
if (isset($_POST['submit'])) {
$nameArray = array('email', 'hometown', 'age', 'height');
$valueArray = array($_POST['email'], $_POST['hometown'], $_POST['age'], $_POST['height']);
$typeArray = array('email', 'string', 'integer', 'float');
$minArray = array(null, 3, 13, 0.9);
$maxArray = array(null, 30, 90, 2.5);
$validator = new Validator();
// check if validation was successful
if ($validator->check($nameArray, $valueArray, $typeArray, $minArray, $maxArray)) {
// successful
$results = 'The Email Address received was: ' . $validator->result['email'] . '<br />' . "\n";
$results .= 'The Home Town received was: ' . $validator->result['hometown'] . '<br />' . "\n";
$results .= 'The Age received was: ' . $validator->result['age'] . '<br />' . "\n";
$results .= 'The Height in meters received was: ' . $validator->result['height'] . '<br />' . "\n";
} else {
// unsuccessful
$errorDisplay .= 'Errors:<br />' . "\n";
foreach ($validator->error as $value) {
if ($value != '') {
$errorDisplay .= $value . '<br />' . "\n";
}
}
unset($value);
$sanitized = $validator->result;
}
}
$markup = <<<EOT
<html>
<head>
<title>Validator Test</title>
</head>
<body>
<p>$errorDisplay</p>
<p>
<form name='test' method='post' action='./validatetest3.php'>
<table>
<tr><td><b>Email Address:</b></td><td><input name='email' type='text' maxlength='100' value="{$sanitized['email']}" /></td><td> </td></tr>
<tr><td><b>Home Town:</b></td><td><input name='hometown' type='text' maxlength='100' value="{$sanitized['hometown']}" /></td><td>(minimum 3 characters, max 30)</td></tr>
<tr><td><b>Age:</b></td><td><input name='age' type='text' maxlength='100' value="{$sanitized['age']}" /></td><td>(minimum 13, maximum 90)</td></tr>
<tr><td><b>Height:</b></td><td><input name='height' type='text' maxlength='100' value="{$sanitized['height']}" /></td><td>(in meters, minimum 0.9, maximum 2.5)</td></tr>
<tr><td> </td><td><input type='submit' name='submit' value='Submit' /></td><td> </td></tr>
</table>
</form>
</p>
<p>
$results
</p>
</body>
</html>
EOT;
echo $markup;
?>
A form with 4 fields: Email Address, Home Town, Age, and Height.
If all fields pass validation, form fields should be blank, and received values should be shown below the form.
If one or more fields fail validation, errors will display above the form, and form fields will be populated with the values that passed validation.
Updates:
1. 2010-04-02: Added explanation.
2. 2010-04-03: Fixed bugs in edge cases of min/max on checkFloat(), checkInteger(), and checkString().
3. 2010-04-03: Added usage example
4. 2010-04-03: Added live link to hosted usage example
5. 2010-04-05: Fixed email validation regex, using the regex here: http://www.regular-expressions.info/email.html
6. 2010-04-07: Updated hosted example to reflect changes, added hosted class file that has much nicer whitespace than you pull off the forum.