Leon's Weblog

October 13, 2005

PHP Application Framework Design: 1 – Getting Started

Filed under: Software Dev — Leon @ 9:10 am

This article describes the design of a complete application framework written in PHP. The reader is assumed to have a working knowledge of PHP. This part of the series describes the scope of the framework and goes over the initial class hierarchy. The following parts cover everything from session handling to creating page templates.

Introduction

I started web application development back in the days of ASP 3.0. Back then, ASP was so deficient that we had to write most of the code in DLLs and include them into the web projects. Sure, it was a bit cumbersome, but IIS was the best platform available for getting a team of VB developers productive. Then ASP.NET came around and fixed many of its predecessors deficiencies so the company where I worked at the time jumped on the bandwagon. Although I didn’t realize it at the time, .NET spoiled me with its ease of developing large applications (without much need for thorough design and planning).

Don’t get me wrong this is not an advertisement for Microsoft. While .NET was gaining ground over the Java platform (please don’t email me saying how Java is better thats not the point here), there were quite a few developments in the open source community as well. Apache has long established itself as the leading web server software and PHP became the open-source answer to ASP. By avoiding the overhead of CGI1, PHP combines speed and efficiency with the power and style of Perl. PHP also happens to be cross-platform and free (as in beer2) which makes it a no-brainer solution for many projects.

Unfortunately, PHP has its limitations. Although PHP does fix many features that make Perl unreadable, using it for large projects can be somewhat difficult (relative to Java or .NET that is). So what are the possible problems? For starters, large projects involve many developers with wide ranges of skill-sets. For web-projects, you may have people who don’t even know how to do anything save design page layouts and make pretty pictures (and yes we appreciate you guys as well). How do you get everyone to work together productively and be consistent? You may say that this is the Team Leaders job but managers can use help too sometimes. Developing in PHP can also be somewhat tedious when you find yourself copy-pasting the same blocks of code (e.g. to authenticate the user, insert the page template, connect to the database etc). This article will try to resolve these issues.

So whats missing from PHP? Well, nothing actually, if you spend some time to design what you want to accomplish. While platforms like .NET force you into doing things the Microsoft way, PHP gives you the flexibility to do things your way. Use this power wisely or else you will see the performance of your PHP application dwindle down to that of .NET or even Java (heaven forbid). And, while we are on the subject, why not just use .NET since it does so much work for you? That question is left up to the reader. Maybe the choice of using PHP was already made for you (by your management by magazine type boss). Maybe PHP is the only tool installed on your server. Or maybe you just like the syntax. I really don’t care and, if you are reading this (as opposed to having this read to you), the choice was probably already made. I also realize that there are some commercial products that provide a canned-framework for you to use that solve all the problems that I described above. I’m not trying to sell you anything just trying to show that you can easily come up with your own framework. Lets get cracking shall we.

Class Hierarchy

I like to start implementing large projects by drawing out the class diagrams and seeing how the components fit together. Luckily, as of version 4, PHP supports object oriented programming with all its bells and whistles. Although this article is written for PHP 4, you can accomplish all of the same results with PHP 5 (which is still somewhat bleeding edge at the time of writing this). Object oriented programming and design is beyond the scope of this article so I hope you are at least somewhat familiar with it. The point is that OOP is the key to developing large applications and web-development is no exception.

Update: PHP 5 is now fairly mainstream. Although the framework, as described, will still work, some of the hacks described in this article are no longer needed.

We will want to keep the framework minimal for performance reasons yet scalable and flexible enough to handle any kind of project that you may encounter. To start of, lets agree that the logic for each and every page of you application will be encapsulated in a class. This class will inherit from the system base class, which will handle everything that you may want your application to do. The benefits of this approach are fairly obvious. First, you are encapsulating all of the complex code away from the project developers. Thus, you can rest assured that your new intern will not mess with your system classes (unless you want her to for some reason). All the pages in the application will be more consistent and you will not be copy-pasting the same code into every page. More importantly, if you decide to change some underlying functions of your application, you will not have to edit each page as long as you keep the interface to your system base class consistent.

