Three tier code example in php

December 8, 2007 Filed under: php, programming — Tags: , , , , — catalin @ 8:20 pm

Pay respect to your customer by talking to him, allowing him to understand the underlying mechanisms of your website programming and by implementing his requirements directly in your code, using his own words for class names, variables and methods. Pay respect to your fellow programmers by writing beautiful code, so they can also understand what you are doing in php.

In this article…

I wrote in a previous article about object oriented (link here: http://www.mbdragan.com/catalin/2007/10/26/object-oriented/). I was saying I usually write my programs after I speak to my client and I draw a kind of UML schema (classes diagram) or CRC cards, and we write together the specifications. Specifications don’t need to be very technical, but they should use a simple to understand language. For instance (well… extremely short version):

“The site should allow users to register, activate account and login. There should be a special kind of user called administrator that is allowed to manage all the other users. He can add products to database and after that users may visit the site and browse products, add them to cart and send orders. The administrator can then work on these orders, change their status as the work is complete and products are delivered.”

By reading this text, you see the classes are User, Admin, Product, Order. Verbs related to them are their methods and attributes are properties. You could write an entire site using only these classes and by writing no other php files. But then you need methods to deal with database operations and display of the objects in HTML pages; having so many methods inside the classes it’s not a good idea because they will obscure the code. What you need to do is keep these classes simple, with as little code as possible; methods and properties of these classes should be those that strictly deal with their behavior as objects, but nothing related to database, HTML display, sending mails and so on.

The architecture I’m presenting you here is called “three tier” because it will split that code into 3 layers: “business logic”, “data layer” and “presentation”.

Presentation layer

How do we “split” the code? That’s easy, we create 3 directories: “presentation”, “logic” and “dblayer”. In presentation we place the files user is visiting from the browser and we focus on writing the code very much similar to the simplest php you learn from the first day you play with it: no functions, no classes. I mean, feel free to call functions and instantiate objects, but don’t declare them, don’t write their body here. Plain HTML that uses include() and require() functions to include header, footer and so on. Just keep it simple, plain HTML with small pieces of PHP that instantiate the objects I have been talking about earlier and present them to the user.

Example of presentation file:

file: /presentation/view_profile.php

<?php
require_once(dirname(__FILE__) . "/../config.php");
require_once(dirname(__FILE__) . "/../logic/User.php");

// let's presume the user is visiting an address like this:
// http://www.example.com/view_profile.php?username=testuser
$u = new User();
$u->getFromDb(@$_GET["username"]);
?>
<html>
<head>
<title>View user profile</title>
</head>
<body>
<h1>View user profile: <?php echo htmlspecialchars($u->username); ?></h1>
<table>
<tr>
<td>Username: </td><td><?php echo htmlspecialchars($u->username); ?></td>
</tr>
<tr>
<td>Name: </td><td><?php echo htmlspecialchars($u->first_name . " " . $u->last_name); ?></td>
</tr>
<tr>
<td>Email: </td><td><?php echo htmlspecialchars($u->email); ?></td>
</tr>
</table>
</body>
</html>

There is a misconception that presentation files must be templetized, such as templates for smarty, in order to call them presentation files. That’s wrong. PHP itself is designed to work with HTML without the need for templates, and there is nothing wrong with that. After all, is in the manual, read it in case you didn’t know; unlike the templates, that are not in the manual. On the other hand, I’m not saying templates are evil; what I’m pointing out is that presentation does not necessarily has to be templetized. You cannot say templates are good or templates are bad. What you can say however is that templates are a matter of pure personal preference, that’s all.

Business logic

The business logic layer consists of the classes I have been talking in my previous article, OOP. It is called “Business” because it must describe the business needs of your customer. This collection of classes is the one most closely related to what your client wants. He’s not a technical guy, so he doesn’t care about how artistically you write your HTML or how cool you code the SQL. What he understands are the human readable specifications - the ones I gave you an example when starting this article. Starting from these specifications you write the classes in business logic layer and you must take care to closely follow real life names. If there is a noun in your specifications called “product” you create a class named “Product”, not “K_Db_Prod_P”, LOL, you got the point. If your customer say “The selling price of a product should be computed based on the buying cost, plus a coefficient fee” then you may write a method like this: function computeSellingPrice($buyingCost, $coefficient) { return $buyingCost * (1 + $coefficient);}.

Business logic layer is the most important in your application and you have to take great care when writing it. This is the coding layer that makes a site different from the others; all sites use HTML for display and HTML looks the same all over; all sites use SQL for storage and SQL looks the same every time. However, each application is different from another based on the functionality. When you take a site written by someone else and you look at HTML or SQL there’s no much effort to understand this kind of code. You just read it and you know what it does.

That’s not the same when it comes to the business needs of your customer, coded in your site. Let’s say you take a project from someone else, you don’t know what it does, there’s no written documentation about what the customer wanted when the project was developed and you cannot contact him right now. Who cares how good you are at HTML or how brilliant you make left joins or unions in SQL or how many years of experience you have in PHP! PHP, HTML and SQL won’t ever tell you what the customer wanted from the project to do. This is where business logic layer comes into play and this is why it is the most important. There are two cases: 1) the code is cruel and silent to you (most of the programmers I know have this feeling when approaching someone else’s code); or 2) the code speaks to you and tells you what the customer wanted.

