Please wait

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:

register.php
<?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:

  1. Username - A text field.
  2. Age - A number field.
  3. Password - A password text field.
  4. Account Type - A radio field with two options.
  5. 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.

register.php
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:

  1. string $text - The string to validate.
  2. int $min - The minimum character length for the string.
  3. 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.

register.php
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:

register.php
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:

register.php
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.

register.php
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:

register.php
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.

Comments

Please read this before commenting