PHP Application Framework Design: 1 - Getting Started
Printable VersionLast edited on 07/02/08, 01:44 PM
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 predecessor’s 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… that’s 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 get everyone to work together productively and be consistent. You
may say that this is the Team Leader’s 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 what’s 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. Let’s 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.
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 application’s core features
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 handlingUsing 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
application’s configuration. We can go with XML or some other even more
intricate technology that will inevitably make the framework less efficient
but let’s 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 handlingThe 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’s 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 don’t 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.
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 let’s 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.
- 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.
- The term "free as in beer" is used in the open-source community to
distinguish things that cost nothing and people’s 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
