AJAX form submission with customized server field validation

Julian Hansen
CERTIFIED EXPERT
Published:
Updated:
This article discusses how to implement server side field validation and display customized error messages to the client.

Overview


This article presents a method for validating form data in a PHP script using custom server side validation rules. The example presented uses AJAX to submit data to the script but the same method can be used for normal form submissions. I have chosen to use AJAX as that makes the most sense for form processing. If the data in a form is not correct you want the user to stay on the page and correct the data. Round tripping to the server to validate and then re-render a page that was already perfectly rendered is wasteful, complicated and just plain unnecessary. With an AJAX request we pass the form data to the server and get back a list of errors. If no errors are returned the form is good - otherwise we display the errors, in this case, adjacent to the field that caused the error but you can put them wherever it suites you. 


Let's start with a basic Bootstrap form with three fields for First Name, Last Name and Email address. I have chosen Bootstrap as this is widely used framework that includes out-of-the-box styles for things like alerts in forms. It is not necessary to use Bootstrap - you can roll your own alerts according to your application requirements.


The HTML

<form class="form" action="validate.php">
    <div class="row">
        <label for="firstname" class="col-md-3">First Name</label>
        <div class="col-md-9">
            <input type="text" name="firstname" id="firstname" class="form-control" />
        </div>
        <label for="lastname" class="col-md-3">Last Name</label>
        <div class="col-md-9">
            <input type="text" name="lastname" id="lastname" class="form-control" />
        </div>
        <label for="email" class="col-md-3">Email</label>
        <div class="col-md-9">
            <input type="text" name="email" id="email" class="form-control" />
        </div>
        <button class="pull-right btn btn-primary">Send</button>
    </div>
</form>


The AJAX

The form is going to be submitted via AJAX - the following code will generically submit a form using AJAX. It retrieves the target from the action attributed of the form.


<script>
$(function() {
    $('form').submit(function(e) {
        // PREVENT DEFAULT SUBMIT
        e.preventDefault();

        // GOOD PRACTICE
        var frm = this;
      
        var data = $(frm).serialize();
        var action = $(frm).attr('action');
        $.ajax({
            url: action,
            data: data,
            type: 'POST',
            dataType: 'JSON'
        }).done(function(resp) {
            if (resp.status) {
                $(frm).html(resp.message);
            }
            else {
                $('#response').html(resp.message);
            }
        });
    });
});
</script>


What about client side validation? While not the subject of this article, client side validation is an important component in the user interaction process. It allows us to correct user errors before going back to the server. However, it is not a replacement for server side validation. The server must assume that all input is potentially harmful - it cannot assume that the client side validation was performed.


The PHP {validate.php}

On the server we could have something like this:


<?php
$response = new stdClass;
$response->status = false;
$response->message = "Invalid parameters";
if ($_POST) {
    $email = isset($_POST['email']) ? $_POST['email'] : false;
    $firstname = isset($_POST['firstname']) ? $_POST['firstname'] : false;
   
    if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $response->status = true;
        $response->message = "Thank you!";
        // Do any other work here for valid submissions like sending an email
        // and / or logging to a database
    }
    else {
        $response->message = "Invalid email";
    }
}
// For completeness we add the JSON header
header('Content-Type: application/json');
// Terminate the script and output the JSON string
// We could also use echo json_encode($response) I use die
// to eliminate the possibility that any other data can be sent
// after the JSON string.
die(json_encode($response));


That should give the basics of a generic AJAX form submission.


However, it is not as useful as it could be - we might want to validate multiple fields and see status messages for each of them.


To do that let's make some changes. Firstly, lets make the form names array references instead. The reason for this will be explained later.


HTML

<form class="form" action="validate.php">
    <div class="row">
        <label for="firstname" class="col-md-3">First Name</label>
        <div class="col-md-9">
            <input type="text" name="data[firstname]" id="firstname" class="form-control" />
        </div>
        <label for="lastname" class="col-md-3">Last Name</label>
        <div class="col-md-9">
            <input type="text" name="data[lastname]" id="lastname" class="form-control" />
        </div>
        <label for="email" class="col-md-3">Email</label>
        <div class="col-md-9">
            <input type="text" name="data[email]" id="email" class="form-control" />
        </div>
        <button class="pull-right btn btn-primary">Send</button>
    </div>