Now lets define what we want our system base class to do. Here is a basic list of features that I will go over in due time:

  • Establish the database connection
  • Define how the page will be laid out and print the HTML
  • Manage authentication and user session information
  • Define all your applications core features

Here is the framework and all of it’s glory:

class_system.php     - The base class which includes the following
|_constants.php      - Application constants
|_functions.php      - Generic functions
|_class_user.php     - Session handling
|_class_template.php - Template handling
|_class_form.php     - Form handling

Using this framework, all of your pages should have this structure:

include "include/class_system.php";
class Page extends SystemBase {
    //your code here
}
$p = new Page();    

The idea here is that we want to minimize the time spent on the most common tasks such as creating new pages. In this case, all we have to do is include the base class (which will make sure that all the other necessary dependencies are also included), extend the base class, and simply make an instance of the class to get the ball rolling. This way you can start thinking about what the page will actually do rather than waste time with common tasks.

Configuration Files and Useful Functions

The simplest idea to explain is the need for a file that will contain the applications configuration. We can go with XML or some other even more intricate technology that will inevitably make the framework less efficient but lets start off with the basics. All we really need is a set of constants that define strings such as the database connection or installation paths. These are strings that we would not want to hunt down within each page of the application every time the database password is changed or the application is moved. The file constants.php is just that. The settings that I find most useful are the URL and system paths to the application.

On a similar note, there are some functions that we would like to always make available and I include them in the file functions.php. These functions are not made as methods of the base class because we would like to make them available to all the classes of our application (including those that do not extend the base class). I will go over the functions in this file when we need to use them. As a simple example, I include the me() function, which returns the file name of the current PHP file. (VB 6 programmers will remember that me is a reference to the current form).

function me() {
    return substr($_SERVER['PHP_SELF'], strrpos($_SERVER['PHP_SELF'],'/')+1,
                  strlen($_SERVER['PHP_SELF']));
}   

The Base Class

Now lets dissect the class_system.php file line by line. The first thing that we do is call session_start() which starts a new user session if none exists or does nothing otherwise (more on sessions in part 2 of this series). Then we include all the need libraries. There are two things to note about the way this is done. First, require_once is used which will only include the file once and throw an exception if the file is not available (as opposed to include which would only make a warning and proceed with execution of the page). The second thing to note is that absolute paths are used to include the files which are all in the same folder. This is done because we don’t want to have to reconfigure the server to get the application working (by changing the include path used in PHP). Relative paths would not work because the base class will be included from various other pages and the relative path would have to work for each page that includes the base class.

session_start();
$path = dirname(__FILE__);
require_once "$path/constants.php";     //defines
require_once "$path/functions.php";     //generic functions
require_once "$path/class_user.php";    //session handling
require_once "$path/class_template.php";//template handling
require_once "$path/class_form.php";    //form handling

The implementation of the system base class is not at all surprising if you consider the way we intended to use it. Since the code is only executed when an instance of the class is created, we put everything that we want to happen into the class constructor. Also, since the base class will not be able to know exactly how to render each page, we make the methods abstract and let polymorphism take care of the rest (i.e. the derived classes will override the base classes methods.) Here is a list of the methods called from within the constructor:

init()              - initialize the page
authenticate()      - perform authentication
handleFormEvents()  - handle page submits
printPage()         - output the HTML
destroy()           - destroy page (close DB connection)

Unfortunately, PHP 4 does not enforce many OOP principles (note that PHP 5 has a new and much more robust object model). You can get everything to work, such as inheritance and polymorphism, but it takes some effort and some faith. For example, there is no concept of method protection so all methods (and attributes for that matter) are made public. This is a big no-no when it comes to OOP so a common convention is to prefix all methods that are intended to be private with an underscore ( _ ) and then take it on faith that the users of the class will not call these methods. Another problem is that we cannot declare the class to be abstract (i.e. we do not want people to declare instances of the base class but rather force them to inherit from it). We can get around this limitation by
including the following lines at the top of our constructor (you can read this article for an in-depth analysis of how this works). The code checks to see if the instance of the class is the base class and throws an exception.

