Create a Central PHP Data Validator Class

Updated on February 25, 2021
Create a Central PHP Data Validator Class header image

Introduction

It's essential to validate untrusted user data before processing when you're working in any PHP CRUD (Create, Read, Update, and Delete) project. Data supplied by end-users must adhere to your organization's set constraints, objectives, and business logic. Untrusted data is especially hazardous in e-commerce or bank-related applications.

In this guide, you'll code a PHP validation logic on Ubuntu 20.04 server using procedural inline code that takes a top-down approach to validate data. Later, you'll learn how to save time and create a clean reusable Validator class that implements the Object-Oriented Programming (OOP) method to check the correctness of data in your application.

Prerequisites

To follow along with this guide, make sure you have the following:

Set up a sample_db Database

Begin by connecting to your server and enter the command below to log in to MySQL as root.

sudo mysql -u root -p

When prompted, key-in the root password of your MySQL server and press Enter to proceed. Then, run the command below to create a sample_db database.

mysql> CREATE DATABASE sample_db;

Next, create a non-root MySQL user. You'll use the credentials of this user to connect to your MySQL server from the PHP code.

mysql> CREATE USER 'test_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'EXAMPLE_PASSWORD';
mysql> GRANT ALL PRIVILEGES ON sample_db.* TO 'test_user'@'localhost';
mysql> FLUSH PRIVILEGES;

Switch to the sample_db database.

mysql> USE sample_db;

Next, create a contacts table. After validating data, any user-inputted values that pass your PHP validation logic will be permanently saved to the MySQL server in this table.

mysql> CREATE TABLE contacts
       (
           contact_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
           first_name VARCHAR(50),
           last_name VARCHAR(50),
           phone VARCHAR(50),
           email VARCHAR(25)
       ) ENGINE = InnoDB;

For now, don't enter any data into the contacts table. You'll do this in the next step using the Linux curl command. Exit from the MySQL command-line interface.

mysql> QUIT;

Create a Validator Code Block Using a Procedural Method

In this step, you'll code a PHP script that takes some inputs and passes them in a validation logic to check the data's correctness. Create a new /var/www/html/contacts.php using nano.

sudo nano /var/www/html/contacts.php

Next, open a new PHP tag and add a text/html header.

<?php

header("Content-Type: text/html");

Then, open a new try { block and enter your database variables depending on the users' credentials that you created earlier. Next, connect to the MySQL server using the PHP PDO class.

try {

   $db_name     = 'sample_db';
   $db_user     = 'test_user';
   $db_password = 'EXAMPLE_PASSWORD';
   $db_host     = 'localhost';

   $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
   $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Next, initialize an empty $error variable. This variable will be populated if any errors are encountered.

   $error = '';

Then enter the code block below to validate the first_name and the last_name fields. In the code below, you're checking if the inputted data has values set for both the first_name and last_name fields. If not, you're appending a new error to the $error variable that you've initiated above. Also, if the inputted has the first_name or the last_name variables defined, you're checking if the defined data contains valid alphanumeric values using the PHP preg_match function.

   if (!isset($_POST['first_name'])) {
       $error .= "Please enter a value for the 'first_name' field.\n";
   } else {
       if (preg_match('/^[a-z0-9 .\-]+$/i', $_POST['first_name']) == false) {
           $error .= "The 'first_name' field must contain a valid value.\n";
       }
   }

   if (!isset($_POST['last_name'])) {
       $error .= "Please enter a value for the 'last_name' field.\n";
   }  else {
       if (preg_match('/^[a-z0-9 .\-]+$/i', $_POST['last_name']) == false) {
           $error .= "The 'last_name' must contain a valid value.\n";
       }
   }

In the same manner, check if the inputted data has the phone field defined. If this is the case, use the PHP preg_match function to check for the phone number's validity.

   if (!isset($_POST['phone'])) {
       $error .= "Please enter a value for the 'phone' field.\n";
   }  else {
       if (preg_match('/^[0-9 \-\(\)\+]+$/i', $_POST['phone']) == false) {
           $error .= "The 'phone' field must contain a valid value.\n";
       }
   }

Finally, check if the email field is set. In case the inputted data contains a value for this field, use the PHP filter_var function to check the email address's validity.

   if (!isset($_POST['email'])) {
       $error .= "Please enter a value for the 'email' field.\n";
   }  else {
       if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) == false) {
           $error .= "IThe 'email' field must contain a valid value.\n";
       }
   }

