Tuesday, 6 March 2007

Gluing Ajax with the Zend Framework

As I moved forward with the implementation of the recordshelf application I wanted to use Ajax in interaction with the Zend Framework. It all started with a typical XMLHttpRequest to one of the defined methods of the customized Zend_Controller_Action objects, like in the following code snippet using the Prototype 1.5 libary.

var id = element.getAttribute('id');

new Ajax.Request('/recordshelf/foo' + id, {

method: 'post',

onSuccess: function(transport, json) {
//use and handle foo response data
},
on500: function(transport) {
//handle error, inform user
},
...
});
This all comes down to a XMLHttpRequest to the recordshelf controller and its hosted foo action by passing one parameter id and handling the response data or HTTP status error code of the called action.

In the requested action of the contoller the provided functionality of the model is used, which migth return a resultset or in the worst case throw an exception, which gets translated into a HTTP status error code of 500. The data or error can be responded to the requesting client in several formats like Json, Xml or just Raw Text. Therefor a Zend_Controller_Response_Http object is build in the request handling action containing all the infomation in the specified response format for the client.

As a image is more worth than thousand words here's an UML sequence diagramm showing the flow and involved components.

UML sequence diagram

The following code outlines how to build an Ajax response for the action foo of the Recordshelf_Controller using Json and Xml as response format. The used PHPDoc tag @ajaxaction is not mandatory, it's just used here to stress that foo action is handling an Ajax request and is used further for examination of the source file via the PHP Reflection API.

class Recordshelf_Controller extends Zend_Controller_Action {

...

/**
* @ajaxaction
*/
public function fooAction() {

$this->_helper->viewRenderer->setNoRender();

$model = $this->getModelInstance();

/* fetch id from request, value validation omitted */
$id = $this->getRequest()->getParam('id');

/* [Json response] */

$responseData = $model->getResponseDataForClient($id);

try {

$responseDataJsonEncoded = Zend_Json::encode($responseData);
$this->getResponse()->setHeader('Content-Type', 'application/json')
->setBody($responseDataJsonEncoded);

} catch(Zend_Json_Exception $e) {
// handle and generate HTTP error code response, see below
}

/* [Xml response] */

$responseDataXmlEncoded = $model->getResponseDataForClientAsXml($id);
$this->getResponse()->setHeader('Content-Type', 'text/xml')
->setBody($responseDataXmlEncoded);

/* [HTTP error code response] */

try {

$responseData = $model->getResponseDataFailing();
...

} catch(Exception $e) {

$this->getResponse()->setHttpResponseCode(500)
->setBody('Internal Server Error');
}
}

...

}
The following image shows the Json server response in the Firebug console.

Json response in Firebug

6 comments:

Ben said...

What happens if a user points their browser to http://your.site.com/Recordshelf_Controller/fooAction ?

I am experimenting with ZF and AJAX and I do not know how to work around this?

Raphael Stolt said...

Hello Ben,

The fooAction of the Recordshelf_Controller is executed by your example request. As the fooAction polls the data for the response from the model by a provided id you might add a guard clause to check for the availability of the id as a parameter in the request object.

If you want to allow only Ajax requests to the action you have to add an additional guard clause checking if the request is a Javascript XMLHttpRequest.

If the 'id is present' guard clause fails you can set a HTTP error code for the Ajax request response. In case the guard clause for the XMLHttpRequest fails you can make a redirect to an action responsible for error handling/displaying.

Ben Bristow said...

This is an excellent blueprint for using AJAX with the Zend framework. The UML is snazzy. Thanks.

Anonymous said...

good tutorial, it helps me very much. thanks

Anonymous said...

You missed out on the two action helpers built in ZF for this task, the context switch, and ajax context.

Raphael Stolt said...

They weren't available three years ago, but welcome to the party ;P