AJAX Chat Tutorial Part 1: Introduction, The Zend Framework
Introduction
Creating a chat application is not a difficult task. Honest!
This tutorial is a step by step introduction to creating a lightweight chat application using XML as a storage medium. As personal motivation, I develop PHP games as a hobby. In pursuit of that hobby I've found that offering a flat threadless forum in such games tends to result in heavy usage as players attempt to use it as a chat room. The reason is that even with the advent of irc and instant messengers, users continue see an alternative web based solution as attractive for a number of reasons whether its limited access to instant messengers through a corporate proxy or simply for convenience sake.
You can download the current version of the source code for this application from: chat.tar.gz or chat.zip. This will be the final version of the code after finishing the tutorial (plus any subsequent patches).
To create this application, I'll be using standard third party libraries. I have no intention of creating new solutions for tasks which are already well covered by a range of excellent libraries. A lot of unnecessary work can be blamed on the "Not Made Here" mentality, so let's not subscribe to that mentality.
On the server side I will be utilizing the Zend Framework 0.20 (released 31 October 2006). On the client side, AJAX and Javascript processing will be simplified by using the Prototype library. Should any visual effects be required, I will use Scriptaculous. Proponents of alternate solutions like jQuery, Dojo or any number of others can apply the same principles using their preferred libraries as they wish.
This chat application tutorial focuses on blending the Zend Framework (PHP5), Javascript and Prototype libraries in order to create a simple elegant solution. The code is liberally licensed under the New BSD License to the extent it's original and readers are free to modify (and mangle) the code under that license as they see fit. Formalities aside, let's dig in!
The Zend Framework
The Zend Framework has quickly established itself as one of my favorite libraries in PHP. Personally I dislike large frameworks which establish an artificial structure you're forced to adhere to. The Zend Framework however is packaged as a library of mostly independent classes which is simple to adapt and mix with my personal library of code.
You can download the library from http://framework.zend.com. Documentation for the current release is sufficient and I know from following the mailing list that it's improving all the time (there's a wikified version under review). Aside from the official documentation, I would recommend http://www.akrabat.com/zend-framework-tutorial/ which is an excellent introductory tutorial well worth reading. Kudos to Rob Allen for putting it together.
Directory Structure
To start, download version 0.20 of the Zend Framework from http://framework.zend.com/download. Create a new directory in your webroot called "chat-tutorial". Inside the new directory we'll need to set up the basic directory structure required to organize the files for the chat application. The recommended directory structure (though feel free to deviate) is as follows:
chat-tutorial/
/application
/controllers
/views
/library
/incubator
/public
/javascript
/styles
/javascript
/data
The ./library/Zend directory from the downloaded Zend Framework should be copied to our own library directory as above. The chat-tutorial/library directory should now contain a Zend directory containing the core library and the Zend.php file. There is also a separate "incubator" directory in the download. The Incubator in the Zend Framework is where new components are placed pending completion, documentation, and at least one public release. In 0.20, the incubator holds the new MVC components we will use. Copy ./incubator/library from the download to out own ./library/incubator.
The ./application directory is where the majority of our application specific classes such as controllers and models are stored. A controller is a class which contains the core logic of our application. This includes the top level code which utilizes libraries, manipulates the Model, sets up Views, and performs user data filtering/validation. This logic is separated into Action methods which can be accessed by formatting the url to the application to include the controller name and Action method name.
If the terminology is confusing, don't worry. We'll see this in action very soon.
The library directory will store all libraries we require such as the Zend Framework (both core and incubator). The use of other PHP libraries is not required for this tutorial but keep this location in mind for other projects you may build with the Zend Framework which require additional PHP libraries.
The public directory contains all files which must be accessible from the web. This includes images, css and javascript. In addition, for the sake of having a separate location, I've added a root javascript directory to hold javascript we create ourselves. It's essential to note that both the public and javascript directories should be the only sub-directories in our application accessible from the web. All other sub-directories contain files which a user does not require access to (i.e. we don't want them to!). So don't give them that access - it's poor security practice to do so. We can manage this access with .htaccess files under Apache.
Finally, we'll be using an XML based storage system to hold chat messages. The XML file will be written to the ./data directory. In order to ensure Apache (and by extension the PHP process) can write to this directory, you will need to ensure the directory permissions are changed as appropriate. The most restrictive permissions while still allowing PHP to write the XML file are advisable. However, for simplicity, you can chmod the directory to 777 on your offline development platform. Windows users can ignore all this permission talk ;).
Getting Started with the Zend Framework
The Zend Framework operates on a principle which maps a url string to a class called the Controller. As you can guess, Controller classes are stored in the ./application/controllers directory. A Controller class contains methods which perform actions, and are for this reason predictably called Actions. An example url such as http://www.example.com/chat/refresh would map to the RefreshAction() method on the ChatController class as stored in "application/controllers" as a ChatController.php file. The important point to note is how the url format tells the Zend Framework which controller and which method on that controller should be called. The technical details of mapping are not required at this stage (it's covered in some detail in the official documentation and the linked to tutorial).
The Zend_Controller class which manages this process (the "RewriteRouter") makes use of clean urls. To support this, all requests must go through a central index.php file ("the Bootstrap"). The Bootstrap file basically sets up the Framework and contains other initialization code such as we might require. It also needs a .htaccess file which ensures that all requests pass through index.php. This idea of having all requests pass through a single entry point in an application is referred to as the Front Controller Design Pattern. A google search should offer all the information on this Pattern you might be interested in if you're curious.
First, the .htaccess file. Create this file in the root "chat-tutorial" directory.
RewriteEngine on
RewriteCond %{REQUEST_URI} !/public.*
RewriteCond %{REQUEST_URI} !/javascript.*
RewriteRule .* index.php
php_flag magic_quotes_gpc off
php_flag register_globals off
This file tells Apache to use its mod_rewrite module when a request targets this directory or any subdirectory. The RewriteRule tells the module that all requests must be mapped onto the index.php file (i.e. make this the single entry point of our application). The final two php_flag entries simply ensure magic_quotes and register_globals are disabled for PHP. This reduces potential security risks, but mainly it ensures we do not become dependent on these settings either by accident or by intention. These two controversial settings will finally be eradicated once PHP 6 is released. Web Security professionals will likely get drunk and party all night when that happens...
In addition we've added two conditional rules. Both inform mod_rewrite that requests to the ./public or ./javascript directories should not be rewritten. The files in these two directories must be publicly accessible and not treated as requests to the application itself.
Our index.php Bootstrap basically sets the stage for our application. It's job is to handle the startup phase of the chat application such as adding initial settings, loading classes, and initializing the Zend Framework classes we intend using.
php
<?php
/**
* Bootstrap file for Chat Tutorial
*/
/*
* The basics...
*/
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', 1); //disable on production servers!
date_default_timezone_set('Europe/London');
/*
* Start our session
*/
session_start();
/*
* Setup the include_path to the ZF library.
* We set the incubator first so the
* incubator classes are loaded in preference
* to core ZF classes where two versions exist.
*
* When 0.21 is released, the MVC classes in
* Incubator will move to the core library.
*/
set_include_path(
'./library/incubator/library'
. PATH_SEPARATOR . './library'
);
include 'Zend.php';
/*
* Use Zend::loadClass() to load essentials
* Probably a good idea to use require_once()
* elsewhere to avoid unnecessary coupling.
*/
Zend::loadClass('Zend_Registry');
Zend::loadClass('Zend_Controller_Front');
Zend::loadClass('Zend_Controller_RewriteRouter');
Zend::loadClass('Zend_View');
/*
* Load and register our View for later use
*/
$view = new Zend_View();
$view->setScriptPath('./application/views');
Zend_Registry::getInstance()->set('view', $view);
/*
* Instantiate a Request to set BaseURL
* See later...
*/
$request = new Zend_Controller_Request_Http();
/*
* Instantiate a RewriteRouter
*/
$router = new Zend_Controller_RewriteRouter();
/*
* On my platform, I need to set the BaseURL for ZF 0.20
* RewriteBase is assumed to be $_SERVER['PHP_SELF'] after
* removing the trailing "index.php" string.
*
* PHP_SELF can be user manipulated. Avoided using SCRIPT_NAME
* or SCRIPT_FILENAME because they may differ depending on SAPI
* being used.
*/
$base_url = substr($_SERVER['PHP_SELF'], 0, -9);
$request->setBaseUrl($base_url);
/*
* Setup and run the Front Controller
*
* Set Controller Dir, add the RewriteRouter, dispatch the
* modified Request (with updated BaseURL) and finally
* get the resulting Response object.
*/
$controller = new Zend_Controller_Front;
$controller->setControllerDirectory('./application/controllers');
$controller->setRouter($router);
$response = $controller->dispatch($request);
/*
* By default Exceptions are not displayed
* That won't do during development.
* Remove this in a live environment though!
*
* $response->renderExceptions(true) will not
* work, it's broken and was fixed in SVN for
* next release. Until then...
*/
if($response->isException())
{
echo $response->getException();
exit; // Stop here - ;)
}
/*
* Echo the response (with headers) to client
* Zend_Controller_Response_Http implements
* __toString().
*/
echo $response;
A lot of the Bootstrap file is quite simple to follow.
We enabled display_errors so we can view any errors or uncaught exceptions. Error reporting has been set to E_ALL|E_STRICT since we're not interested in falling afoul of deprecated features or insecure practices (like uninitialized variables). A default Timezone has been set since PHP5.1+ does not trust the server it runs on to provide it correctly (there's no trust anymore...sigh). We started a PHP Session to store session specific data for users between requests. We also setup our include path to include the ./library directory. Since we're not using a database I've omitted a ./application/models directory which would normally be added to the include path also.
The next section is where the Zend Framework itself is bootstrapped. We include the base Zend class and use it to load several required classes. Zend::loadClass() is a static method which replaces the underscores in a class name with directory separators to find the path to the relevant file to include (this is commonly known as the PEAR convention). It then includes these files. It's a handy method to have available.
We also setup Zend_View and set the path to where our Templates (or Views) are located, i.e. application/views. A View may sound exotic but it's just a simple HTML template which may contain PHP code to control the insertion of variable data. If you've ever used Smarty, Template-Lite, Savant or similar it's the same idea.
The last section sets up the controller, adds a RewriteRouter class, and sets a base url on a new Request class. The final controller sequence call will match the incoming request's url to a relevant Controller class and Action method (using the default Zend_Controller_RewriteRouter rule ":controller/:action/*") to be called. It's possible to define alternate routing schemes which add parameters, their defaults, and any requirements. We don't need a specific RewriteRouter here however, but see RESTful Web Services With Zend Framework for examples.
With 0.20, I've found it often becomes necessary to tell the Framework about our location above the webroot. The setBaseUrl() method should be extremely portable and simply figures out where the application is located to ensure the Framework parses any request uri's correctly.
The rest is handling the reponse which of course we simply need to echo.
The Index Controller
Since the chat application is quite simple, we'll only require a single controller. To validate our Zend Framework setup we'll add a very basic one which will simply echo "Hello World!". The IndexController class should be saved as IndexController.php within our application/controllers directory.
<?php
class IndexController extends Zend_Controller_Action
{
public function IndexAction()
{
/*
* echo() would work also, but let's get used
* to the idea of a Response object.
* This is plain text, so we'll set the content type
* also.
*/
$this->getResponse()->setHeader('Content-Type', 'text/plain');
$this->getResponse()->setBody('Hello World');
}
}
Navigating to http://www.example.com/chat-tutorial/ should result in "Hello World!" being echoed to our browser. If you can view the response headers (look up the Web Developer extension for Firefox), you'll note the Content-Type was set to text/plain. This is the complex way, a simple echo would have worked just as well - but it's good to get into the habit of using a Response object as standard.
Finally Done
Thus ends our section on setting up the Zend Framework! We'll delve into other Framework classes later in this tutorial but since the ZF is not our only focus in this tutorial, I'll refer you (once more for good measure :)) to Rob Allen's tutorial mentioned earlier for a much more in-depth introduction.
Next up we'll examine setting up the javascript libraries we'll be using when creating our chat application.
The Zend Framework is a community based project. If you are interested in learning more or contributing to it, visit this page.

