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:
- An Ubuntu 20.04 server.
- A sudo user.
- A LAMP Stack.
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.