Simple Blog – Example 2: Procedural

Simple Blog is a multi-part series. Check out The Index

There is a lot of cleanup to do after Example 1: Structured Programming. In this example, we’ll focus on cleaning up repetitive blocks of code and markup by using functions and includes.  Using a procedural programming style, we’ll transform our code from page-level scripts, to functional blocks of code and markup that can be reused throughout the application as required. The four main areas we’ll rethink and rewrite are:

  1. Directory Structure
  2. Site Configuration
  3. Page Layout
  4. Reusable Functions

1. Directory Structure

In this example, we’ll be extracting blocks of code and markup from our pages and storing them in external files which can be imported back into our base pages as required. To differentiate between our base pages and our includes, let’s start by creating a new directory named ‘includes’. We’ll use this directory to store all configuration, layout, and function files which will be imported into our base pages. Note, in a production application, you would secure this directory by creating it above your application’s root directory.

Next, instead of storing all include files in the include directory, let’s create two more sub-directories named ‘functions’ and ‘templates’. These sub-directories will help us further differentiate between blocks of code which will be stored in ‘functions’ and blocks of markup which will be stored in ‘templates’.

2. Site Configuration

Our site configuration file we’ll help setup site-wide default values such as default timezone, default currency, and default database credentials. It also helps us setup absolute paths to our base directory and our includes, functions, and templates directories. Finally, the configuration files gives us a central location to import functions that will be used on each and every page.


	////////////////////////////////////////////////////////////////////////////////
	// Configure the default time zone
	////////////////////////////////////////////////////////////////////////////////
	date_default_timezone_set('MST');

	////////////////////////////////////////////////////////////////////////////////
	// Configure the default currency
	////////////////////////////////////////////////////////////////////////////////
	setlocale(LC_MONETARY, 'en_US');

	////////////////////////////////////////////////////////////////////////////////
	// Define constants for database connectivity
	////////////////////////////////////////////////////////////////////////////////
	define('DATABASE_HOST', 'localhost');
	define('DATABASE_NAME', 'blog');
	define('DATABASE_USER', 'blog');
	define('DATABASE_PASSWORD', 'secret');

	////////////////////////////////////////////////////////////////////////////////
	// Define absolute application paths
	////////////////////////////////////////////////////////////////////////////////

	// Use PHP's directory separator for windows/unix compatibility
	defined('DS') ? NULL : define('DS', DIRECTORY_SEPARATOR);

	// Define absolute path to server root
	define('SITE_ROOT', dirname(dirname(__FILE__)).DS);

	// Define absolute path to includes
	define('INCLUDE_PATH', SITE_ROOT.'includes'.DS);
	define('FUNCTION_PATH', INCLUDE_PATH.'functions'.DS);
	define('TEMPLATE_PATH', INCLUDE_PATH.'templates'.DS);

	////////////////////////////////////////////////////////////////////////////////
	// Include library, helpers, functions
	////////////////////////////////////////////////////////////////////////////////
	require_once(FUNCTION_PATH.'functions.inc.php');
	require_once(FUNCTION_PATH.'database.inc.php');
	require_once(FUNCTION_PATH.'post.inc.php');

  • In the section titled “Define constants for database connectivity” we setup the default hostname, username, password and database name to be used for database connectivity. Now, if we need to update our database credentials, we only need to edit a single file.
  • In the section titled “Define absolute application paths” we setup the default path to our base directory using dirname(dirname(__FILE__)), which simply returns the parent directory of the configuration file. Once an absolute path to our base directory is setup, we use the base directory  to build the default include path which in turn we use to build the default function and template paths.
  • Finally, in the section titled “Include library, helpers, functions” we import or include our commonly used functions. Since the configuration file will be imported into each and every base page, our libraries, helpers, and function will also be imported.

3. Page Layout

Granted, Simple Blog’s user interface is so simplistic it’s not really a viable interface for a real world application, but conceptually it’ll do fine to illustrate this basic concept of tearing down the markup.  We’re just looking for two distinct types of markup: 1. markup that is repeated on every page and 2. markup that is unique to each page.

From example 1, we already know that the repeating blocks of markup are at the top of the page, our header, and at the bottom of the page, our footer.  So, let’s start by creating two new files in our templates directory called ‘header.inc.php’ and ‘footer.inc.php’. Next, copy and paste the header markup from any page into the header file and repeat for the footer file.

Here’s the markup for header.inc.php:


	<!DOCTYPE HTML>
	<html>

	<head>
	    <title>Simple Blog by Graeson Lewis</title>
	    <link rel="stylesheet" type="text/css" href="../css/styles.css" >
	<head>

	<body>

		<div class="container">
			<h1>Simple Blog - Procedural</h1>
			<h3><a href="create.php">Add Post</a></h3><hr />

