As PHP applications become more and more complex, it can be easy to end up with a tangled mess of code that makes maintenance nearly impossible. Applying the concept of tiered applications can help to alleviate some of the difficulty in maintaining complex applications.
What Do You Mean by Tiered Applications?
Tiered programming is the practice of keeping different components, ideas, or languages separate from each other.
In front-end development, tiered markup would be using external stylesheets and JavaScript.



By linking to a CSS file rather than embedding styles in your HTML markup, it becomes easier to change the
formatting of your websites because now all styling information is conveniently stored in one place, separated
from the markup of the document. And multiple HTML pages can pull in the exact same CSS file, your whole site
can be updated style-wise by simply changing one line of CSS.
In back-end development, the same rules apply, but we're dealing with different components. In broad terms, we're
looking at three tiers: the Database (storing and retrieving data), Business
(processing and handling of data), and Presentation (how data is displayed) tiers.
Why Should I Care?



It might not be immediately obvious, but separating your applications into a tiered structure will have a huge
impact on your code's ability to change in the future. For example, if you have a blogging system set up, and
it becomes necessary to create an RSS feed for the blog, a properly tiered application would allow you to simply
set up an RSS template, then call the database and business functions that you've already written.
On the opposite end, if your client suddenly decided that PostgreSQL was a better choice for their organization
than MySQL, you would only have to rewrite your database functions, all without touching the business or
presentation logic of the application.
In terms of reusability, you could have multiple database functionalities (supporting MySQL, PostgreSQL, and
Oracle, for example), that could be easily dropped into new rollouts of your application using just a few
lines of code in one place, rather than editing several lines of your code across multiple functions.
The Wrong Way
To start, let's take a fairly simple task—pulling the title and body text of an entry out of a database,
creating a shortened version of the entry, and placing the data into HTML markup—and go about it in
entirely the wrong way. In the following code, we'll write one function to perform all of our
tasks:
1 |
|
2 |
function displayEntry() |
3 |
{
|
4 |
$entryDisp = NULL; |
5 |
|
6 |
// Get the information from the database
|
7 |
$sql = "SELECT title, entry FROM entries WHERE page='blog'"; |
8 |
$r = mysql_query($sql) or die(mysql_error()); |
9 |
while($entry = mysql_fetch_assoc($r)) { |
10 |
$title = $entry['title']; |
11 |
$text = $entry['entry']; |
12 |
|
13 |
// Create the text preview
|
14 |
$textArray = explode(' ',$text); |
15 |
$preview = NULL; |
16 |
for($i=0; $i<24; $i++) { |
17 |
$preview .= $textArray[$i] . ' '; |
18 |
}
|
19 |
$preview .= $textArray[24] . '...'; |
20 |
|
21 |
// Format the entries
|
22 |
$entryDisp .= <<<ENTRY_DISPLAY |
23 |
|
24 |
<h2> $title </h2> |
25 |
<p> |
26 |
$preview
|
27 |
</p> |
28 |
ENTRY_DISPLAY; |
29 |
}
|
30 |
|
31 |
return $entryDisp; |
32 |
}
|
This code outputs HTML markup along these lines:
1 |
|
2 |
<h2> Entry One </h2> |
3 |
<p>
|
4 |
This is the shortened description of entry number one. It |
5 |
displays the first 25 words of the entry and then trails |
6 |
off with an ellipsis... |
7 |
</p>
|
8 |
<h2> Entry Two </h2> |
9 |
<p>
|
10 |
This is the shortened description of entry number two. It |
11 |
displays the first 25 words of the entry and then trails |
12 |
off with an ellipsis... |
13 |
</p>
|
Though this might appear logical, it's actually really undesirable. Let's go over what makes this
code a less-than-optimal approach.
The Problem with This Approach



