Showing posts with label Ajax. Show all posts
Showing posts with label Ajax. Show all posts

Saturday, 31 March 2007

Using the Zend Framework plugin system for server-sided Ajax request identification

In an earlier article "Gluing Ajax with the Zend Framework" I'm using a doclet called @ajaxaction to mark actions that are handling Ajax requests.

If using the Prototype, jQuery or mootools libary, these requests differ from pure HTTP request by having a currently non-standard "X-Requested-With = XMLHttpRequest" header and their response mostly doesn't get rendered via a view. Unless you might use AXAH(Asychronous XHTML and HTTP) where the server might respond with snippets of XHTML to insert into the targeted DOM area.

The build filter should determine if the requested action is called in an Ajax context, by checking the request for the above-mentioned header and auditing if the action requested is marked as an Ajax action via a PHP doclet tag. If both off these contracts are met the request should be handled or otherwise be rejected for further controller dispatching.

Other usefull plugin scenarios would be verifying authentication, verifying the use of a valid session, tracking usefull client data and evaluating ACLs before any action will be dispatched by the controller.

If you are not using one of the mentioned javascript libaries you can add the Ajax identifing header by using the setRequestHeader method of the 'native' XMLHttpRequest object or a similar method of your preferred libary to enable the server-side identification of Ajax.

The following code fragment shows the custom plugin located in the recordshelf/libary/Recordshelf/Request/Ajax directory. It checks pre to every dispatch if the defined contracts(isAjaxRequest and isActionDoclettedAsAjaxHandler) are met and acts adequate.

If there is an Ajax request and the action matches against a specified doclet, here it is @ajaxaction, the action is released for dispatching by the controller. Otherwise a Http Status Code of 501 is returned to the client and the rejected action is logged.

<?php

class Recordshelf_Request_Ajax_Detection extends Zend_Controller_Plugin_Abstract {

protected $_request = null;
private $_doclet = null;

public function __construct($doclet) {

if($doclet == '') {

throw new Zend_Controller_Exception('No doclet tag provided.');

}

$this->_doclet = $doclet;

}

public function preDispatch($request) {

$this->_request = $request;

if($this->isAjaxRequest()) {

if(!$this->isActionDoclettedAsAjaxHandler($this->_doclet)) {

$action = $this->_request->getActionName().'Action';
$controller = ucfirst($this->_request->getControllerName()).'Controller';

Zend_Log::log("{$controller}->{$action} is not ajax docletted. Rejecting call.",
Zend_Log::LEVEL_ERROR);

$this->getResponse()->setHeader('Content-Type', 'text/txt')
->setHttpResponseCode(501)
->appendBody('Not Implemented')
->sendResponse();

exit(0);

}

}

}
/**
* Determines if request is in Ajax context.
* @return boolean
*/
private function isAjaxRequest() {

if($this->_request->getHeader('X-Requested-With') == '') {
return false;
}

return true;

}
/**
* Determines if requested action handles Ajax requests by checking for a provided custom doclet.
* @param string The custom doclet to validate against.
* @return boolean
*/
private function isActionDoclettedAsAjaxHandler($doclet) {

if($this->_request->getControllerName() != '') {

$controller = ucfirst($this->_request->getControllerName()).'Controller';

} else {

return false;

}

$action = $this->_request->getActionName().'Action';
$method = new ReflectionMethod($controller, $action);

$illuminator = new Recordshelf_Server_Reflection_Doclet($method->getDocComment());
$isAjaxDocletAvailable = $illuminator->hasDoclet($doclet);

if($isAjaxDocletAvailable) {

return true;

} else {

return false;

}

}

}
The plugin makes use of a custom Recordshelf_Server_Doclet class build on top of the PHP Reflection API to analyse the PHP doclets of the requested action method(s). Sadly the Zend_Server_Reflection doesn't currently support an full access to the doclets of a method, as Zend_Server_Reflection_Method only allows access to the textual part of the PHP doclet area, means everything without a preceding @. I'd like to see this IMHO basic feature added to the Zend_Server_Reflection component in future to avoid a fall back on the PHP Reflection API and to keep the use of reflection/introspection within the Zend Framework.

After the plugin has been installed, it has to be registered in the bootstrap file by chaining the registerPlugin method "fluently" on the Zend_Controller_Front instance. The plugin takes the doclet to identify/tag server-sided Ajax actions as an argument.
<?php
...
$controller->setControllerDirectory('/path/to/controllers')
->setRouter(new Zend_Controller_Router())
->registerPlugin(new Recordshelf_Request_Ajax_Detection('ajaxaction'));
...
?>
The custom Recordshelf_Server_Doclet class illuminates the PHPDoc comment of the requested action method, allows validation against any available doclet and provides access to all doclets found.
<?php

class Recordshelf_Server_Reflection_Doclet {

private $_doclets = null;
private $_comment = null;

public function __construct($comment) {

$this->_comment = $comment;
$this->_illuminate();

}
/**
* Worker method for illuminating the PHPDoc comment.
* @return null On empty PHPDoc comment and non-available doclets.
*/
private function _illuminate() {

if($this->_comment == '') {

$this->_doclets = array();
return null;

}

if(!$this->containsDoclets()) {

$this->_doclets = array();
return null;

}

$comment = trim(str_replace(array('/**', '*/', '*', '{@link'), '', $this->_comment));
$comment = trim(substr($comment, stripos($comment, '@'), strlen($comment)));

$doclets = explode('@', $comment);

array_shift($doclets);

foreach($doclets as $index => $doclet) {

$doclet = trim($doclet);

if(stripos($doclet, '(') && stripos($doclet, ')')) {

$value = substr($doclet, stripos($doclet, '('), stripos($doclet, ')'));
$value = str_replace(array('(',')', ' '),'', $value);
$values = explode(',', $value);

$tmp = null;

foreach($values as $index => $value) {

if(stripos($value, '=')) {

$docletValues = explode('=', $value);

$tmp[] = array('key' => trim($docletValues[0]), 'value' => trim($docletValues[1]));

} else {

$tmp[] = array('key' => trim($value));

}

}
$doclet = substr($doclet, 0, stripos($doclet, '('));
$this->_doclets[] = array(trim($doclet), $tmp);

} else {

$this->_doclets[] = trim($doclet);

}

}

}
/**
* Checks if provided PHPDoc comment contains any doclets.
* @return boolean
*/
private function containsDoclets()

if(stripos($this->_comment, '@')) {

return true;

}

return false;

}
/**
* Acessor for all found doclets.
* @return mixed An array containing all doclets.
*/
public function getDoclets() {

return $this->_doclets;

}
/**
* Checks if the provided specific doclet is available.
* @param string The name of the doclet.
* @return boolean
*/
public function hasDoclet($name) {

if($name == '') {

return false;

}

if(in_array($name, $this->_doclets)) {

return true;

} else {

return false;

}

}
}
The above stated Recordshelf_Server_Reflection_Doclet class already provides a basic skeleton and some features for the use of Annotations in the Zend Framework, like in the Stubbles framework, but is very far from beeing complete.

Saturday, 17 March 2007

Bulletproof Ajax book review

Yesterday the new book, the sequel to DOM Scripting, of Jeremy Keith finally arrived via amazon. As I'm almost through I can only state that Jeremy created another sure shot book with the focus on frontend-sided Ajax. I even learned a new thing or two. I can recommend this book to anybody who's new to Ajax and need's a good and libary-free introduction to the concepts behind it. If you are already versed in building ajaxied applications I can recommend it as a good and not very time consuming refreshment as it is fun and very fluent to read. In case you are looking for a book with a main focus on server-sided Ajax this book might not the one to choose.

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