Introduction

This article investigates the design and implementation of a scalable dynamic website using PHP as the deployment platform.
A N-tier architecture is proposed to facilitate scalability. Each tier is to be loosely coupled with the others allowing for the separation of functionality and administration. The article also investigates the use of Web Services and XML with PHP to realize such system.
An N-tier Architecture
Figure 1: A Scalable N-Tier Architecture
Figure 1: A Scalable N-tier Architecture
The design is based around the introduction of a Content Server. The Content Server provides a Web Service based interface over the Entities and Relations in the database. This separation allows the sites to retrieve/update the content without having to worry about the intricacies of SQL or the location of particular content. It also gives us an opportunity to provide features such as security and caching at a single place. Each site retrieves the data needed using the Content Server. The site would ship this XML data to the browser along with the reference to the XSL needed to present this in HTML. The browser would access the referred XSL (note: this XSL may itself be dynamically generated) and apply it to the XML to obtain the HTML page. For quicker response times the transformation may be done at the server side.
A Web Service implementation in PHP
PHP provides an ideal platform to implement Web Services. There are numerous PHP libraries available to make this as easy and painless as possible. The library used here is called NUSOAP . The following is a code snippet of how you would expose a PHP function as a Web Service method.

<?php
require_once('nusoap.php');

$server = new soap_server;
$server->register('get_siteuser');
$server->register('get_siteuser_list');

