AJAX Chat Tutorial is a tutorial that is
a step by step introduction to creating a lightweight chat application
using XML as a storage medium. You can download the current version of
the source code for this application to help you along.
AJAX Chat Tutorial : 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.
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
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.
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.
|