Two Tier Web Applications in PHP

Subject: MVC, Web, development, PHP, programming, theory, architecture, object-oriented

This article demonstrates a very simple MVC management application for PHP.

Below is a simple example of a Product Management application. Here's the finished result: List Products. For this example, I only include the code for listing the products, and I left out the database queries. Of course, it is simplified to the max because it's just an example.

A Sample 2-Tier Application

Break the application into "actions". Here's some examples of actions in the Product Management application: "list", "edit", "update", "new", "delete", where each action performs an action with a product or products. Each has its own PHP function associated with it.

function runListAction(&$input, &$output) {}
function runEditAction(&$input, &$output) {}
function runUpdateAction(&$input, &$output) {}
function runNewAction(&$input, &$output) {}
function runDeleteAction(&$input, &$output) {}

Notice how the names of the functions contain the action name. Given an action name, I can produce the corresponding function name like this:

$functionName = "run".ucfirst($action)."Action";

$input and $output are associative arrays. I feed all the data from the user and put it into $input, as you will see later, in Application.php. The function runListAction() would query the database for products and pack the records into $output, except for this example, I'll leave the database part out. Then runListAction() calls the template, Product/list.php.

Group these actions together into a class definition. This will be the controller:

<?php

 
// Product/ProductController.php
  // The Product "Controller"
 
 
Class ProductController
 
{
    var
$app;
   
    function
ProductController(&$app)
    {
     
// Make the Application's services
      // available to the controller
     
$this->app =& $app;
    }
   
    function
runListAction(&$input, &$output)
    {
     
// Skipping database stuff.
      // For this example we'll provide our own data here:
     
$output["records"][] = array(
       
"SKU"=>"1234",
       
"Title"=>"Book",
       
"Price"=>"7.00"
     
);
     
$output["records"][] = array(
       
"SKU"=>"2222",
       
"Title"=>"Chair",
       
"Price"=>"16.00"
     
);
     
$output["records"][] = array(
       
"SKU"=>"7654",
       
"Title"=>"Pencil",
       
"Price"=>"1.00"
     
);
     
     
// Call the template
     
include("Product/list.php");
    }
   
    function
runEditAction(&$input, &$output)
    {
      print
"Product.edit: not coded yet";
    }
   
    function
runUpdateAction(&$input, &$output)
    {
      print
"Product.update: not coded yet";
    }

    function
runDeleteAction(&$input, &$output)
    {
      print
"Product.delete: not coded yet";
    }

    function
runNewAction(&$input, &$output)
    {
      print
"Product.new: not coded yet";
    }
  }
 
?>

Save the controller class definition in its own file, name the file the same name as the class (ProductController.php) and put it in a folder called 'Product': Product/ProductController.php.

Here's the php/html template for listing the products. Notice the minimal php code:

<?php
 
// Product/list.php
  // The "View"
?>
<html>
<head><title>Products</title></head>
<style type="text/css">
  body { background: #FFFFFF; font-family: Arial, Helvetica, sans-serif; }
  table { background: #CCCCCC; }
  th { background: #EEEEEE; }
  td { background: #FFFFFF; }
</style>
<body>
<h1>Products</h1>

  <table border="0" cellspacing="2" cellpadding="5">
    <tr>
      <th>SKU</th>
      <th>Title</th>
      <th>Price</th>
      <th>Action</th>
    </tr>
<?php
 
foreach( $output["records"] as $product ) {
?>
    <tr>
      <td><?php ph($product["SKU"]) ?></td>
      <td><?php ph($product["Title"]) ?></td>
      <td align="right"><?php ph($product["Price"]) ?></td>
      <td>
        <a href="index.php?action=Product.edit&SKU=<?php ph($product["SKU"]) ?>">edit</a>
        <a href="index.php?action=Product.delete&SKU=<?php ph($product["SKU"]) ?>">delete</a>
      </td>
    </tr>
<?php
 
}
?>
  </table>
 
</body>
</html>

It shouldn't take much time for the novice php programmer to figure out how this template works. Here's what the file looks like in Dreamweaver. Very clean...

Build another class that provides a global object that manages all the controllers together and provides an easy way of calling their actions.

<?php
 
 
// lib/Application.php

  // This class acts as the hub of the application
 
Class Application
 
{
    function
run($controllerAction)
    {
     
// Decode the state into controllerName and action
      // e.g. "Product.list" becomes "Product" and "list"
     
list($controllerName,$action) = explode(".", $controllerAction);
     
     
// Import the controller's class definition
     
$controllerClass = $controllerName."Controller";
      include(
"$controllerName/$controllerClass.php");
     
     
// Instantiate the controller
     
eval("\$controller = new $controllerClass(\$this);");
     
     
// Set up the input and output for the controller
     
$input = getInput();
     
$output = array();
     
     
// Finally, we call the controller's action
     
$functionName = "run".ucfirst($action)."Action";
      eval(
"\$controller->$functionName(\$input, \$output);");
    }
  }

?>

Save this in a file called Application.php and put it in a folder called 'lib': lib/Application.php.

Here's the file that is actually called by the browser, index.php. The url for this example is index.php?action=Product.list.

<?php

 
// index.php

 
require_once("lib/Application.php");
 
 
// Convenience function used by the template
  // 'ph' is short for "print html"
  // Use this to print non-html strings in an html context
 
function ph($value)
  {
    print
htmlspecialchars($value);
  }
 
 
// Another convenience function. Used for getting input from the user
  // We don't use global variables to get user input, ok?
 
function getInput()
  {
    if( !isset(
$_REQUEST) ) // php version < 4.1
   
{
      global
$_REQUEST;
     
$_REQUEST = array_merge(
       
$GLOBALS["HTTP_GET_VARS"],
       
$GLOBALS["HTTP_POST_VARS"]
      );
    }
    return
$_REQUEST;
  }

 
// Get the requested action
 
$input = getInput();
  if( !isset(
$input["action"]) )
    die(
"\"action\" isn't defined!");
 
 
// Run the action
 
$app = new Application();
 
$app->run($input["action"]);
 
?>

Basically for a 2-tier application, the "View" should never have any code that deals with database queries. It should deal only with unpacking data prepared by the controller, and then displaying it. If you are working with a web designer who knows a little php, you can easily train the designer to handle all the php code in the template.

For a 3-tier application, the controller should also never deal with database queries. Instead it should work with data objects that privately know how to interface with the database engine.

As you start get into it and start actually using a multi-tier framework, you may be surprised how much work will be involved to get to a point where you can just concentrate on building your applications instead of tweaking and modifying your framework to get it to handle a wide range of applications. In the long run, it's definitely worth it.

As a side note, I've looked at the various 3rd-party template engines for php, and wondered "why would I need any of them?" It just doesn't make any sense if you already know how to separate application logic from display logic. Isn't php a template engine already?