Adventures in PHP: A Simple Guestbook

So my exploration of PHP continues with the need to write a guestbook application for class that I am taking. I am very familiar with aspects of object oriented programming in Java, but PHP is another monster altogether. Conventions and OOP styles that I’m used to in Java almost fit into the PHP world, but not quite. I find myself putting together, what I feel is poor coding style in order to make PHP work. Maybe it’s just me, who knows. I’m sure I’ll look back later and say, “What was I thinking when I wrote that”. I think every coder thinks that at some point or another when they open an old project.

Anyway, back to the PHP…

In my last entry, I was working on my database connection object. With that piece in place, I started to think about how to approach the guestbook project. The application itself is fairly simple: I need a form for input, an controller layer to do form processing, and a database layer to handle all the MySQL stuff. Since I already had the database layer and the underlying table defined, I started working on my front end forms. I knew what fields I needed since the guestbook table was defined, so it was a matter of whipping up a basic HTML form to start.

<?php
/**
 * Description of guestbook
 *
 * @author Russell Shingleton
 */
?>

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Guest Book</title>
        <script src="js/jquery-1.9.1.min.js"></script>
        <script src="js/jquery-ui.min.js"></script>
        <script src="js/jquery.loadmask.min.js"></script>
        <script src="js/validation.js"></script> 
        <script src="js/main.js"></script>        
        <link rel="stylesheet" type="text/css" href="css/jquery-ui/jquery-ui.css">
        <link rel="stylesheet" type="text/css" href="css/main.css">
        <link rel="stylesheet" type="text/css" href="css/jquery.loadmask.css">
        <link rel="stylesheet" type="text/css" href="css/validation/validation.css">
    </head>
    <body>
        <div id="main">
            <div id="content">
                <form id="guestbook_form" class="validated" action="includes/GuestbookAction.php"  target="_blank">
                    <div class="formElement">
                        <label for="author">Name</label>
                        <input type="text" name="author" id="author" class="required" minLength="2" maxLength="45" />
                    </div>
                    <div class="formElement">
                        <label for="email">Email Address</label>
                        <input type="text" name="email" id="email" class="required email" 
                               style="width: 250px" minLength="5" maxLength="65"/>
                    </div>
                    <br/>
                    <div class="formElement">
                        <label for="title">Title</label>
                        <input type="text" name="title" id="title" class="required" minLength="5" 
                               maxLength="100" style="width: 600px;"/>
                    </div>
                    <div class="formElement">
                        <label for="message" style="display: inline-block;">Message</label>
                        (<span class='current-words'>                
                        </span>
                        <span class='max-words'>                
                        </span>)
                        <textarea name="message" id="message" class="required" maxChar="500" 
                                  minLength="5"></textarea>
                    </div>
                    <input type="hidden" name="action" value="add" />
                    <input type="hidden" id="debug" name="debug" value="true" />
                    <span id="button-block">
                        <input id="guestbook_form_submit" type="submit" value="Post" />
                    </span>
                    <span id="message-block"></span>
                </form>
                <hr/>
                <div id="guestbook_entries"></div>
            </div>
        </div>
        <div style="display:none">
            <div class="entry-template">
                <div class="entry-head">
                    <div class="author"></div>
                    <div class="email"></div>
                    <div class="date"></div>
                </div>
                <div class="title"></div>
                <div class="message"></div></div>
        </div>
        <script type="text/javascript">
            $(document).ready(function(){
                getEntries();

                $('#guestbook_form_submit').on('click', function(e){
                    e.preventDefault();
                    var form = $('#guestbook_form');
                    $('#debug').remove();
                    $('#message-block').html('');
                    if(validateForm(form).valid){
                        $.post(form.attr('action'), form.serialize(), function(data){
                            if(data.response === 'SUCCESS'){
                                clearForm(form);
                                getEntries();
                                initTextareas();
                                $('#message-block').html('Post Successful!');
                                $('#message-block').css('color', 'green');
                            }else{                            
                                var errors = $('<ul></ul>');
                                $(data).each(function(i, val){
                                    errors.append('<li>'+val+'</li>');  
                                });
                                $('#message-block').append(errors);
                                $('#message-block').css('color', 'maroon');
                            }
                        }, 'json');
                    }
                });
            });
        </script>
    </body>
</html>

I added some extra attributes to the form elements to build in some JavScript validation later with my validation library, and included a section for entries to be populated in from the database using the entry-template div as a pattern. To make it as smooth as possible, the majority of the client interaction would be done with jQuery and AJAX.

Now that the form was complete, I need to work on my controller layer to process my form data, validate the fields, do my inserts, and fetch the existing entries from the database to populate on the page. Sounds easy right? It really wasn’t that bad although the unfamiliarity with PHP does make it so.

The first thing I need to do in my controller layer was figure out how to get the form data and validate it. In PHP, post data can be retrieved from the request option using $_REQUEST[‘form_input_name’]. Through trial and error, I’ve found it best to first check to see if there is a valid request parameter by wrapping the $_REQUEST[‘form_input_name’] in isset():

if (isset($_REQUEST['form_input_name'])){
     // do magic
}else{
     // go home
}