Poor Legibility—This code is diluted. Its purpose, though documented in the comments
(sort of), is tough to discern. If you wrote this, then came back to it in six months, it wouldn't be instantly
clear what was going on, which means a few seconds/minutes wasted trying to interpret your own code.
Too Narrow in Focus—This function is crippled by its specificity: the database query only works for one type of entry;
the text preview creation is hard-coded into the function; the formatting is specific to the type of entry
being displayed. In order to create a slightly different implementation of this functionality, we'd be forced to
create a second function that looked almost exactly the same, even if all we needed to change was the number of
words in the text preview.
Lack of Scalability—This is pretty closely related to the idea of being too narrow in focus; if we want to add more functionality (such
as a link or an image), our function will get larger and more difficult to manage. And what if we want to add
conditions that affect how an entry is displayed? It's easy to see how programming like this allows for code to
quickly become sprawling and unmanageable.
The Big Problem: Lumping Database, Business, and Display Logic
This is the sweeping issue that is causing all of the aforementioned problems.
By combining all three of our logic
types, we end up with a narrow, messy, hard-to-manage, nearly-impossible-to-reuse tangle of code.
Imagine an application where each type of display (RSS feed, entry preview, full entry display, etc.) was built with
a function like the one above, with the database access, business logic, and presentation logic all written together.
Now imagine that there are nine pages on the site, all of which have their own entry display and preview functions.
Even if we assume the application is really simple and that there are only two functions per site page, we're still
looking at almost twenty functions that will need to be updated if changes become necessary.
Improving the Code
To improve the code above, we'll spread our different types of logic across several functions. If done properly, we
should end up with a set of highly reusable, easily understood functions that stack to perform a variety of tasks.



