Data Validation
So far, we've talked about validation by using the isset()
function. That's almost never enough. You'll have various fields with different requirements. Let's imagine we had a file called register.php
with the following contents:
<?php
declare(strict_types=1);
$accountTypes = ["checking", "savings"];
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Validation code here
}
?>
<form action="register.php" method="POST">
<label>Username:</label>
<input type="text" name="username" />
<label>Age:</label>
<input type="number" name="age" />
<label>Password:</label>
<input type="password" name="password" />
<label>Account Type:</label>
<?php foreach($accountTypes as $option) : ?>
<?php echo $option; ?>
<input type="radio" name="account" value="<?php echo htmlspecialchars($option); ?>" />
<?php endforeach; ?>
<label>
Sign up for newsletter <input type="checkbox" name="newsletter" value="yes" />
<button type="submit">Submit</button>
</form>
In the above example, we have five fields:
- Username - A text field.
- Age - A number field.
- Password - A password text field.
- Account Type - A radio field with two options.
- Newsletter - A checkbox field.
For the account type field, we're looping through an array. On each iteration, a radio button is displayed where the value
attribute is set to the value from the array. We're wrapping the value with the htmlspecialchars()
function for extra precaution.
Above the form, we can check for a form submission by checking the $_SERVER['REQUEST_METHOD']
variable. After doing so, we'll start validating the fields.
Pay close attention to the $errors
variable. Initially, it'll be an empty array. If a field fails validation, this array will be populated with error messages.
Character Length Validation
The first field is for the username. For this field, let's enforce a character limit. Character limits can be enforced by checking the string's length. If the length is below or above a threshold, the validation will fail.
Validation is not always unique to a specific field. For this reason, it's recommended to define a function that can validate any value. For example, here's a function called validateLength
.
function validateLength(string $text, int $min = 0, int $max = 999) : bool {
$length = mb_strlen($text);
return $length >= $min && $length <= $max;
}
This function has three parameters:
string $text
- The string to validate.int $min
- The minimum character length for the string.int $max
- The maximum character length for the string.
We're allowing for the thresholds to be configurable. Inside the function, we're grabbing the length of the string with the mb_strlen()
function and then comparing it against the thresholds. As long as the length is within the range, the function returns true
. Otherwise, false
return is returned.
Here's how we can use it.
if (!validateLength($_POST['username'], 3, 60) ) {
$errors[] = "Username must be between 3 - 60 characters";
}
If the validateLength()
function returns false
, a message gets pushed into the $errors
array.
Above our form, we can display those errors with the following:
<ul>
<?php foreach($errors as $error) : ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
Number Validation
The second field is for the age. Validating numbers is similar to validating strings. Typically, you will want to make sure a number is within a range. Here's a basic example of a function that does so:
function validateNumber(int|float $num, int $min = 0, int $max = 999) : bool {
return $num >= $min && $num <= $max;
}
In this example, we're accepting the number as the first argument. The data type is a union type int|float
.
Next, we can use the function like so:
if (!validateNumber((int) $_POST['age'], 18, 100)) {
$errors[] = "You must be between 18 - 100 years old.";
}
Pay close attention to the first argument. By default, browsers submit input data as strings. Despite using the number
type, the value gets formatted as a string. Therefore, if you were to pass on the $_POST['age']
variable to the validateNumber()
function as-is, you would receive an error. To avoid errors, typecast the value into an int
type.
Validate Passwords
The third field is a password. For passwords, developers prefer to use regular expressions. Every site has different rules for passwords. For our example, we'll write a regular expression to allow the following:
- Lowercase letters
- Uppercase letters
- Numbers
In addition, we're going to set a minimum character length of 5 characters. Here's a function for validating passwords.
function validatePassword(string $password) {
$regex = "/[a-zA-z0-9]/";
return mb_strlen($password) >= 5 && preg_match($regex, $password);
}
As you can see, we're using the mb_strlen()
function to check the length and applying a regular expression with the preg_match()
function.
Afterward, we can use it like so:
if (!validatePassword($_POST['password'])) {
$errors[] = "Invalid password.";
}
Validating Radio Buttons
Radio buttons only allow for one selection to be made. To validate these fields, we can use the in_array()
function. This function has two arguments. The first is the value to search, and the second is an array of valid values. It'll return a boolean based on if the first argument can be found in the array.
We don't have to define a special function for validating radio buttons. The in_array()
function is more than sufficient.
if (!in_array($_POST['account'] ?? "", $accountTypes)) {
$errors[] = "Invalid selection.";
}
In the above example, we're using the ??
operator before passing on a value. If the user does not select an option, browsers do not submit the field with the request. That's important to understand. Even if the form was submitted, not all fields will appear in the $_POST
or $_GET
variables. Always check to see if a radio button input has a value before validating it.
Validating Checkboxes
Checkboxes are similar to radio buttons. If a checkbox is not checked, browsers do not send the field with the form submission. Therefore, to validate it, you simply just have to check if it was set with the isset()
function.
In our form, we have a checkbox for a newsletter. If we want to check if the box was ticked, we can do something like this:
if (!isset($_POST['newsletter']) || $_POST['newsletter'] !== 'yes') {
$errors[] = "Did not sign up for newsletter";
}
In this example, we're performing two conditions. Firstly, we're checking if the field can be found in the form submission with the isset()
function. Secondly, we're checking if the $_POST['newsletter']
variable is equal to 'yes'
.
Prefill Fields
While not required, it's common practice to prefill fields with their original values after an invalid form submission. You can do so by setting the value
attribute on an <input>
element like so:
<input type="text" name="username" value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>" />
For radio buttons and checkboxes, things are slightly different. A radio button or checkbox can be preselected by adding the checked
attribute.
<input type="radio" name="account" value="<?php echo htmlspecialchars($option); ?>"
<?php echo isset($_POST['account']) && $_POST['account'] === $option ? 'checked': '' ?>>
In this example, we're using the isset()
function and comparing the submitted value with the $option
variable, which stores the value in the current iteration of the loop. If both conditions are met, the checked
attribute is rendered. Otherwise, nothing appears.
Complete Example
<?php
declare(strict_types=1);
$accountTypes = ["checking", "savings"];
$errors = [];
function validateLength(string $text, int $min = 0, int $max = 999) :bool {
$length = mb_strlen($text);
return $length >= $min && $length <= $max;
}
function validateNumber(int|float $num, int $min = 0, int $max = 999) : bool {
return is_numeric($num) && $num >= $min && $num <= $max;
}
function validatePassword(string $password) {
$regex = "/[a-zA-z0-9]/";
return mb_strlen($password) >= 5 && preg_match($regex, $password);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Validation code here
if (!validateLength($_POST['username'], 3, 60) ) {
$errors[] = "Username must be between 3 - 60 characters";
}
if (!validateNumber((int) $_POST['age'], 18, 100)) {
$errors[] = "You must be between 18 - 100 years old.";
}
if (!validatePassword($_POST['password'])) {
$errors[] = "Invalid password.";
}
if (!in_array($_POST['account'] ?? "", $accountTypes)) {
$errors[] = "Invalid selection.";
}
if (!isset($_POST['newsletter']) || $_POST['newsletter'] !== 'yes') {
$errors[] = "Did not sign up for newsletter";
}
}
?>
<ul>
<?php foreach($errors as $error) : ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<form action="" method="POST">
<label>Username:</label>
<input type="text" name="username" value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>" />
<label>Age:</label>
<input type="number" name="age" <?php echo htmlspecialchars($_POST['age'] ?? ''); ?>/>
<label>Password:</label>
<input type="password" name="password" />
<label>Account Type:</label>
<?php foreach($accountTypes as $option) : ?>
<?php echo $option; ?>
<input type="radio" name="account" value="<?php echo htmlspecialchars($option); ?>"
<?php echo isset($_POST['account']) && $_POST['account'] === $option ? 'checked': '' ?>>
<?php endforeach; ?>
<label>
Sign up for newsletter <input type="checkbox" name="newsletter" value="yes"
<?php echo isset($_POST['newsletter']) && $_POST['newsletter'] === 'yes' ? 'checked': '' ?>/>
<button type="submit">Submit</button>
</form>
Prefilling the password
You'll notice that every field is prefilled except for the password. For security reasons, this is the only field that should never be prefilled to prevent the password from being viewable. You should always ask a user to refill the password field on failed submissions.
Exercise
The regular expression we've written for the password is pretty basic. As an exercise, try updating the regular expression to allow for special characters, such as $
or #
.
Key Takeaways
- It's considered good practice to define functions for validating any value. You shouldn't design a function to validate a specific field.
- Passwords are typically validated with regular expressions.
- Radio buttons can be validated by checking if the value submitted can be found within an array of possible options.
- Prefilling fields provides for a better user experience. All fields should be prefilled except for sensitive fields, such as passwords.