Here’s the markup for footer.inc.php


		</div>

	</body>

	</html>

Next, replace the header and footer markup on each base page with the respective include statement:


	<?php require_once(TEMPLATE_PATH.'header.inc.php'); ?>
	<?php require_once(TEMPLATE_PATH.'footer.inc.php'); ?>
	

4. Reusable Functions

I’ve divided  reusable functions into three categories: PHP Functions, Database Functions, and Post Functions.  As such, lets start by creating three new files in the functions directory named ‘functions.inc.php’, ‘database.inc.php’, and ‘post.inc.php’.

Following are the contents of ‘functions.inc.php’:


	function redirect_to($url) {
		if (isset($url)) {
			header("Location: " . $url);
		}
	}

	function sanitize_input($string) {
		return mysql_escape_string($string);
	}

	function sanitize_output($string) {
		return htmlspecialchars($string);
	}

  • The first function listed, redirect_to() is simply a helper function to redirect the user to a new page.  For my taste, typing out PHP’s native header(“Location: page.php”) is counterintuitive. On the other hand, writing redirect_to(‘page.php’) makes perfect sense to me and is self-documenting for others. At a quick glance, it’s clear that redirect_to(‘page.php’) is redirecting the user to ‘page.php’.
  • Next, what’s the point of having a function called sanitze_output() that simply wraps another function called htmlspecialchars? Well, I’m just learning about PHP best practices and input/output sanitation. So, let’s say tomorrow I find a better way to perform  output sanitation that includes trimming the input or maybe I find out that I should be using htmlentities() instead of htmlspecialchars(). Moving this repetitive task into a function called sanitize_output() allows me to expand or change the functionality of output sanitation across the entire site by updating a single function, rather than searching and replacing all occurrences of htmlspecialchars().

Following are the contents of ‘database.inc.php’:


	function open_database() {

		// Set global variable scope
		global $connection;

		// Open database connection
		$connection = mysql_connect(DATABASE_HOST, DATABASE_USER, DATABASE_PASSWORD)
			or die(mysql_error());

		// Select target database
		mysql_select_db(DATABASE_NAME, $connection)
			or die(mysql_error());
	}

	function execute_statement($statement) {

		// Get connection from global scope
		global $connection;

		// Open database connection
		open_database();

		// Execute database query
		$result = mysql_query($statement, $connection)
			or die(mysql_error());

		// Close database connection
		close_database();

		// Return results
		return $result;
	}

	function execute_non_query($dml) {

		// Execute database query
		$result = execute_statement($dml);

		// Get affected rows
		return mysql_affected_rows($result);
	}

	function execute_query($sql) {

		// Initialize dataset
		$dataset = FALSE;

		// Execute database query
		$result = execute_statement($sql);

		// Count number of rows
		$count = mysql_num_rows($result);

		// Check for single row returned
		if ($count == 1) {

			// Fetch a single row from database cursor
			$dataset = mysql_fetch_assoc($result);
		}

		// Check for multiple rows returned
		if ($count > 1) {

			// Initialize rows array
			$dataset = array();

			// Fetch all rows from database cursor
			while ($row = mysql_fetch_assoc($result)) {
				$dataset[] = $row;
			}
		}

		// Return dataset
		return $dataset;
	}

	function close_database() {

		// Set global variable scope
		global $connection;

		// Close database connection
		if (isset($connection)) {
			mysql_close($connection);
		}
	}

  • In example 1, we saw a lot of repetition of basic database operations scattered across every page.  Basic tasks like connect, execute, close were pervasive.  Above we’re automating the process of interacting with the database, by creating two key functions: execute_query() and execute_non_query().
  • Now, if we want to execute a query like ‘select * from post’ or ‘select * from post where id = ?’, all we need to do is call the execute_query($sql) function and pass it a parameter containing the sql statement.  The function will in turn, automatically connect to the database, execute the query, close the connection, and return the requested dataset.
  • Likewise, if we want to execute a non-query like insert, update, or delete, all we do is call the execute_non_query($dml) function and pass it a parameter containing the dml statement.  The function will automatically connect, execute, close and return the number of rows affected by the statement.
  • Finally, notice that ‘functions.inc.php’ and ‘database.inc.php’ file can be used by any PHP application.  In other words, the functions created in these files are application agnostic and are in no way tied to Simple Blog. Now that’s the code reusability we’re after.