This is the best way I’ve found so far to determine if a value is present. This will determine if the form field was ignore, later we need to determine if the field was required server side in case our client side validation fails. More on that to come. The final action class looks like this:

<?php
require "GuestbookDAO.php";

$action = $_REQUEST["action"];

if (isset($_REQUEST['title'])) {
    $title = $_REQUEST["title"];
} else {
    $title = '';
}
if (isset($_REQUEST['author'])) {
    $author = $_REQUEST["author"];
} else {
    $author = '';
}
if (isset($_REQUEST['email'])) {
    $email = $_REQUEST["email"];
} else {
    $email = '';
}
if (isset($_REQUEST['message'])) {
    $message = $_REQUEST["message"];
} else {
    $message = '';
}

$gb = new GuestbookDAO();
switch ($action) {
    case "entries":
        return $gb->getEntriesJSON();
    case "add":
        $response = $gb->addEntry($title, $author, $email, $message);
        if (isset($_REQUEST['debug'])) {
            // If in debug mode, display the server side validation test screen
            $error = false;
            if (strlen($response) == 0) {
                $response = "Post Successful";
            } else {
                $error = true;
            }
            include 'ServersidePostTest.php';
        } else {
            // If not in debug, return JSON
            print($response);
            return;
        }
    //return $gb->getEntriesJSON();
    default :
        return;
}
?>

So I’ve determined that I can get all my values from the request, and set them to local variables using the above conditional model, now I just need to define the fields that I’m retrieving and get them into the database after I check that they are not empty. To do this I decided to create a database access object (DAO) to handle some of the business logic, inserts, and queries I would need.

The GuestbookDAO.php contains a reference to the DbConnector.php class. When I create a new instance of the GuestbookDAO class, it in turn would have an instance of the DbConnector class to do all the database interaction. The GuestbookDAO would do all the guestbook specific routines like query, insert, and validate data prior to insert. If I needed additional database entities later, I would use this as my pattern.

I started the GuestbookDAO with 3 functions and a constructor. The constructor instantiates an instance of the DbConnector to be used in the core functions of the GuestbookDAO. The three functions I started with were:

  • public function addEntry($title, $author, $email, $message) This function would serve as my insert entry point. I would also build my server side validation routine into this function to ensure the data was valid before inserting it into the database.
  • public function getEntries() This function is used to perform tests as well as render a server response when no JavaScript is enabled as well as return a raw resultset.
  • public function getEntriesJSON() This function is used to return a JSON encoded resultset to be used in populating the guestbook entries.

The resulting class looks like this:

<?php

require "DbConnector.php";

/**
 * Description of GuestbookConnector
 *
 * @author Russell Shingleton
 */
class GuestbookDAO {

    private $db;
    private $table = "guestbook";

    function __construct() {
        $this->db = new DbConnector();
    }

    public function addEntry($title, $author, $email, $message) {
        $this->db->openConnection();
        $title = mysql_real_escape_string($title);
        $author = mysql_real_escape_string($author);
        $email = mysql_real_escape_string($email);
        $message = mysql_real_escape_string($message);
        $errors = array();
        if (strlen($title) > 100) {
            array_push($errors, "Title must be less than 100 characters, current length: " . strlen($title));
        }
        if (strlen($title) < 5) {
            array_push($errors, "Title must be at least than 5 characters, current length: " . strlen($title));
        }
        if (strlen($author) > 45) {
            array_push($errors, "Author must be less than 45 characters, current length: " . strlen($author));
        }
        if (strlen($author) < 2) {
            array_push($errors, "Author must be at least than 2 characters, current length: " . strlen($author));
        }
        if (strlen($email) > 65) {
            array_push($errors, "Email must be less than 65 characters, current length: " . strlen($email));
        }
        if (strlen($email) < 5) {
            array_push($errors, "Email must be at least than 5 characters, current length: " . strlen($email));
        }
        if (strlen($message) > 500) {
            array_push($errors, "Message must be less than 500 characters, current length: " . strlen($message));
        }
        if (strlen($message) < 10) {
            array_push($errors, "Message must be at least than 10 characters, current length: " . strlen($message));
        }

        if (count($errors) > 0) {
            return json_encode($errors);
        }
        error_log('Insert RECORDS');
        $this->db->insert("INSERT INTO $this->table (Title, Author, Email, Message) VALUES 
            ('$title', '$author', '$email', '$message')");
        $this->db->closeConnection();

        return '{"response":"SUCCESS"}';
    }

    public function getEntries() {
        $this->db->openConnection();
        $resultSet = $this->db->query("SELECT * FROM $this->table");
        $this->db->closeConnection();
        if ($resultSet === FALSE) {
            error_log("Invalid resultset"); // TODO: better error handling
        }
        return $resultSet;
    }

    public function getEntriesJSON() {
        $resultSet = $this->getEntries();
        $rows = array();
        while ($r = mysql_fetch_array($resultSet)) {
            $rows[] = $r;
        }
        $json = json_encode($rows);
        print($json);
    }

}

?>

With these these three main components and my DbConnector class in place, I could begin testing and flesh out all my JavaScript. A demo of the working project can be found here.