Once the data goes through the validation logic, examine the value of the $error variable you initiated earlier. If this variable is not empty, it means some validation errors occurred. In that case, echo out the errors to the application user and stop further code processing.

   if ($error != '') {
       echo "Validation failed, plese review the following errors:\n" . $error. "\n";
       exit();
   }

In case the data passes the validation logic, enter the code below to create a parameterized query and save the data to the database using the PHP PDO class.

   $sql = 'insert into contacts
          (
          first_name,
          last_name,
          phone,
          email
          )
          values
          (
          :first_name,
          :last_name,
          :phone,
          :email
          )
          ';

   $data = [
           'first_name' => $_POST['first_name'],
           'last_name'  => $_POST['last_name'],
           'phone'      => $_POST['phone'],
           'email'      => $_POST['email']
           ];

   $stmt = $pdo->prepare($sql);
   $stmt->execute($data);

   echo " The record was saved without any validation errors.\n";

In case the PDO class encounters errors, catch and echo them out.

   } catch (PDOException $e) {
        echo 'Database error. ' . $e->getMessage();
   }

When completed, your /var/www/html/contacts.php file should be similar to the content below.

<?php

header("Content-Type: text/html");

try {

   $db_name     = 'sample_db';
   $db_user     = 'test_user';
   $db_password = 'EXAMPLE_PASSWORD';
   $db_host     = 'localhost';

   $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
   $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

   $error = '';

   if (!isset($_POST['first_name'])) {
       $error .= "Please enter a value for the 'first_name' field.\n";
   } else {
       if (preg_match('/^[a-z0-9 .\-]+$/i', $_POST['first_name']) == false) {
           $error .= "The 'first_name' field must contain a valid value.\n";
       }
   }

   if (!isset($_POST['last_name'])) {
       $error .= "Please enter a value for the 'last_name' field.\n";
   }  else {
       if (preg_match('/^[a-z0-9 .\-]+$/i', $_POST['last_name']) == false) {
           $error .= "The 'last_name' must contain a valid value.\n";
       }
   }

   if (!isset($_POST['phone'])) {
       $error .= "Please enter a value for the 'phone' field.\n";
   }  else {
       if (preg_match('/^[0-9 \-\(\)\+]+$/i', $_POST['phone']) == false) {
           $error .= "The 'phone' field must contain a valid value.\n";
       }
   }

   if (!isset($_POST['email'])) {
       $error .= "Please enter a value for the 'email' field.\n";
   }  else {
       if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) == false) {
           $error .= "The 'email' field must contain a valid value.\n";
       }
   }

   if ($error != '') {
       echo "Validation failed, plese review the following errors:\n" . $error. "\n";
       exit();
   }

   $sql = 'insert into contacts
          (
          first_name,
          last_name,
          phone,
          email
          )
          values
          (
          :first_name,
          :last_name,
          :phone,
          :email
          )
          ';

   $data = [
           'first_name' => $_POST['first_name'],
           'last_name'  => $_POST['last_name'],
           'phone'      => $_POST['phone'],
           'email'      => $_POST['email']
           ];

   $stmt = $pdo->prepare($sql);
   $stmt->execute($data);

   echo " The record was saved without any validation errors.\n";

   } catch (PDOException $e) {
        echo 'Database error. ' . $e->getMessage();
   }

Save and close the file by pressing Ctrl + X, then Y and Enter. Once you're through creating the file, you'll test the validation logic in the next step.

Test the Validation Logic

Use the Linux curl command to test if the /var/www/html/contacts.php file is working as expected. Run the command below to send valid $_POST data to the file.

curl --data "first_name=JOHN&last_name=DOE&phone=1222222&email=john_doe@example.com" http://localhost/contacts.php

You should get the output below that confirms the data is 100% valid according to your validation code.

The record was saved without any validation errors.

Next, try to violate the rules of your validation logic by typing the command below.

curl  http://localhost/contacts.php

Since you've not sent any $_POST data, you'll get the errors below.

Validation failed, please review the following errors:
Please enter a value for the 'first_name' field.
Please enter a value for the 'last_name' field.
Please enter a value for the 'phone' field.
Please enter a value for the 'email' field.

Again, try to send a request to the same URL but send empty $_POST values this time around.

curl --data "first_name=&last_name=&phone=&email=" http://localhost/contacts.php

Since no valid values will be detected, you should receive the following errors.

Validation failed, please review the following errors:
The 'first_name' field must contain a valid value.
The 'last_name' must contain a valid value.
The 'phone' field must contain a valid value.
The 'email' field must contain a valid value.