</form>


On the server we can now access all of our form variables by accessing the data array. Because our variables are all in the data array we can easily retrieve the form-only variables from the post and pass them to our validation. You could simply pass the $_POST array to the validation function with the same result but if there happen to be other fields in the POST that you don't want in the outgoing email or database entry then this method allows you to isolate the variable you do want from any other control variables (CSRF, Captcha etc).


PHP

<?php
$data = isset($_POST['data']) ? $_POST['data'] : false;
if ($data &amp;&amp; is_array($data) {
  // we have valid form input
}


To validate each field lets define an array that contains the field names as keys each pointing to a sub-array. 

Each sub-array has at least a message and optionally other parameters that determine how the field must be validated. The array below uses three different validation methods

  • Check for existence only and optionally a length
  • Use a regex pattern to verify validity
  • Specifically validate an email address


$validate = array (
    'firstname' => array('msg' => 'Please enter a valid first name', 'type' => 'exist', 'length' => 3),
    'lastname' => array('msg' => 'Please enter a valid last name', 'type' => 'regex', 'pattern' => '/^[a-zA-Z\-]{3,}$/'),
    'email' => array('msg' => 'Please enter a valid email address', 'type' => 'email')
);

Note: I have used different rules for firstname and lastname so that I can illustrate how different fields can have different validation rules - in reality these two fields would probably share the same rule.


Validation vs Sanitation

A short sidebar on Validation vs Sanitation. Validating data is making sure incoming input matches the rules for data required by your business logic / model. We to see that the correct data has been retrieved for each field. 

Sanitation is the removal of any potential malicious data from the submission. While there is often overlap between these two necessary server side processes - they are distinctly different. In this article we are specifically looking at validation.


Let's define how each of these validation options will be processed.


<?php
//...
// CODE TO MANAGE CLIENT INTERACTION LEFT OUT FOR THIS SNIPPET
// ...
// THE VALIDATION CONTROL FUNCTION. TAKES AN ARRAY OF DATA 
// EXTRACTED FROM THE $_POST AND A VALIDATION ARRAY AGAINST WHICH
// TO VALIDATE THE EXTRACTED DATA
function validate($data, $validation)
{
    // WE ONLY NEED TO CHECK THE VALIDATION ARRAY AND THEN USE
    // KEYS TO SEE IF THE VALUE EXISTS IN THE DATA ARRAY

    // OUR RETURN ARRAY - ASSUME THE BEST
    $error = array();

    // CHECK EACH VALIDATION ENTRY AGAINST DATA
    foreach($validation as $k => $v) {

        // DOES THE DATA ITEM EXIST?
        $valid = isset($data[$k]);

        // IF SO CHECK THE RULES
        if ($valid) {
            // CUSTOM VALIDATION HAPPENS HERE. WE MAKE A FUNCTION NAME OUT OF
            // THE type VALUE AND THEN CALL THE FUNCTION (IF IT EXISTS) WITH
            // WITH OUR FIELD DATA AND THE SUB-ARRAY FOR THIS ENTRY
            if (isset($v['type'])) {
                $function = "validate_{$v['type']}";
                if (function_exists($function)) {

                    // VALIDATION FUNCTIONS RETURN true / false
                    // WE USE RETURN TO TEST VALIDITY
                    $valid = $function($data[$k], $v);
                }
            }
           
            // IF NO TYPE IS DEFINED WE ASSUME EXISTENCE IS SUFFICIENT
        }
       
        // IF VALIDATION FAILED ADD THE ERROR MESSAGE (BY CONTROL ID) TO THE
        // ARRAY WE ARE GOING TO RETURN TO THE CALLING PROCESS
        if (!$valid) {
            // USE THE ERROR IN THE SUB-ARRAY (IF PRESENT) OTHERWISE USE DEFAULT
            $error[$k] = isset($v['msg']) ? $v['msg'] : 'Please enter a value';
        }
    }

    // RETURN ERRORS (IF ANY)
    return $error;
}


// CHECK FOR EXISTENCE ONLY AND OPTIONALLY LENGTH
function validate_exist($item, $v)
{
    // Get length if defined otherwise any length will do
    $length = isset($v['length']) ? $v['length'] : 0;
    return (strlen($item) >= $length);
}


// VALIDATE BY REGULAR EXPRESSION
function validate_regex($item, $v)
{
    // WAS A PATTERN DEFINED? IF SO USE IT, OTHERWISE MATCH ANYTHING
    $pattern = isset($v['pattern']) ? $v['pattern'] : '/.*/';

    return preg_match($pattern, $item);
}


// VALIDATE EMAIL - WE COULD HAVE USED A REGEX FOR THIS BUT WE
// WILL USE THE filter_var() FUNCTION TO DO IT HERE
function validate_email($item, $v)
{
    return filter_var($item, FILTER_VALIDATE_EMAIL) !== false;
}


All that we need on the server is to add the code that collects the form data, sends it for validation and reports the result.


<?php
$response = new stdClass;

// ASSUME A NEGATIVE OUTCOME 
$response->status = false;
$response->message = "Invalid parameters";

// GET OUR FORM DATA SAFELY
$data = isset($_POST['data']) ? $_POST['data'] : false;

// CHECK DATA IS VALID
if ($data && is_array($data)) {
    // WE HAVE FORM INPUT SO ASSUME THE BEST AND 
    // SET A SUCCESS MESSAGE. THIS ONLY GETS USED IF THERE ARE NO ERRORS.
    $response->message = "Thank you for your input.";
   
    // THIS IS OUR VALIDATION ARRAY INDEXED BY FIELD
    $validate = array (
        'firstname' => array('msg' => 'Please enter a valid first name', 'type' => 'exist', 'length' => 3),
        'lastname' => array('msg' => 'Please enter a valid last name', 'type' => 'regex', 'pattern' => '/^[a-zA-Z\-]{3,}$/'),
        'email' => array('msg' => 'Please enter a valid email address', 'type' => 'email')
    );
   
    // GET ANY FIELD ERRORS THAT MAY EXIST
    $response->errors = validate($data, $validate);
   
    // STATUS IS SET BY WHETHER THERE WERE ANY ERRORS OR NOT
    $response->status = empty($response->errors);

    // IF NO ERRORS THEN WE CAN PROCESS THE FORM
    if ($response->status) {
      // DO VALID FORM PROCESSING HERE: SEND EMAIL, WRITE TO DB
    }
}


// RETURN OUR STATUS ARRAY
die(json_encode($response));



What we should get back in our AJAX call is a JSON object with errors and field id's so we can display custom errors or process valid form.


Here is how we handle the return in our jQuery code

jQuery

<script>
$(function() {
    $('form').submit(function(e) {
        // PREVENT DEFAULT SUBMIT
        e.preventDefault();
       
        // JUST GOOD PRACTICE
        var frm = this;
       
        // SERIALIZE THE FORM DATA
        var data = $(frm).serialize();
       
        // GET THE DEFAULT FORM ACTION
        var action = $(frm).attr('action');
       
        // REMOVE ANY EXISTING .alert
        $('.alert').remove();
       
        // AJAX TO THE SERVER
        $.ajax({
            url: action,
            data: data,
            type: 'POST',
            dataType: 'JSON'
        }).done(function(resp) {
            // ALL IS WELL SO REPLACE THE FORM WITH THE
            // SUCCESS MESSAGE
            if (resp.status) {
                $(frm).html(resp.message);
            }
            else {
                // LOOP THROUGH ERRORS AND FOR EACH CREATE A <div>
                // WTIH THE MESSAGE AND ADD IT AFTER THE <input>
                for(var i in resp.errors) {
                    var error = $('<div/>', {class: 'alert alert-danger'}).html(resp.errors[i]);
                    $('#' + i).after(error);
                }
            }
        });
    });
});
</script>

That's it - the basics of an AJAX form with customised validation and return error messages


Here is a screen shot of a failed validation attempt

The above shows that there was a problem with the firstname and the email address. Let's look at the console (F12) to see what was returned.


From the above we can see that the return status was "false". You will notice there is a success message in the return. This is because the return object by default is a success and we override it when a validation fails. 

The error array shows the fields that failed and the message to show for each field.


A full listing for reproducing the above is shown below


HTML

<!doctype html>
<html>
<title>AJAX, jQuery form submission with server side field validation</title>
<head>
</head>
<body>
<form class="form" action="validate.php">
    <div class="row">
        <label for="firstname" class="col-md-3">First Name</label>
        <div class="col-md-9">
            <input type="text" name="data[firstname]" id="firstname" class="form-control" />
        </div>
        <label for="lastname" class="col-md-3">Last Name</label>
        <div class="col-md-9">
            <input type="text" name="data[lastname]" id="lastname" class="form-control" />
        </div>
        <label for="email" class="col-md-3">Email</label>
        <div class="col-md-9">
            <input type="text" name="data[email]" id="email" class="form-control" />
        </div>
        <button class="pull-right btn btn-primary">Send</button>
    </div>
</form>
<script src="http://code.jquery.com/jquery.js"></script>
<script>
$(function() {
    $('form').submit(function(e) {
        // PREVENT DEFAULT SUBMIT
        e.preventDefault();
       
        var data = $(this).serialize();
        var frm = this;
        var action = $(frm).attr('action');
        $('.alert').remove();
        $.ajax({
            url: action,
            data: data,
            type: 'POST',
            dataType: 'JSON'
        }).done(function(resp) {
            if (resp.status) {
                $(frm).html(resp.message);
            }
            else {
                for(var i in resp.errors) {
                    var error = $('<div/>', {class: 'alert alert-danger'}).html(resp.errors[i]);
                    $('#' + i).after(error);
                }
            }
        });
    });
});
</script>


PHP Listing  {validate.php}

<?php
$response = new stdClass;
$response->status = false;
$response->message = "Invalid parameters";
$data = isset($_POST['data']) ? $_POST['data'] : false;
if ($data &amp;&amp; is_array($data)) {
    // we have valid form input
    $response->message = "Thank you for your input.";
    $validate = array (
        'firstname' => array('msg' => 'Please enter a valid first name', 'type' => 'exist', 'length' => 3),
        'lastname' => array('msg' => 'Please enter a valid last name', 'type' => 'regex', 'pattern' => '/^[a-zA-Z\-]{3,}$/'),
        'email' => array('msg' => 'Please enter a valid email address', 'type' => 'email')
    );
   
    $response->errors = validate($data, $validate);
    $response->status = empty($response->errors);
}
die(json_encode($response));


function validate($data, $validation)
{
    // we only need to check the validation array and
    // then use thekeys to see if the values exist in the data array
    $error = array();
    foreach($validation as $k => $v) {
        $valid = isset($data[$k]);
        if ($valid) {
            if (isset($v['type'])) {
                $function = "validate_{$v['type']}";
                if (function_exists($function)) {
                    $valid = $function($data[$k], $v);
                }
            }
        }
        if (!$valid) {
            $error[$k] = isset($v['msg']) ? $v['msg'] : 'Please enter a value';
        }
    }
   
    return $error;
}


function validate_exist($item, $v)
{
    $length = isset($v['length']) ? $v['length'] : 0;
    return strlen($item) >= $length;
}


function validate_regex($item, $v)
{
    $pattern = isset($v['pattern']) ? $v['pattern'] : '/.*/';
    return preg_match($pattern, $item);
}


function validate_email($item, $v)
{
    return filter_var($item, FILTER_VALIDATE_EMAIL) !== false;
}


6
5,911 Views
Julian Hansen
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.