To get started, we'll plan out the necessary functionality to get a better idea of
how it should be constructed:
- Retrieve the entry and title columns for a given page from the "entries" table in the database
- Shorten the body of the entry to a 25-word preview and append an ellipsis
- Insert the data into HTML tags to display on the user's browser
As you can see, our plan clearly identifies a database, business, and presentational tier. We can now
write functions to fulfill each of these steps with relative ease.
Step 1—The Database Tier
To get information from the database, we're going to write a very simple function. To encourage good coding
practice, I'm going to use the mysqli extension, but
I'm not going to focus on how it works. If you're not using it already, I'd encourage you to explore mysqli or a
similar extension (i.e. PDO) to secure your MySQL queries against
injection attacks.
So let's jump right into the code:
1 |
|
2 |
function getDataFromDB($page) |
3 |
{
|
4 |
/*
|
5 |
* Connect to a MySQL server
|
6 |
*/
|
7 |
$mysqli = new mysqli('localhost', 'user', 'password', 'world'); |
8 |
if (mysqli_connect_errno()) { |
9 |
printf("Connect failed: %s\n", mysqli_connect_error()); |
10 |
exit; |
11 |
}
|
12 |
|
13 |
/*
|
14 |
* Create a prepared statement for pulling all entries from a page
|
15 |
*/
|
16 |
if ($stmt = $mysqli->prepare('SELECT title, entry FROM entries WHERE page=?')) { |
17 |
/*
|
18 |
* Create a multi-dimensional array to store
|
19 |
* the information from each entry
|
20 |
*/
|
21 |
$entries = array(); |
22 |
|
23 |
/*
|
24 |
* Bind the passed parameter to the query, retrieve the data, and place
|
25 |
* it into the array $entries for later use
|
26 |
*/
|
27 |
$stmt->bind_param("s", $page); |
28 |
$stmt->execute(); |
29 |
$stmt->bind_result($title, $entry); |
30 |
while($stmt->fetch()) { |
31 |
$entries[] = array( |
32 |
'title' => $title, |
33 |
'entry' => $entry |
34 |
);
|
35 |
}
|
36 |
|
37 |
/*
|
38 |
* Destroy the result set and free the memory used for it
|
39 |
*/
|
40 |
$stmt->close(); |
41 |
}
|
42 |
|
43 |
/*
|
44 |
* Close the connection
|
45 |
*/
|
46 |
$mysqli->close(); |
47 |
|
48 |
/*
|
49 |
* Return the array
|
50 |
*/
|
51 |
return $entries; |
52 |
}
|
If you break down what this function is doing, we are literally just requesting
two columns (title and entry) from our table (entries), then storing each entry
in a multi-dimensional associative array, which is the return value of the
function. We pass one parameter, $page, so that we can determine which page we're
grabbing information for. In doing so, we've now created a function that will work
for every page of our site (provided they all have a 'title' and 'entry' field).
Notice that our function does nothing to handle the data; it simply acts
as a courier, grabbing the information we request and passing it on to whatever
comes next. This is important, because if it did anything more, we'd be in the
realm of business logic.
Why is it important to separate the business logic from the
database logic?
The short answer is that it allows for database
abstraction, which essentially means that the data could be migrated from MySQL
into another database format, such as PostgreSQL or Oracle, all without
changing the data-handling functions (business tier), since the output would
still simply be a multi-dimensional associative array containing entries with a
title and entry column, no matter what kind of database we're using.
Step 2—The Business Tier
With the data loaded into our array, we can start processing the information to
suit our purposes. In this example, we're trying to create an entry preview. In
the first example, the length of the preview was hard-coded into the function,
which we decided is a bad practice. In this function, we'll pass two parameters
to our code: the text to process, and the number of words we want to display as
our preview.
Let's start by looking at the function:
1 |
|
2 |
function createTextPreview($text, $length=25) |
3 |
{
|
4 |
/*
|
5 |
* Break the text apart at the spaces and create the preview variable
|
6 |
*/
|
7 |
$words = explode(' ', $text); |
8 |
$preview = NULL; |
9 |
|
10 |
/*
|
11 |
* Run a loop to add words to the preview variable
|
12 |
*/
|
13 |
if($length < count($words)) { |
14 |
for($i=0; $i<$length; $i++) { |
15 |
$preview .= $words[$i] . ' '; // Add the space back in between words |
16 |
}
|
17 |
$preview .= $words[$length] . '...'; // Ellipsis to indicate preview |
18 |
} else { |
19 |
/*
|
20 |
* If the entry isn't as long as the specified preview length, simply
|
21 |
* return the whole entry.
|
22 |
*/
|
23 |
$preview = $text; |
24 |
}
|
25 |
|
26 |
/*
|
27 |
* Return the preview
|
28 |
*/
|
29 |
return $preview; |
30 |
}
|
In the function above, we simply check that the number of words in the supplied
entry is greater than the number of words we want to show in our preview, then
add words to the newly-created $preview variable one at a time until we hit our
target length, at which point we append an ellipsis and return $preview.
Just like in our database tier, we're keeping the code within the bounds of the
business tier. All we're doing is creating a text preview; there is no
interaction with the database, and no presentational elements such as HTML
markup.
Step 3—The Presentation Tier
Finally, we need to display the data we've retrieved and processed. For our
purposes, we'll be displaying it with extremely simple HTML markup:
1 |
|
2 |
<?php
|
3 |
$entries = getDataFromDB(); // Load entries into an array |
4 |
foreach($entries as $entry) { |
5 |
/*
|
6 |
* Place the title and shortened entry text into two appropriately
|
7 |
* named variables to further simplify formatting. Also note that
|
8 |
* we're using the optional $length parameter to create a 30-word
|
9 |
* text preview with createTextPreview()
|
10 |
*/
|
11 |
$title = $entry['title']; |
12 |
$preview = createTextPreview($entry['entry'], 30); |
13 |
?>
|
14 |
|
15 |
<h2> <?php echo $title; ?> </h2> |
16 |
<p> <?php echo $preview; ?> </p> |
17 |
|
18 |
<?php
|
19 |
} // End foreach loop |
20 |
?>
|
The idea behind this code is simple: first, load the entries using our
database function, then loop through the entries, shortening the entry text
using our preview function and then placing the title and entry preview into
presentational markup.
Now, in order to change the layout of the entry previews, only two lines of
HTML need to be adjusted. This is a far cry less confusing than the original
function that handled all three tiers.
Closing Thoughts on Tiered Programming
I'd like to point out that the above example is very basic, and is meant only to
demonstrate the concept of tiered programming. The idea is that by keeping the
different types of programming logic separated, you can vastly increase the
readability and maintainability of your code.
NOTE: There's a great article
on TheDailyWTF.com discussing the use and misuse of tiered software design, and
wonderful commentary on the
article that presents differing opinions. Multi-tiered applications are extremely useful, but also easy to
misunderstand and over-complicate, so remember to thoroughly plan your software before building to avoid causing
more problems than you're solving.
Do you have any thoughts on tiered applications? What steps are you taking to
maximize ease of maintenance and future changes in the code you write? Let us
know in the comments!
- Subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.