Comments
First hurdle... (bear in mind i am totally new to this...).. where do i need to save the index.php? And then where should i navigate to in order to view the results?
I know these are baby questions, but I am a fast, logical learner, so once i am given a good kick start i usually can work things out for myself! :D
although i would expect to hear from me again soon!
Thanks
in advance
If you placed the 'chat-tutorial' directory in your webservers document root, you would navigate to:
http://<yourServerName>/chat-tutorial/
to run your script. For example, I use my own PC as a testing server, so I navigate to:
http://localhost/chat-tutorial/
1. Extend the directory structure to support another folder:
./application/views/scripts
You have to create for each controller a seperate diretory there named the controllers name in lower case, i.e. scripts/index for the IndexController.
2. Simply use __autoload magic method and you don't have to include each class file seperately. Look at the bootstrap script below.
3. Add a new needed conroller ErrorController and its view script views/scripts/error/error.phtml. (You have to use the extension .phtml for every view script.
4. Make use of the following bootstrap script:
<?php
/**
* Bootstrap file for Chat Tutorial
*/
/*
* The basics...
*/
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', 1); //disable on production servers!
date_default_timezone_set('Europe/Berlin');
/*
* Setup the include_path to the ZF library.
* We set the incubator first so the
* incubator classes are loaded in preference
* to core ZF classes where two versions exist.
*
* When 0.21 is released, the MVC classes in
* Incubator will move to the core library.
*/
set_include_path( './library/incubator/library' .
PATH_SEPARATOR . './library' .
PATH_SEPARATOR . './application/controllers'
);
require_once('Zend/Loader.php');
function __autoload($class)
{
Zend_Loader::loadClass($class);
}
/*
* Start our session
*/
$session = new Zend_Session_Namespace('chat');
/*
* Load and register our View for later use
*/
$view = new Zend_View();
$view->setScriptPath('./application/views');
Zend_Registry::getInstance()->set('view', $view);
/*
* Setup and run the Front Controller
*
* Set Controller Dir, add the RewriteRouter, dispatch the
* modified Request (with updated BaseURL) and finally
* get the resulting Response object.
*/
$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('./application/controllers');
$controller->setDefaultControllerName('index');
$router = $controller->getRouter();
$controller->setRouter($router);
try {
$controller->dispatch();
}
catch (Exception $e){
echo $e->getMessage();
echo "<pre>";
echo $e->getTraceAsString();
echo "</pre>";
}
?>