Example of business logic file:

file: /logic/User.php

<?php
class User
{
	private $is_logged_in = 0;
	public $username = "";
	public $password = "";
	public $first_name = "";
	public $last_name = "";
	public $email = "";

	/**
	 * @var DatabaseConnection
	 */
	private $db;

	public function User()
	{
		global $dbConnection;
		$this->db = $dbConnection;
	}

	public function __sleep()
	{
		return array("is_logged_in", "username", "password", "first_name", "last_name", "email");
	}

	public function getFromDb($username)
	{
		$u = $this->db->tbUser->getByUsername($username);
		$this->username = $u->username;
		$this->password = $u->password;
		$this->first_name = $u->first_name;
		$this->last_name = $u->last_name;
		$this->email = $u->email;
	}

	public function login($argUsername, $argPassword)
	{

		if(strlen($argUsername) == 0 || strlen($argPassword) == 0)
			return false;
		$u = $this->db->tbUser->getByUsername($argUsername);
		if(strlen($u->username) == 0)
			return false; // username not found
		if($u->password != md5($argPassword))
			return false; // wrong password

		// all is ok, proceed with login:
		$this->is_logged_in = 1;
		$this->username = $u->username;
		$this->password = $u->password;
		$this->first_name = $u->first_name;
		$this->last_name = $u->last_name;
		$this->email = $u->email;

		return true;
	}

	public function isLoggedIn()
	{
		return ($this->is_logged_in == 1);
	}

	public function logout()
	{
		$this->is_logged_in = 0;
	}

	/**
	 * $data is $_POST information received from registration form.
	 */
	public function register($data)
	{
		$this->username = @$data["username"];
		$this->password = md5(@$data["password"]);
		$this->first_name = @$data["first_name"];
		$this->last_name = @$data["last_name"];
		$this->email = @$data["email"];

		if($this->validateRegister(@$data["password"], @$data["confirm_password"]))
		{
			$this->dbInsert();
			return true;
		}
		else
		{
			return false;
		}
	}

	public function updateProfile()
	{
		if($this->validateUpdate())
		{
			$this->dbUpdate();
			return true;
		}
		else
		{
			return false;
		}
	}

	private function validateRegister($password, $confirmPassword)
	{
		// for simplicity I only check username to be unique in this example:
		if(strlen($this->username) < 5 || strlen($this->username) > 250)
			return false;
		if(preg_match("/[^a-zA-Z0-9]/", $this->username))
			return false; // only alpha-numerical characters allowed

		if(strlen($password) < 6 || strlen($password) > 250)
		    return false;
		if($password != $confirmPassword)
		    return false;
		if(strlen($this->first_name) > 250 || strlen($this->last_name) > 250)
			return false;
		if(strlen($this->email) > 250)
			return false;

		// check to see if this username is available
		$temp = $this->db->tbUser->getByUsername($this->username);
		if(strlen($temp->username) > 0)
			return false; // there's another user

		return true;
	}

	private function validateUpdate()
	{
		// make sure you only modify first_name and last_name,
		// username and email stay the same as they were given
		// by the user in the first day he registered:
		$originalRecord = $this->db->tbUser->getByUsername($this->username);
		if(strlen($originalRecord->username) == 0)
			return false; // no user could be found to update

		if($originalRecord->email != $this->email)
			return false; // you are not allowed to change your email address
		if(strlen($this->first_name) > 250 || strlen($this->last_name) > 250)
			return false;

		// all ok:
		return true;
	}

	private function dbInsert()
	{
		$this->db->tbUser->insert($this);
	}

	private function dbUpdate()
	{
		$this->db->tbUser->update($this);
	}
}
?>

Data layer

As you see, there is no SQL code in my examples until now. Nor in presentation, neither in logic. The only thing you can see in logic (business logic layer) are some thin lines like this: $this->db->tbUser->insert($this). In the constructor of a User, you see it will remember a reference to a $dbConnection - database connection. Knowing that, $this->db->tbUser->insert($this) translates as " TableUser, member of my personal DatabaseConnection, must insert() the informations about ME into the Database, as a new record; (ME, I mean I’m thinking from inside the object instance of User) ".

You start the data layer by creating a folder called data_layer and placing inside of it a class for Database connection and several classes for tables - one class for each table in your database. The Database connection class is supposed to describe your database as it is - you connect to it using a host, username and password, it has a name of it’s own, and several tables inside of it:

