Simple Blog – Example 3: Object Oriented

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

In the previous example, Simple Blog – Example 2, we rolled-up our repetitive code and markup into functions and templates. Furthermore, we grouped functions according to their problem domain. For example, we pushed all of the generic database operations into a file called database.inc.php and pushed all of the post operations into a file called post.inc.php.

In this exercise, we’ll take each of these function groups and roll them into their own classes. First, we’ll create a Database class to facilitate basic database operations such as connect, execute, fetch, and close. Next, we’ll create a Post class to automate the process of selecting, updating, inserting, and deleting data from our post database table.

Database Class

First, let’s take a quick look at the overall layout of the database class.


	class Database 
	{
		private $connection;
		private $hostname;
		private $username;
		private $password;
		private $database;
		
		public function __construct()
		{
			...
		}

		public function openConnection()
		{
			...
		}

		public function closeConnection()
		{
			...
		}

		public function executeStatement($statement)
		{
			...
		}
	
		public function executeSql($sql)
		{
			...
		}

		public function executeDml($dml)
		{
			...
		}
		
		public function sanitizeInput($value)
		{
			...
		}
	}

Now, let’s look at a few key methods in the Database class.


	public function __construct()
	{
		$this->hostname = DATABASE_HOST;
		$this->username = DATABASE_USER;
		$this->password = DATABASE_PASSWORD;
		$this->database = DATABASE_NAME;		
	}

	public function openConnection()
	{
		// Open database connection
		$this->connection = mysql_connect($this->hostname, $this->username, $this->password) 
			or die(mysql_error());
	
		// Select target database
		mysql_select_db($this->database, $this->connection) 
			or die(mysql_error());
	}

	public function closeConnection()
	{
		if (isset($this->connection)) {
			// Close database connection
			mysql_close($this->connection) 
				or die(mysql_error());
		}
	}

  • The constructor method will simply configure the object’s hostname, username, password, and database name attributes using the constants setup in our configuration file.
  • The openConnection() method connects to the database engine and then selects the target database. Notice how the object’s connection attribute is set equal to the return value of mysql_connect().
  • The closeConnection() method closes the database connection.

	public function executeStatement($statement)
	{
		// Open database connection
		$this->openConnection();
	
		// Execute database statement
		$result = mysql_query($statement, $this->connection) 
			or die(mysql_error());

		// Close database connection
		$this->closeConnection();
	
		// Return result
		return $result;
	}

	public function executeSql($sql)
	{
		// Execute database statement		
		$result = $this->executeStatement($sql);
	
		// Check number of rows returned
		if(mysql_num_rows($result) == 1) 
		{
			// Fetch one row from the result
			$dataset = mysql_fetch_object($result);
		} 
		else 
		{
			// Fetch multiple rows from the result
			$dataset = array();		
			while ($row = mysql_fetch_object($result)) {
				$dataset[] = $row;
			}
		}
	
		// Close database cursor
		mysql_free_result($result);
	
		// Return dataset
		return $dataset;
	}

	public function executeDml($dml)
	{
		// Execute database statement
		$this->executeStatement($dml);
	
		// Return affected rows
		return mysql_affected_rows($this->connection);
	}

  • The executeStatement() method opens a connection, executes any database statement, closes the connection, and returns the result.
  • The executeSql() method calls the executeStatement() method to execute a database query. The executeSql() method then fetches a single object or an array of objects from the result depending on the number of rows returned.
  • The executeDml() method calls the executeStatement() method to execute a data manipulation statement such as an insert, update, or delete. The executeDml() method then retrieves and returns the number of rows affected by the operation.
  • Note, most of the database wrapper classes I’ve read call the connect method in the constructor. Personally, I prefer to call database connects as late as possible and call database disconnects as early as possible. For this reason, you’ll see that I’ve called the openConnection() and closeConnection() methods in the executeStatement() method instead of the constructor.

	public function sanitizeInput($value)
	{
		if (function_exists('mysql_real_escape_string')) 
		{
			if (get_magic_quotes_gpc())	
			{
				// Undo magic quote effects
				$value = stripslashes($value);
			}
			// Redo escape using mysql_real_escape_string
			$value = mysql_real_escape_string($value);
		} 
		else 
		{
			if (!$this->get_magic_quotes_gpc())	
			{
				// Add slashed manually
				$value = addslashes($value);
			}
		}
		// Return sanitized value
		return $value;
	}

  • In the previous example, we pushed data sanitation out of our base pages and into our post functions. Now, we’re pushing data sanitation one level deeper by grouping our input sanitation with our basic database operations. This approach makes sense being that input sanitation is a more natural database action, then a user action.
  • The sanitizeInput() method escapes values using a few different techniques. Which technique is used will depend on the version and configuration of PHP.
  • For example, if magic quotes are disabled and mysql_real_escape_string is not available, the sanitizeInput() method will escape the value using the addslashes() function. Alternatively, if magic quotes are enabled and mysql_real_escape_string is available, we’ll first undo magic quotes using the stripslashes() and then re-escape the value using mysql_real_escape_string.