Following are the contents of ‘post.inc.php’:


	function get_posts() {

		// Build database query
		$sql = 'select * from post';

		// Execute query and return all rows
		return execute_query($sql);
	}

	function get_post_by_id($id) {

		// Sanitize user input
		$id = (int)$id;

		// Build database query
		$sql = sprintf("select * from post where id = %d limit 1", $id);

		// Execute query and return single row
		return execute_query($sql);
	}

	function insert_post($title, $content) {

		// Sanitize user input
		$title = sanitize_input($title);
		$content = sanitize_input($content);

		// Build database query
		$sql = sprintf("insert into post (title, content) values ('%s', '%s')", $title, $content);

		// Execute non-query and return rows affected
		return execute_non_query($sql);
	}

	function update_post($title, $content, $id) {

		// Sanitize user input
		$id = (int)$id;
		$title = sanitize_input($title);
		$content = sanitize_input($content);

		// Build database query
		$sql = sprintf("update post set title = '%s', content = '%s' where id = %d", $title, $content, $id);

		// Execute non-query and return rows affected
		return execute_non_query($sql);
	}

	function delete_post($id) {

		// Sanitize user input
		$id = (int)$id;

		// Build database query
		$sql = sprintf("delete from post where id = %d limit 1", $id);

		// Execute non-query and return rows affected
		return execute_non_query($sql);
	}

  • Unlike the two files listed above, ‘post.inc.php’ is very specific to the Simple Blog application. This file includes all of the custom database functions that will create, read, update, and delete blog posts for our application.  However, it’s important to note that even though these particular functions are hard coded to match the naming conventions of our Simple Blog data model, the same CRUD operations are pervasive across PHP applications. For example, almost every PHP application out there will have a sql statement that reads ‘select * from table where id = ?’ or ‘delete from table where id = ?’.  So, the only thing tying ‘post.inc.php’ to Simple Blog is the fact that we’ve hard coded the table name and column names into the function.  This could be easily abstracted so that these functions too are portable by using dynamic table and column names.
  • The function names should be self-descriptive. For example, get_posts() retrieve’s all post from the database where as get_post_by_id($id) retrieves a single post from the database.
  • Notice that the two key functions created in ‘database.inc.php’, execute_query() and execute_non_query(), are used throughout to automate the process of interacting with the database.
  • Also, notice that input sanitation was pulled out of the base pages and coupled with the database operations.

Now that we’ve pulled our repetitive code and markup out from our old base pages, let look at a few examples from our new base pages. You’ll see that we’ve entirely stripped the data access layer from our base pages and instead created a set of consumable functions that can be called by the base page.  Consequently, unlike our old base pages which hosted data, behavior, and presentation layers, our new base pages are comprised of only behavior and presentation layers.

List Page


	// Initialize site configuration
	require_once('includes/config.inc.php');

	// Get rows from database
	$posts = get_posts();

	?>

	<?php require_once(TEMPLATE_PATH.'header.inc.php');?>

		<?php foreach($posts as $post): ?>

			... same as Example 1

		<?php endforeach;?>

	<?php require_once(TEMPLATE_PATH.'footer.inc.php');?>

  • First, we import the global configuration file using PHP’s require_once() function.
  • Next, we call our get_posts() functions to retrieve an array of posts.
  • Finally, notice that our HTML markup remains untouched with the exception of including the newly created header.inc.php and footer.inc.php files.

Create Page


	<?php

	// Initialize site configuration
	require_once('includes/config.inc.php');

	// Initialize form values
	$title = NULL;
	$content = NULL;

	// Check for page postback
	if ($_SERVER['REQUEST_METHOD'] == 'POST') {

		// Get user input from form
		$title = $_POST['title'];
		$content = $_POST['content'];

		// Execute database query
		insert_post($title, $content);

		// Redirect to site root
		redirect_to('.');
	}

	?>

	<?php require_once(TEMPLATE_PATH.'header.inc.php'); ?>

		<form method="POST" action="<?php
			echo sanitize_output($_SERVER['PHP_SELF']); ?>">

			... same as Example 1

		</form>

	<?php require_once(TEMPLATE_PATH.'footer.inc.php'); ?>
	
  • First, we import the global configuration file using PHP’s require_once() function.
  • Next, our conditional check for page postbacks is unchanged.
  • Next, all of our data access code is boiled down into a single function call.
  • Finally, notice that our HTML markup remains untouched with the exception of including the newly created header.inc.php and footer.inc.php files.

Next

Identify candidates classes for object oriented development.
Simple Blog – Example 3

Download

You can download the source code for each example at box.net.
Download Source

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s