file: /dblayer/Database.php

<?php
require_once(dirname(__FILE__) . "/TableUser.php");
class Database
{
	private $conn; // resource returned by mysql_connect
	private $host = "";
	private $username = "";
	private $password = "";
	private $name = "";

	public $tbUser;
	// ... and more members if your database has more tables:
	// public $tbProduct;
	// public $tbOrder;

	public function Database($argHost, $argUsername, $argPassword, $argName)
	{
		$this->host = $argHost;
		$this->username = $argUsername;
		$this->password = $argPassword;
		$this->name = $argName;
	}

	public function registerTables()
	{
		$this->tbUser = new TableUser($this, "user");
		// ... and more members if your database has more tables:
		// $this->tbProduct = new TableProduct($this, "product");
		// $this->tbOrder = new TableOrder($this, "order");
	}

	public function connect()
	{
		$this->conn = mysql_connect($this->host, $this->username, $this->password);
		mysql_select_db($this->name, $this->conn);
	}

	public function escape($s)
	{
		return mysql_real_escape_string($s);
	}

	public function query($q)
	{
		return mysql_query($q, $this->conn);
	}

	public function fetch_array($res)
	{
	    return mysql_fetch_array($res);
	}

}
?>

For each table in your database you create a PHP class describing it. Such a class should provide your application all necesary means to access the real table. All SQL code related to that table must be written in it’s PHP class, for each logical operation you write a separate method:

file: /dblayer/TableUser.php

<?php
class TableUser
{
	private $db; // reference to the Database connection

	public function TableUser(&$argDb)
	{
		$this->db = &$argDb;
	}

	/**
	 * @return User
	 */
	public function getByUsername($username)
	{
		$q = "select * from users
			where username = '" . $this->db->escape($username) . "' ";
		$res = $this->db->query($q);
		if(!$row = $this->db->fetch_array($res))
			$row = array();
		$u = new User();
		$u->username = @$row["username"];
		$u->password = @$row["password"];
		$u->first_name = @$row["first_name"];
		$u->last_name = @$row["last_name"];
		$u->email = @$row["email"];
		return $u;
	}

	public function insert(User $u)
	{
		$q = "insert into users (username, password, first_name, last_name, email)
			values('" . $this->db->escape($u->username) . "',
			'" . $this->db->escape($u->password) . "',
			'" . $this->db->escape($u->first_name) . "',
			'" . $this->db->escape($u->last_name) . "',
			'" . $this->db->escape($u->email) . "') ";
		$this->db->query($q);
	}

	public function update(User $u)
	{
		$q = "update users
		set password = '" . $this->db->escape($u->password) . "',
		first_name = '" . $this->db->escape($u->first_name) . "',
		last_name = '" . $this->db->escape($u->last_name) . "',
		email = '" . $this->db->escape($u->email) . "'
		where username = '" . $this->db->escape($u->username) . "' ";

		$this->db->query($q);
	}
}
?>

Additional files

In my example I have been using a configuration file to connect to database and an sql file to create the database.

This is the config file (place it in the root of the application):

file: /config.php

<?php

require_once(dirname(__FILE__) . "/dblayer/Database.php");

$config = array(
    "db_host" => "localhost",
    "db_username" => "root",
    "db_password" => "",
    "db_name" => "three_tier_db"
);

$dbConnection = new Database($config["db_host"], $config["db_username"], $config["db_password"], $config["db_name"]);
$dbConnection->registerTables();
$dbConnection->connect();

?>

And this is the sql script (use it to create the table in the database and add a dummy record):

file: /create.sql

create table users (
    username varchar(255) not null default '',
    password varchar(255) not null default '',
    first_name varchar(255) not null default '',
    last_name varchar(255) not null default '',
    email varchar(255) not null default '',
    constraint primary key(username)
) TYPE=MYISAM;

# e16b2ab8d12314bf4efbd6203906ea6c is md5 of 'testpassword'
insert into users(username, password, first_name, last_name, email)
values('testuser', 'e16b2ab8d12314bf4efbd6203906ea6c', 'Test', 'User', 'test_user@example.com');

So in the end, if you follow this example, you get a hierarchy of files like this:

/presentation/view_profile.php
/logic/User.php
/dblayer/Database.php
/dblayer/TableUser.php
/config.php
/create.sql

You should configure your Apache by adding a new virtual host so that the root of your website is /presentation. The files /configure.php, /create.sql and directories /dblayer and /logic should not be visible from HTTP. One more thing I would like to add, I’m sure there could be better ways to write this if it were a real application, such as: $config and $dbConnection are not supposed to be global variables, instead they should be members of a singleton; abstract classes and inheritance may also come in handy; and so on and so on… But the point is, this article is supposed to be as simple as possible to understand and it is meant to point out only the essential of three tier programming with no other burden for the reader.

Read more

You can read more about three tier here:

or you can google it to find more resources.