How to make a blazing fast AJAX call to a Zend Framework application

Posted by Dominik Marczuk on November 13th, 2012, with 5 comments.

Category: Tutorials
Programming language: PHP, JavaScript
Tags: Zend Framework, AJAX, jQuery

Difficulty: Advanced
Completion time: half an hour

Nowadays, a common practice is loading only the necessary content and making an extra request using JavaScript in order to load additional content as soon as it's requested. For instance, a link to a user's profile, when hovered over, might make an AJAX request to load some basic user info (avatar, bio, etc.). This content is not loaded at the time the entire page is generated in order not to strain the server. If the user never chooses to view the additional profile information by hovering over the profile link, the extra content is not loaded at all.

Another example might be a comment form, loaded using AJAX in order to prevent spam submissions, as described in my earlier article.

The catch

If you are using Zend Framework (or any other "big" framework, for that matter), you might face a slight problem when making an additional AJAX call. Let's say you have a route defined for your ajax calls:

<ajax>
    <type>Zend_Controller_Router_Route</type>
    <route>ajax/:action/*</route>
    <defaults>
        <module>default</module>
        <controller>ajax</controller>
    </defaults>
</ajax>

With such a route you know that as soon as you request /ajax/fivestar, Zend Framework will run the ajax router and determine that the resource you wish to run is the default module, ajax controller, fivestar action.

This is nice, easy and comfortable. There's a catch though: it's also crawling slow!

Note that Zend Framework has little mercy on the server. It eats up quite a lot of memory and CPU ticks. Bear in mind that a Zend Framework application will instantiate a front controller, as many routers as needed, plugins and other resources, then it will take care of the entire process of routing and dispatching, instantiate a controller, launch its action, use the view and perhaps even post process the response before outputting it. Not to mention all the database queries that might be executed without a real need. This is wasteful when all we need to do is fetch some minimal amount of information.

As a rather extreme example, I can recall an issue with image galleries on a community portal that I sometime have to dabble with. There, the users may create image galleries with detailed privacy settings for each picture. Displaying a picture requires quite some application logic to be launched and executes a few queries to the database. We have decided it would be best to make a separate request and output the images via PHP headers if they can be displayed or fetch a placeholder image in the opposite case. The portal is quite a large application and before any output is generated, lots of code is launched and many database queries are executed. When the server suffers from increased traffic, a single request takes up to a few seconds. And since each image in a gallery required the entire Zend application to be executed, the entire page needed an absurd amount of time to fully load.

The solution

The solution is as simple as always: don't run Zend Framework. But how does one do that? After all, some parametres might have been passed in the URI, database access might be needed, etc. While these can be done manually, wouldn't it be simpler to just reuse code already available in Zend Framework?

Zend Framework, unlike e.g. CodeIgniter, is not a full stack framework. Its components enjoy a relative independence from each other and some may be used standalone. In order to achieve our efficient AJAX call, we will need access to two such components without letting the entire Zend application to run. These are the request and the database communication layer.

Remember the .htaccess file Zend Framework generates? It passes all requests through index.php - unless the requested file is physically present in the filesystem. We can start by creating a file that we'lll be able to execute. We'll call it ajax.php and we'll place it in the public folder, right beside index.php. Its contents will be similar to what index.php holds. First, let's create our basic constants and, if needed, tell PHP that our include path needs to point to the library directory. A disclaimer for those intimidated by the code that follows: don't worry about fully understanding it: it's copied almost verbatim from the default index.php file that's autogenerated by Zend Framework. We'll deal with the meat in a second. For now, let's have a look at the beginning of our ajax.php file:

defined('APPLICATION_PATH') || define('APPLICATION_PATH', realpath(PUBLIC_PATH . '/../application'));
defined('APPLICATION_ENV') || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
defined('APPLICATION_DOMAIN') || define('APPLICATION_DOMAIN', (getenv('APPLICATION_DOMAIN') ? getenv('APPLICATION_DOMAIN') : $_SERVER['SERVER_NAME']));
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH.'/../library'),
    get_include_path()
)));

Once this is done, we can instantiate Zend's autoloader:

require_once "Zend/Loader/Autoloader.php";
Zend_Loader_Autoloader::getInstance();

From now on, we can use Zend Framework elements without manually requiring their files. The above code is usually run internally by the framework, but here, since we bypass the application, we need to run it manually.

Now, let's create our request object. It's as simple as instantiating the HTTP request class:

$request = new Zend_Controller_Request_Http();

And finally, the database layer. Since the database connection details are defined in the config file (probably application.ini), it's good to retrieve them instead of writing them manually. So, let's read the application.ini and extract the connection details from it:

$config = new Zend_Config_Ini(
    APPLICATION_PATH."/configs/application.ini",
    APPLICATION_ENV
);
$db = Zend_Db::factory($config->resources->db->adapter, array(
    "host" => $config->resources->db->params->host,
    "username" => $config->resources->db->params->username,
    "password" => $config->resources->db->params->password,
    "dbname" => $config->resources->db->params->dbname
));

From now on, we should have everything that's necessary for completing the AJAX request.

Final code

A hypothetical contents of the file might therefore look like this:

// define constants
defined('APPLICATION_PATH') || define('APPLICATION_PATH', realpath(PUBLIC_PATH . '/../application'));
defined('APPLICATION_ENV') || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
defined('APPLICATION_DOMAIN') || define('APPLICATION_DOMAIN', (getenv('APPLICATION_DOMAIN') ? getenv('APPLICATION_DOMAIN') : $_SERVER['SERVER_NAME']));

// fix include path
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH.'/../library'),
    get_include_path()
)));

// use the autoloader
require_once "Zend/Loader/Autoloader.php";
Zend_Loader_Autoloader::getInstance();

// get the request object
$request = new Zend_Controller_Request_Http();

// get the database connection
$config = new Zend_Config_Ini(
    APPLICATION_PATH."/configs/application.ini",
    APPLICATION_ENV
);
$db = Zend_Db::factory($config->resources->db->adapter, array(
    "host" => $config->resources->db->params->host,
    "username" => $config->resources->db->params->username,
    "password" => $config->resources->db->params->password,
    "dbname" => $config->resources->db->params->dbname
));

// fetch and return user bio
$userId = intval($request->getQuery("user_id",0));

$user = $db->fetchRow(
    "SELECT * FROM user WHERE is = :id",
    array("id" => $userId)
);

die($user["bio"]);

This could be called using jQuery like this:

$(function() {
    $(".author-bio").on("hover", function() {
        $.ajax({
            data: "text",
            url: "/ajax.php",
            data: { user_id: $(this).attr("data-userid"); },
            success: function (data) {
                $(".author-bio-content").text(data);
            }
        });
    });
});

As you can see, the AJAX request is calling our ajax.php file directly, passing it a query parametre and retrieving plain text output. All of which is done while completely overriding Zend Framework's main application.

Comments

Rashmirathi (2013-05-22 10:10)

Dear Dominik

Thanks for pointing out the issue implementing AJAX in Zend the obvious way and also for posting this nice tutorial.
However with newer versions of the Zend in place, this post leaves a big blank to the implementation.
I tried implementing this with ZF2, however lost my way.

Do you think, it would be possible to upgrade this tutorial so we can really get the benefit out of it.

Thanks again!

Dominik Marczuk (2013-05-22 10:21)

I haven't had a chance to get a decent look at Zend Framework 2 yet, so a follow up article is not feasible for the time being. If you find your way around the issue in ZF2, it would be much appreciated if you shared your experience :).

Rashmirathi (2013-05-22 14:07)

Thanks for a quick reply.
I'm 5 days old with Zend, hence not sure if I could nail it alone.
However, I'm still trying and shall be glad to share if I could make it work.

Thanks again.

Post a comment