if (!is_subclass_of($this,'SystemBase')) {
    trigger_error('Base instantiation from non subclass',E_USER_ERROR);
    return NULL;
}

Database Access

The application framework should provide a clean interface to your data regardless of the type of SQL server uses. It is also very desirable to have loose coupling between your application and your database backend. So, for example, if you ever decide to change the location of the database or even change the type of SQL server used, you dont want to have to rewrite all your code. Luckily, someone has already tackled this problem for us and all we have to do is use the PEAR::DB module, which provides a consistent interface to several popular database servers. You can read all about it at the PHP Extension and Application Repository and I recommend reading the following PEAR::DB tutorial as well.

Update: The new preferred database library is PEAR::MDB2 which is a merge of PEAR::DB and Metabase libraries. You will find migrating from DB to MDB2 fairly straight forward. There is even a migration guide.

Assuming that the PEAR::DB module is installed and configured correctly, all we need to have a persistent database connection available in every page is a reference to a PEAR::DB object. We will create this object as a member variable in the SystemBase class and create the database connection inside the constructor. We already have the necessary constants to build a DNS string and there is also a function in functions.php that returns a pointer to a new PEAR::DB object.

function &db_connect() {
    require_once 'DB.php';  //pear db class
    PEAR::setErrorHandling(PEAR_ERROR_DIE);

    $db_host = DB_HOST;
    $db_user = DB_USER;
    $db_pass = DB_PASS;
    $db_name = DB_NAME;

    $dsn = "mysql://$db_user:$db_pass@$db_host/$db_name";
    $db = DB::connect($dsn);
    $db->setFetchMode(DB_FETCHMODE_ASSOC);
    return $db;
}

The function imports the PEAR:DB library if it has not yet been imported, creates a DNS string, and connects to the database. All we have to do now is use this function in the SystemBase constructor as follows: $this->db = db_connect(); and we are done.

Optimization

You may think it is a bit too early to discuss code optimization at this point since we have yet to implement most of the necessary features. Code optimization is more of an art and I will not go into a line-by-line analyses of how we can achieve the same effect faster. The point here is that we already can tell what the major bottlenecks will be so lets nip the problem now before its gets out
of hand. The most obvious performance loss will be due to the persistent database connection that we establish for each page. In fact, if you make a few timing test to benchmark the performance of the framework you will see that establishing the connection alone take more time than everything else combined. Although it is a necessary evil and we will have to pay that price for it when using the database, not every page in our application will have to connect to the database. In fact, not every page will need to print out HTML content (i.e. it may just process the request and redirect). So before we go on lets define a few constants for the base class to indicate that we don’t need a database connection or to print the page. You will simply define these constants in pages that need them.

    define('NO_DB', 1)    if persistent DB connection is not needed 
    define('NO_PRINT', 1) if page does not get rendered

Summary

So far, we have laid the foundation for how our application will behave and how the framework will be used. We established an OOP design for the application, defined some constants and function to expedite routine work, and added database support. Read the next part to see how to manage session data and users.

  1. CGI (Common Gateway Interface) was a popular method for using server side code to generate dynamic web pages. Any programming language can be used for CGI as long as it can read system environment variables that are set by the web server to provide information about the page request. Perl has been the language of choice for CGI programming because CGI programs involve a lot of text processing. Unfortunately, CGI does not scale well because each page request requires the server to fork a new process to run the CGI program. mod_perl is a way to reduce the overhead by including Perl as an internal Apache module; however, benchmarks show that PHP (which also runs as an Apache module) is still faster.
  2. The term “free as in beer” is used in the open-source community to distinguish things that cost nothing and peoples rights. The term was originally published in this paper.

Navigate: Part 1: Getting Started, Part 2: Managing Users, Part 3: Page Templates, Part 4: Forms and Events

1 Comment »

  1. Hi!
    Excellent material, brief, as a matter of fact, with examples. Many thanks to you. Personally for me has very much helped to realize some aspects of web-programming. Once again thank you.

    Comment by Alex — March 29, 2007 @ 8:42 am

RSS feed for comments on this post. TrackBack URI

Leave a comment