function 
get_siteuser($user_id
{

}
function 
get_siteuser_list($siteuser
{    

}
?>
NUSOAP also provides Web Service client-side libraries. The following is example code for invoking a Web Service:

<?php
require_once("nusoap.php");
$soapclient = new soapclient("http://localhost/~ nmcengine.php");
echo 
$soapclient->call('get_siteuser',array('user_id'=>"$arg1"));
?>
As you can see calling a web service using PHP is simply three lines of code. You may have noticed that WSDL is not being used to invoke the web service. NUSOAP libraries provide functionality to create the WSDL which will be needed if the web service is being invoked by other means in PHP or a different programming language.
The Content Engine
The Content Engine simply provides an XML based interface to the database schema. The Content Engine interface maps directly onto the tables, their attributes and the relationships between the tables. Given the following simplified schema:
Content Engine diagram
The following are some of the methods exposed:
getUser(user_id):User Get the user element given the user id
findUsers(user):UserList Get a list of user elements given the criteria in the user element. Here the user element would be partially populated with the fields we need to match in the "where" clause of our query.
insertUser(user):boolean Insert new user. Here the user details are provided in the user element
updateUser(user):boolean Update the specified user
deleteUser(user_id):boolean Delete the specified user
findMediaByUser(user_id):MediaList Find Media that is owned by the specified user
The User and UserList XML elements are specified by the following graphical representation of an XML schema:
XML schema
The Content Engine Implementation
There are two main challenges when implementing each function of the Content Engine:
  1. How to transform the resultset into XML in a generic manner?
  2. How to access the attribute values from the XML coming in (i.e. in find, insert and update functions).
Generic transformation of query results to XML
In PHP (and no doubt other languages) each resultset, as well as containing the result data, contains the names of the attributes returned. This allows us to generically generate XML from a resultset without having to "hardcode" any XML tags for the attributes, however for each row we have to define the enclosing XML tags. The following are the interface definitions of two helper functions used to return an XML element and an XML element list given the query to execute and the connection to execute it on:

<?php

/* 
Returns the first rows as elements within their column-names as the tag-names
The elements are enclosed in the specified $elemName
 */
function get_element($elemName$conn$query
{

}

/*
Returns the first rows as elements enclosed within their column-names as the tag-names
The elements are enclosed in the specified $elemName. The full list is enclosed in the 
specified $listName
*/
function get_element_list($listName$elemName$conn$query
{
        
}
?>
Manipulating XML using XPATH in PHP and dynamic query generation
The easiest way to extract the values from an XML document is to create a DOM (Document Object Model) from the document and use XPATH to identify the individual elements and attributes. The new versions of PHP provide built in DOM functionality however if it is not there is an open source API (http://sourceforge.net/projects/phpxpath/) that does exactly this. The following is a code snippet using this API.

<?php

require_once('../commonfiles/XPath.class.php');
$user "&lt;User>&lt;user_id>ID0001&lt;/user_id>&lt;/User>";
$xpath = new XPath();
$xpath->importFromString($user);
$value$xpath->getData('/User/user_id'));
?>
The next challenge is to dynamically generate the query given the XML. This is quite simple as the tag-names in our XML directly correspond to the table names and the column-names for those tables. This allows us to dynamically generate the SQL corresponding to the elements in the provided XML. For example given the following XML as criteria for one of our find methods:
<User><surname>Jackson</surname><preffered-genre>pop</preffered-genre></User>
The query in SQL would look like the following:
SELECT * FROM User WHERE surname='Jackson' AND prefered-genre='pop'
As you can see, all the dymanic parts of the query can be extracted from the XML itself. The same could be accomplished for the insert and update queries.
NOTE: the above somewhat simplifies the complications involved in dynamic query generation. For example how would you make sure that there are no enclosing commas for numeric values and how you would implement SQL clauses to match patterns (e.g. LIKE ‘%@mac.com”). However these could be accomplished using different techniques such as maintaining a data dictionary or factoring in additional metadata from the resultset. These issues are deliberately ignored as they are beyond the scope of this brief article.
A Content Engine Generator
As the above sections states a lot of the functionality of the Content Engine can be accomplished in a generic and programmatic way. However the implementation of each function of the content server is long-winded and tedious. So why not generate most of the content engine. This is where PHP again proves to be a natural.
Rather then generating HTML from PHP code we can generate PHP code from PHP code … if you know what I mean :). To illustrate how simple this is the following is PHP/pseudo-code code-snippet to generate the "get by id" and "get list by criteria" functions for your schema: (NOTE: you don’t have to give too much attention to the code itself but notice that the code represented in "red" is what will be generated and other colors represent the generator itself.)

<?php

//get all table description in your schema
for ($i=0$i count($tables); $i++) {
    
$table_name $tables[$i];
    
$column_names getColumnNames($db_name$table_name$conn);
    
$nmcengine.="
    
function get_$table_name(\$$column_names[0]) {
    \$query=\"SELECT * FROM $table_name WHERE $column_names[0]=\$$column_names[0]\";
    global \$conn;
    return get_element('$table_name', \$conn, \$query);
}

function get_{$table_name}_list(\$$table_name) {
    \$xpath = new XPath();
     \$xpath->importFromString(\$$table_name);
    \$query=\"SELECT * FROM $table_name\";
    \$clause_count=0;\n"
;
    
    for(
$j=0;$j count($column_names);$j++) {
        
$nmcengine.="
    \$value=interpret_result(\$xpath->getData('/$table_name/$column_names[$j]'));
    nextClause(\$query, \$value,\$clause_count, '$column_names[$j]');"
;
    }    
    
$nmcengine.="
    
    global \$conn;
    \$result = get_element_list('{$table_name}_list', '$table_name', \$conn, \$query);
    return displayArray(\$result);
}    
"

?>
There will be some functions that may be impossible to generate (i.e. application specific helper functions) the generated code can include a static page where you would define these functions manually.
A Datalayer (abstracting the database specific operations)
In PHP the database based functions are vendor specific. If the Content Server is to be able to support multiple database vendors then these database functions need to encapsulated by a uniform API i.e. a datalayer.
The datalayer is implemented by defining an API that all operations in Content Engine would use. Then all we’ll need to do is to change the include file for the API implementation to switch the database. The following are the function definitions for the API.

<?php
// initialise the db environment
function init_db_env () {
    
}
// creates and returns a database connection
function create_connection ($host$username$password) {
    
}

// use a particular database (only needed for some database) the one that don’t it can be // left blank
function use_db($dbname) {
    
}

// close the specified database connection
function close_connection($conn) {
    
}

/* 
Returns the first rows as elements within their column-names as the tag-names
The elements are enclosed in the specified elemName
 */
function get_element($elemName$conn$query) {
    
}

/*
Returns the first rows as elements enclosed within their column-names as the tag-names
The elements are enclosed in the specified elemName. The full list is enclosed in the 
specified listName
*/
function get_element_list($listName$elemName$conn$query) {
        
}


/*
Inserts into the tablename the values specfied by the name/value pairs in the valuemap
*/
function insert_element($tablename$value_map$conn) {
    
}

/*
Executes the specified insert query on the specified connection
*/
function do_insert($conn$query) {
    
}

/*
Update the specified table with the value specified in the value_map and the sellection 
criteria specified in the selection_map using the specified connection
*/
function update_element($tablename$value_map$selection_map$conn) {
    
}

/*
delete from $tablename where $id_field_name=$id_field_value, using the specified connection
*/
function delete_element($tablename$id_field_name$id_field_value$conn) {
    
}
?>
Note: This would only work if the API strictly conforms to the naming conventions defined in the above definition. This would be better enforced using a more object-oriented approach.
A XSL based Presentation Layer
Now that we are getting all our data in XML, all our PHP pages have to do is use the Content Engine to get the relevant data in XML and supply the link to XSL that represents the presentation logic for that page. The XML is sent to the browser with a link to the XSL that transforms it into HTML. Most good web-browsers come with a built in engine to do the transformation at the client side. However if the browser does not support XSL transformation, this could be determined at the server side the server can do the transformation and send the result for those browsers. Note: for those occasions when the layout of the page itself needs to change dynamically we can embed PHP in the XSL that gets executed when the browser requests the XSL for a particular XML.
Conclusion
The proposed architecture provides the following set of benefits:
PHP’s comprehensive support for XML-based technologies make’s it an ideal platform for developing rapid Web Service Based N-Tier applications. Using XML as the standard data format within an N-tier application greatly simplifies the separation of concerns that is the corner stone of such architectures.
It would be interesting to see how well this proposed architecture performs in a real-world application given the real-world security and performance concerns.
Acknowledgements
This article was developed as a direct result of a team exercise for the e-Commerce Technologies Module as part of MSc in Advanced Comp Science at the University of Manchester. The other team members are: