Back-end validation with PHP

                                
                                    public function sendMessage($firstName,$lastName,$email,$telephone,$subject,$message){
                                    global $form, $dbConnection;

                                    /* Validate inputs */

                                    // firstName error checking
                                    $field = "firstName";  
                                    if(!$firstName || strlen($firstName = trim($firstName)) == 0){
                                        $form->setError($field, "* First name is required");
                                    }
                                    else{
                                        $firstName = stripslashes($firstName);
                                        if(strlen($firstName) < 2){
                                            $form->setError($field, "* First name too short");
                                        }
                                        else if(strlen($firstName) > 35){
                                            $form->setError($field, "* First name too long");
                                        }
                                        /* Check if name is not alphanumeric */
                                        else if(!preg_match("/^[a-zA-Z-\s']*$/", $firstName)){
                                            $form->setError($field, "* Please enter a valid first name");
                                        }            
                                    }
                            
                            
                                    // Email error checking 
                                    $field = "email"; 
                                    if(!$email || strlen($email = trim($email)) == 0){
                                        $form->setError($field, "* Email is required");
                                    }
                                    else{
                                        $email = stripslashes($email);
                                        if(strlen($email) > 254){
                                            $form->setError($field, "* Email too long");
                                        }
                                        /* Check if valid email address */
                                        if(!preg_match("/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/", $email)){
                                            $form->setError($field, "* Please enter a valid email");
                                        }            
                                    }
                                
                                    // Telephone error checking 
                                    $field = "telephone"; 
                                    if(!$telephone || strlen($telephone = trim($telephone)) == 0){
                                        $form->setError($field, "* Telephone is required");
                                    }
                                    else{
                                        $telephone = stripslashes($telephone);
                                        if(strlen($telephone) < 11){
                                            $form->setError($field, "* Telephone too short");
                                        }
                                        else if(strlen($telephone) > 13){
                                            $form->setError($field, "* Telephone too long");
                                        }
                                        /* Check if name is not alphanumeric */
                                        else if(!preg_match("/^(((\+44\s?\d{4}|\(?0\d{4}\)?)\s?\d{3}\s?\d{3})|((\+44\s?\d{3}|\(?0\d{3}\)?)\s?\d{3}\s?\d{4})|((\+44\s?\d{2}|\(?0\d{2}\)?)\s?\d{4}\s?\d{4}))(\s?\#(\d{4}|\d{3}))?$/", $telephone)){
                                            $form->setError($field, "* Please enter a valid telephone");
                                        }     
                                    }                    

                                    // Message error checking
                                    $field = "message";  
                                    if(!$message || strlen($message = trim($message)) == 0){
                                        $form->setError($field, "* Message is required");
                                    }
                                    else{
                                        $message = stripslashes($message);
                                        if(strlen($message) < 2){
                                            $form->setError($field, "* Message too short");
                                        }
                                        else if(strlen($message) > 2000){
                                            $form->setError($field, "* Message too long");
                                        }
                                        /* Check if message is not alphanumeric */
                                        else if(!preg_match("/^[\w\-\s\.\,]+$/", $message)){
                                            $form->setError($field, "* Message not alphanumeric");
                                        }            
                                    }

                                    // If errors found show form again with error messages, else save to database
                                    if($form->errorCount > 0 ){
                                        return 0; // 0 = have validation errors
                                    }
                                    else{
                                        if($dbConnection->error)
                                        {   
                                            return -1; // -1 = connection error
                                            
                                        }else{
                                            $result = $dbConnection->saveMessage($firstName, $lastName, $email, $telephone, $subject, $message);
                                            if(!$result){
                                                return -2; // -2 = error while saving
                                            }
                                            else{
                                                return 1; // success!
                                            }
                                        }
                                    }
                                }
                                     
                            

This method accepts input fields, and saves it to the database if it passes validation. Otherwise it returns the errors and values back to the form. This method resides inside a "session class" that has other methods such as filterInputs() and is used in conjuction with a "form class" that handle how to return errors and values back to the form.

Front-end validation with Javascript

                                
                                function validate(field, label, required, type){
                                    const inputField = $(`#${field}`);    
                                    const namePattern = /^[a-zA-Z-\s']*$/; 
                                    const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; 
                                    const phonePattern = /^(((\+44\s?\d{4}|\(?0\d{4}\)?)\s?\d{3}\s?\d{3})|((\+44\s?\d{3}|\(?0\d{3}\)?)\s?\d{3}\s?\d{4})|((\+44\s?\d{2}|\(?0\d{2}\)?)\s?\d{4}\s?\d{4}))(\s?\#(\d{4}|\d{3}))?$/; //UK phone regex , 
                                    const textPattern = /^[\w\-\s\.\,]+$/; 
                                    
                                    let error = false;
                                    // If required and empty, show error and return false right away.
                                    if (required && inputField.val() == ""){ 
                                        inputField.addClass('input-field-error');
                                        inputField.siblings('.input-error').text(`${label} is required`);
                                        return false;       
                                    }
                                    
                                    // Check lengths and patterns
                                    // Name
                                    if(type == 'name'){        
                                        if (inputField.val().length > 35) {
                                            error =  `${label} is too long`;
                                        } 
                                        else if (inputField.val().length  < 2) {
                                            error =  `${label} is too short`;
                                        }
                                        else if(!namePattern.test(inputField.val().trim())){
                                            error = `Please enter a valid ${label}`;
                                        }

                                    }
                                    // Email
                                    else if(type == 'email'){
                                        if (inputField.val().length > 254) {
                                            error =  `${label} is too long`;
                                        } 
                                        else if(!emailPattern.test(inputField.val().trim())){
                                            error = `Please enter a valid ${label}`;
                                        } 
                                    }
                                    // Phone
                                    else if(type == 'phone'){
                                        if (inputField.val().length > 13) {
                                            error =  `${label} is too long`;
                                        } 
                                        else if (inputField.val().length  < 11) {
                                            error =  `${label} is too short`;
                                        }
                                        else if(!phonePattern.test(inputField.val().trim())){
                                            error = `Please enter a valid ${label}`;
                                        } 
                                    }
                                    // Subject
                                    else if(type == 'subject'){
                                        if (inputField.val().length > 254) {
                                            error =  `${label} is too long`;
                                        } 
                                        else if (inputField.val().length  < 2) {
                                            error =  `${label} is too short`;
                                        }
                                        else if(!textPattern.test(inputField.val().trim())){
                                            error = `${label} not alphanumeric`;
                                        } 
                                    }

                                    // Show and return result
                                    if(error){
                                        inputField.addClass('input-field-error');
                                        inputField.siblings('.input-error').text(error);
                                        return false;        
                                    }
                                    else{
                                        inputField.removeClass('input-field-error');
                                        inputField.siblings('.input-error').text('');
                                        return true;
                                    }
                                    
                                }
                                     
                            

The validate() method accepts the type of field to be tested and returns true if the input is valid and false otherwise. Input fields are tested for string length and compared to a pattern. I used this together with jQuery on('input') event to fire the validation as the user types on the field.

Laravel Trait

                                
                                    trait SearchTrait
                                    {
                                    public function extractCommonWords($string){
                                        $stopWords = array('i','a','about','an','and','are','as','at','be','by','vs','v','com','de','en','for','from','how','in','is','it','la','of','on','or');   //sample array of stop words
                                        $acronyms = array('ca'=>'court of appeals','nlrc'=>'national labor relations commission','comelec'=>'commission on election', 'up' => 'university of the philippines' ); //sample array of acronymns used

                                        $string = preg_replace('/\s\s+/i', '', $string); // replace whitespace
                                        $string = trim($string); // trim the string
                                        $string = preg_replace('/[^a-zA-Z0-9 -]/', '', $string); // only take alphanumerical characters, but keep the spaces and dashes too…
                                        $string = strtolower($string); // lowercase
                                    
                                        //put words in array
                                        preg_match_all('/\b.*?\b/i', $string, $matchWords);
                                        $matchWords = $matchWords[0];
                                    
                                        //add acronym equivalent
                                        foreach ( $matchWords as $key=>$item ) {
                                            foreach ( $acronyms as $short=>$long ) {
                                                if (strcmp($item,$short) == 0 ) {
                                                    $acroWords =  explode(" ",$long);
                                                    foreach($acroWords as $acroWord){
                                                    array_push ($matchWords, $acroWord);
                                                    }
                                                }
                                            }
                                        }
                                    
                                        //remove all stop words, empty matches and single letters
                                        foreach ( $matchWords as $key=>$item ) {
                                            if ( $item == '' || in_array(strtolower($item), $stopWords) || strlen($item) <= 1 ) {
                                                unset($matchWords[$key]);
                                            }
                                        }
                                    
                                        $wordCountArr = array();
                                        if ( is_array($matchWords) ) {
                                            foreach ( $matchWords as $key => $val ) {
                                                $val = strtolower($val);
                                                if ( isset($wordCountArr[$val]) ) {
                                                    $wordCountArr[$val]++;
                                                } else {
                                                    $wordCountArr[$val] = 1;
                                                }
                                            }
                                        }
                                        arsort($wordCountArr);
                                        return $wordCountArr;
                                    }

                                     
                            

This function filters out unnecessary stop words and replaces acronymns with equivalent values. It is essential as it makes the search string produce more relevant results. The function first removes whitespace and non-alphanumeric characters, then puts words in an array and loops through it to replace the acronymns, then do another loop to remove stop words (and 1 letter words). The last and final loop is used to count the number of occurance of a keyword and store it in an associative array. Finally a sorted array is returned based on the number of occurance of the keyword. I placed this method inside a Laravel Trait so that it can be reused across all controllers by simply including the trait 'SearchTrait'.

Eloquent Query with Elastic Search

                                
                                    public function index(Request $request)
                                    {
                                        // Convert special characters to HTML entities                            
                                        $keyword = htmlspecialchars($request->keyword);
                                        $ponente = htmlspecialchars($request->ponente);
                                        $gr_no = htmlspecialchars($request->gr_no);
                                        $gteyear = htmlspecialchars($request->gteyear);
                                        $lteyear = htmlspecialchars($request->lteyear);
                                        $sort = ($request->sort) ? $request->sort: 0;
                                        $order = $this->getOrder($sort);
                                        $filtered = $this->filtered =  $this->isFiltered($request);
                                
                                        $this->keyword = 0;
                                        $numberOfEntries = 10;                    
                                        
                                        // Define the search rules for Elastic Search
                                        if($keyword || $filtered){

                                        $selectfields = ['id','title', 'ponente', 'gr_no','decision_date','search_title','common_title','slug','landmark','cited_last','cited_count','views'];
                                        $this->search_rules = array();
                                        if($keyword){
                                            $this->search_rules[] = array(
                                                'match' => [
                                                    'title' => [
                                                        'query' => $keyword,
                                                        'boost' => 3,
                                                    ]
                                                ]
                                            );
                                        }
                                
                                        if($gr_no){
                                            $this->search_rules[] = array(
                                                'match' => [
                                                    'gr_no' => [
                                                        'query' => $gr_no,
                                                        'boost' => 3
                                                    ]
                                                ]
                                            );
                                        }
                                        if($ponente){
                                            $this->search_rules[] = array(
                                                'match' => [
                                                    'ponente' => [
                                                        'query' => $ponente,
                                                        'boost' => 3
                                                    ]
                                                ]
                                            );
                                        }
                                        if($gteyear || $lteyear){
                                            $this->search_rules[] = array(
                                                'range' => [
                                                    'decision_date' => [
                                                        'gte' => $gteyear."||/y",
                                                        'lte' => $lteyear."||/y",
                                                        'format' => "yyyy"
                                                    ]
                                                ]
                                            );
                                        }
                                        
                                        // Apply Search Rule
                                        if($keyword){
                                            $query = Decision::search($keyword)->select($selectfields)
                                                ->rule(function($builder) {
                                                    return [
                                                        ($this->keyword && $this->filtered) ? 'must':'should' =>
                                                        [  $this->search_rules
                                                        ]
                                                        ];
                                                });
                                        }
                                        else {
                                            $query = Decision::search(' ')->select($selectfields)
                                            ->rule(function($builder) {
                                                return [
                                                    'must' =>
                                                    [  $this->search_rules
                                                    ]
                                                    ];
                                            });
                                        }                   
                                
                                
                                        if($sort)
                                        {  $sort = $this->getSortKeyword($sort);
                                            $query = $query->orderBy($sort, $order);
                                        }
                                
                                        $decisions = $query->paginate($numberOfEntries)->appends('sort',$request->sort)->appends('keyword',$keyword)->appends('ponente',$ponente)->appends('gteyear',$gteyear)->appends('lteyear',$lteyear);
                                    }                        
                                
                                
                                    return view('frontend.decisions.index',['decisions' => $decisions,'keyword' => $keyword,'gr_no' => $gr_no,'ponente' => $ponente]);
                                    }
                                
                                
                                     
                            

The function shown above is a trimed version of the index page controller for Decisions (Court Case Decisions). The search form on the page has a keyword field and other fields to filter the results such as ponente, gr_no and decision date. Special characters that are submitted are converted to HTML entities. Then search rules are defined based on which fields have values. The search rules are then applied to the Model using the search() method followed by the select() method. The function returns the index view containing the paginated decisions and previous entries

SASS Mixin

                                
                                    @mixin triangle($color: null, $size: 90px) {
                                        width: 0;
                                        height: 0;
                                        content: '';
                                        z-index: 1;
                                        border-style: solid;
                                        border-width: 35px $size 0;
                                        z-index:4;
                                        @if($color != null){
                                            border-color: $color transparent transparent;
                                        }
                                    }
                                     
                            

This is a simple example of a reusable mixin in SASS which I call on other class styles. I added a size parameter to accomodate different widths and also color for custom colors as needed.

Simple Laravel Scope Filter

                                
                                    public function scopeFilter(){
                                        $companies = Company::where('created_by', '=', auth()->user()->id);

                                        if (request('search') !== null)
                                        {   $companies->where(function($query) {
                                                $query->where('name','like','%'.request('search').'%')
                                                    ->orWhere('email','like','%'.request('search').'%')
                                                    ->orWhere('website','like','%'.request('search').'%');
                                            });
                                        }
                                        
                                        $companies = $companies->latest('updated_at')->paginate(10)->withQueryString();    
                                        return $companies;
                                    }
                                     
                            

This is an example of a basic scope filter for use in a controller. On this case the Company model is being queried first for the creator that matches the current logged in user. And subsequently by the search string entered by the user. The search string is compared against the name, email and website fields.

Simple SQL Subquery

                                
                                    SELECT mov_title, mov_time FROM movie 
                                    WHERE mov_id IN(
                                        SELECT mov_id FROM movie_genres 
                                        WHERE gen_id IN (
                                            SELECT gen_id FROM genres 
                                            WHERE gen_title = 'Comedy')
                                        ) 
                                    AND mov_time <= 120
                                     
                            

The objective of this simple SQL sub-query is to find all movies of type comedy which only run 2 hours or less, it should display the movie title and run-time columns.

Breakdown: On the innermost select statement, I simply retrieved the gen_id of 'Comedy' from the genres table. I then used that gen_id to get all the movies that have that genre from the movie_genres table. On the last query, I used all the mov_ids to get the titles and mov_times, and also where I used the mov_time to filter out movies that are greater than 120 minutes (2 hours).

Query result

Result: After running the SQL code on the online editor, this is the result.

Movie Database

Database: This is the database from w3resource for reference.