As you can see from the outputs above, the validation logic is working as expected. However, the procedural validation code you've used on this file is not recommended since it does not allow code re-use. To put this into perspective, if you're working on a project that requires a hundred CRUD modules, you'll be repeating the same validation code in each file.

To overcome this repetition and promote proper code re-use, you'll learn how to code a centralized PHP Validator class in the next step.

Create a Central PHP Validator Class

Use nano to open a new Validator.php class in the root directory of your webserver. Since this is a class file, it is recommended to declare it in PascalCase. That is, capitalize the first letter of each compound word and use an underscore(_) separator. Since your class name has a single word, name it Validator.php.

sudo nano /var/www/html/Validator.php

Start by declaring a new Validator class.

<?php

class Validator
{

Then, create a validate function inside the class. When you create a function within a class, it's called a method. Your new validate method takes two arguments. The first parameter($params) takes an array of the source data that requires validation. In this case, you will pass the HTTP $_POST array here. Then, use the $validation_rules variable to pass a named array of all the validation rules you want to check.

    public function validate($params, $validation_rules)
    {

Then inside a try { block declare an empty $response variable. You'll append any errors during the validation logic execution to this variable.

        try {

            $response = '';

Loop through the $validation_rules array to send each field and its required validations to the validation logic.

            foreach ($validation_rules as $field => $rules)
            {

For each field split, the validations required using the PHP explode function. When calling this class, you'll separate a single field's validation rules using the vertical bar | separator. If a rule is defined as required by the calling file and the field is not defined, return the The field_name is required. error.

                foreach (explode('|', $rules) as $rule) {
                    if ($rule == 'required' && array_key_exists($field, $params) == false) {
                        $response .= "The " . $field ." is required.\n";
                    }

Otherwise, if a field being checked exists and some additional rules defined(e.g. alphanumeric|phone|email), continue to check the data's validity.

                    if (array_key_exists($field, $params) == true){
                        if ($rule == 'alphanumeric' && preg_match('/^[a-z0-9 .\-]+$/i', $params[$field]) == false) {
                            $response .= "The value of " . $field . " is not a valid alphanumeric value.\n";
                        }

                        if ($rule == 'phone' && preg_match('/^[0-9 \-\(\)\+]+$/i', $params[$field]) == false) {
                            $response .= "The value of " . $field . " is not a valid phone number.\n";
                        }

                        if ($rule == 'email' && filter_var($params[$field], FILTER_VALIDATE_EMAIL) == false) {
                            $response .= "The value of " . $field . " is not a valid email value.\n";
                        }
                    }
                }
            }

When you're through checking all the fields and their defined rules, return the response to the calling file. The $response should now contain all the validation errors encountered, if any. Otherwise, it will be a blank string value.

        return $response;

Return any general errors if encountered.

        } catch (Exception $ex) {
     return $e->getMessage();
 }
    }
 }

When completed, your /var/www/html/Validator.php file should be similar to the below content.

<?php

class Validator
{
    public function validate($params, $validation_rules)
    {
        try {

            $response = '';

            foreach ($validation_rules as $field => $rules)
            {
                foreach (explode('|', $rules) as $rule) {
                    if ($rule == 'required' && array_key_exists($field, $params) == false) {
                        $response .= "The " . $field ." is required.\n";
                    }

                    if (array_key_exists($field, $params) == true){
                        if ($rule == 'alphanumeric' && preg_match('/^[a-z0-9 .\-]+$/i', $params[$field]) == false) {
                            $response .= "The value of " . $field . " is not a valid alphanumeric value.\n";
                        }

                        if ($rule == 'phone' && preg_match('/^[0-9 \-\(\)\+]+$/i', $params[$field]) == false) {
                            $response .= "The value of " . $field . " is not a valid phone number.\n";
                        }

                        if ($rule == 'email' && filter_var($params[$field], FILTER_VALIDATE_EMAIL) == false) {
                            $response .= "The value of " . $field . " is not a valid email value.\n";
                        }
                    }
                }
            }

        return $response;

        } catch (Exception $ex) {
     return $e->getMessage();
 }
    }
 }

Save and close the file. In this next step, you'll modify the /var/www/html/contacts.php file to use the new Validator class. Please note that you can extend the rules in this class depending on your application's demands. For instance, you can add new logic to check currency values, such as restricting negative numbers.

Create the New Validator Class

First, delete the old /var/www/html/contacts.php file.

sudo rm /var/www/html/contacts.php

Then, open the file again using nano.

sudo nano /var/www/html/contacts.php

Enter the information below to the /var/www/html/contacts.php file.

<?php

header("Content-Type: text/html");

try {

   $db_name     = 'sample_db';
   $db_user     = 'test_user';
   $db_password = 'EXAMPLE_PASSWORD';
   $db_host     = 'localhost';

   $pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
   $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

   require_once 'Validator.php';

   $validator = new Validator();
   $validation_rules = [
                       'first_name' => 'required|alphanumeric',
                       'last_name'  => 'required|alphanumeric',
                       'phone'      => 'required|phone',
                       'email'      => 'required|email',
                       ];

   $error = $validator->validate($_POST, $validation_rules);

   if ($error != ''){
       echo "Validation failed, plese review the following errors:\n" . $error. "\n";
       exit();
   }


   $sql = 'insert into contacts
          (
          first_name,
          last_name,
          phone,
          email
          )
          values
          (
          :first_name,
          :last_name,
          :phone,
          :email
          )
          ';

   $data = [
           'first_name' => $_POST['first_name'],
           'last_name'  => $_POST['last_name'],
           'phone'      => $_POST['phone'],
           'email'      => $_POST['email']
           ];

   $stmt = $pdo->prepare($sql);
   $stmt->execute($data);

   echo " The record was saved without any validation errors.\n";

   } catch (PDOException $e) {
        echo 'Database error. ' . $e->getMessage();
   }

Save and close the file. In the above file, you've replaced the procedural code with a cleaner syntax that calls the Validator class. You include the class using the PHP require_once statement and the new function.