Post Class

First, let’s take a quick look at the overall layout of the Post class.


	class Post 
	{
		public $id;
		public $title;
		public $content;
		public $created;

		public static function getBySql($sql) 
		{
			...
		}

		public static function getById($id) 
		{
			...
		}

		public static function getAll() 
		{
			...
		}

		public function insert() 
		{
			...
		}

		public function update() 
		{
			...
		}

		public function delete() 
		{
			...
		}
	
		public function save() 
		{
			...
		}	
	}
	

Now, let’s look at a few key methods in the Post class.


	public static function getBySql($sql) 
	{
		// Instantiate database object
		$database = new Database();
	
		// Execute database query
		return $database->executeSql($sql);
	}

	public static function getById($id) 
	{
		// Sanitize user input
		$id = (int)$id;
	
		// Build database query
		$sql = sprintf("select * from post where id = %d limit 1", $id);
	
		// Execute database query
		return self::getBySql($sql);					
	}

	public static function getAll() 
	{
		// Build database query
		$sql = 'select * from post';

		// Execute database query
		return self::getBySql($sql);
	}

  • The getBySql() method is a helper that aids the process of selecting data from the post table.
  • The getById() method retrieves a single post from the database.
  • The getAll() method retrieves all posts from the database.
  • Notice, that all three get methods are declared as static methods. This means we can call the methods from our base pages without instantiating the post class. For example, we can simply say $post = Post::getById($id). We’ll see more of this in action later.

	public function insert() 
	{
		// Open database connection
		$database = new Database();

		// Sanitize user input
		$title = $database->sanitizeInput($this->title);
		$content = $database->sanitizeInput($this->content);	

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

		// Execute statement
		return $database->executeDml($dml);
	}

  • The insert() method creates a new post in the database and returns the number of affected rows.
  • Unlike the get methods, insert() is not a static method. So, before calling insert() we need to instantiate post. For example, $post = new Post().

	public function update() 
	{
		// Open database connection
		$database = new Database();

		// Sanitize user input
		$id = (int)$this->id;
		$title = $database->sanitizeInput($this->title);
		$content = $database->sanitizeInput($this->content);

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

		// Execute data manipulation
		return $database->executeDml($dml);
	}

  • The update() method edits an existing post and returns the number of affected rows.
  • Unlike the get methods, update() is not a static method. So, before calling update() we need to instantiate post. For example, $post = new Post().

	public function delete() 
	{
		// Open database connection
		$database = new Database();

		// Sanitize user input
		$id = (int)$this->id;
	
		// Build database query
		$dml = sprintf("delete from post where id = %d limit 1", $id);

		// Execute data manipulation
		return $database->executeDml($dml);
	}

  • The delete() method deletes an existing post and returns the number of affected rows.
  • Unlike the get methods, delete() is not a static method. So, before calling delete() we need to instantiate post. For example, $post = new Post().

	public function save() 
	{
		// Check object for id
		if (isset($this->id)) 
		{	
			// Return update when id exists
			return $this->update();
		
		} 
		else 
		{
			// Return insert when id does not exists
			return $this->insert();
		}
	}	

  • The save() method is a helper that aids the process of inserting and updating records.
  • From our base pages we call save() instead of insert() or update(). If the object’s id is set, the method will call update(). Conversely, if the object’s id is not set, the method will call insert().

Base Pages

Now, that we’ve got two working classes, let’s take a look at how these classes are consumed by our base pages. For the most part, consuming these new classes just consists of minor syntactical changes. Here’s our new List page which retrieves posts from the database and displays them as HTML.


	<?php 

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

	// Get posts from database
	$posts = Post::getAll();

	?>

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

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

			<h4>
				<a href="read.php?id=<?php 
					echo $post->id;?>"><?php 
					echo $post->title;?>
				</a>
			</h4>

			<p>
				<?php echo $post->content;?>
				<?php echo $post->created;?>
			</p>

		<?php endforeach; ?>

	<?php require_once(TEMPLATE_PATH.'footer.inc.php'); ?>
	
  • Notice how we call the static method getAll(). First, we call the Post class and then the getAll() method using Post::getAll().
  • Also, notice the syntactical change made to retrieve each value. For example, $post->title or $post->content.

Finally, let’s take a look at how our non-static methods are called:


	// Create post
	$post = new Post();
	$post->title = $title;
	$post->content = $content;
	$post->save();


	// Update post
	$post = new Post();
	$post->id = $id;
	$post->title = $title;
	$post->content = $content;
	$post->save();


	// Delete post
	$post = new Post();
	$post->id = $id;
	$post->delete();	

  • First, notice how we start by instantiating Post.
  • Next, we initialize the object’s relevant attributes. For example, $post->id = $id.
  • Finally, we call the relevant method. For example, $post->delete().
  • Note, that when creating or updating posts we call the helper method save().

Next

Separating data, presentation, and behavior into models, views, and controllers.
Simple Blog – Example 4

Download

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

Advertisements

One thought on “Simple Blog – Example 3: Object Oriented

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