   ...
   require_once 'Validator.php';
   $validator = new Validator();
   ...

Next, you're defining an array with the field names and their associated validations separated by the vertical bar |.

   ...
   $validation_rules = [
                       'first_name' => 'required|alphanumeric',
                       'last_name'  => 'required|alphanumeric',
                       'phone'      => 'required|phone',
                       'email'      => 'required|email',
                       ];
   ...

Next, you're calling the validate method inside the Validator class and passing the array of field names($_POST variables in this case) to be evaluated alongside the validation rules. If a validation error is encountered, you're echoing it out.

   ...

   $error = $validator->validate($_POST, $validation_rules);



   if ($error != ''){
       echo "Validation failed, plese review the following errors:\n" . $error. "\n";
       exit();
   }
   ...

Once you've coded the Validator class, you'll run some tests in the next step to see if it works as expected.

Test the New Validator Class

Use the Linux curl command to send valid data to the contacts.php file that you've just edited.

curl --data "first_name=MARY&last_name=ROE&phone=7777777&email=mary_roe@example.com" http://localhost/contacts.php

Since the data passes all the validation logic, you should get the output below.

 The record was saved without any validation errors.

Next, try calling the same URL without declaring any $_POST variables.

curl http://localhost/contacts.php

You should now get an error showing that the fields are mandatory.

Validation failed, please review the following errors:
The first_name is required.
The last_name is required.
The phone is required.
The email is required.

Define the fields but this time around, leave them blank.

curl --data "first_name=&last_name=&phone=&email=" http://localhost/contacts.php

Again, validation should fail and you should get an error for each independent field.

Validation failed, please review the following errors:
The value of first_name is not a valid alphanumeric value.
The value of last_name is not a valid alphanumeric value.
The value of phone is not a valid phone number.
The value of email is not a valid email value.

From the outputs above, the Validator class is working as expected. Double-check that the clean data was actually saved to the database by logging back to the MySQL server.

sudo mysql -u root -p

Then, enter your root password and press Enter to continue. Then switch to the sample_db database.

mysql> USE sample_db;

Next, run the command below to retrieve the data from the contacts table.

mysql> SELECT
       contact_id,
       first_name,
       last_name,
       phone,
       email FROM contacts
       ;

You should now get two records as shown below with valid data.

+------------+------------+-----------+---------+----------------------+
| contact_id | first_name | last_name | phone   | email                |
+------------+------------+-----------+---------+----------------------+
|          1 | JOHN       | DOE       | 1222222 | john_doe@example.com |
|          2 | MARY       | ROE       | 7777777 | mary_roe@example.com |
+------------+------------+-----------+---------+----------------------+
2 rows in set (0.00 sec)

Your Validator class is working as expected and you can include it in any file by using the PHP require_once statement.

Conclusion

In this guide, you've tested both the procedural and Objected OOP validation logic in PHP on your Ubuntu 20.04 server. The latter method is cleaner, easy to understand, and maintain. Consider using the OOP validation logic in all your PHP programming projects to check data's validity from external sources.