Showing posts with label Metatags. Show all posts
Showing posts with label Metatags. 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.

Wednesday, 31 January 2007

Using the PHP 5 reflection API to keep track of unsolved refactorings

When I'm developing software with php I try to keep myself to the TDD flow of Red, Green and Refactor.

But sometimes the refactoring isn't obvious at that current time or there is to much stress to complete this task right away so I leave a meta tag in PHPDoc syntax to mark the method I have to get back on later. These tags are defined in an own coding convention so they are unknown for tools like PHPDocumentor. So I played around with the very suitable reflection API to write an unsolved refactoring processor.

The used coding or meta tag conventions define several tags like unsolvedRefactoring and needsCodeReview for methods who need an other pair of eyes. The code review meta tag might be used by an prosessor to email the person which should join the code review. This mail might contain a simple note with an filepath to the class or even the whole method body and so on.

The following class uses these conventions to mark the smelling or non-reviewed methods.

class ExampleWithUnsolvedRefactorings {

/**
* Puuh. That's an empty method and a strong smelling method.
* @param integer The smelling scale.
* @unsolvedRefactoring Your hints to remove the smell.
*/
public function smellingMethod($in) {
// omitted method body
}
/**
* This method is smelling too.
* @unsolvedRefactoring The thoughts to remove the smell.
*/
public function anOtherOdourfullMethod() {
// omitted method body
}
/**
* This methods needs a code review.
* @needsCodeReview reviewerOfYourTrust@example.com
*/
public function seekFeedbackMethod() {
// omitted method body
}
}
As mentioned above these tags can't be processed with tools like PHPDocumentor so I had to come up with an own class processor or a refactoring tracker.

There for I used the reflection API of PHP 5 to extract the metatags from the target class. It provides several classes like ReflectionClass, ReflectionMethod with lots of handy methods to inspect a class and it's contained structure.

So crafted this class to get the defined methods of a the test class and extract the ones with unsolved refactorings. At this point this solution isn't able to extract the hints stated after the metatags which might contain thoughts about the steps of this refactoring. The review tags are not handled by this example.
class ClassFileNotReadableException extends Exception {

}
class UnsolvedRefactoringsProcessor {

private $_refactoringTag = NULL;
private $_classToInspect = NULL;
private $_methodsToRefactor = NULL;

public function __construct($classToInspect, $refactoringTag = 'unsolvedRefactoring') {
$this->_classToInspect = $classToInspect;
$this->_refactoringTag = $refactoringTag;
$this->_methodsToRefactor = array();
}

private function setClassToInspect($classToInspect) {
$this->_classToInspect = $classToInspect;
}

private function isClassFileReadable() {
if(file_exists($this->_classToInspect.'.php')) {
return true;
}
return false;
}

private function inspectClass() {
if(!$this->isClassFileReadable()) {
throw new ClassFileNotReadableException($this->_classToInspect . ' is not readable.');
}
include($this->_classToInspect.'.php');
$this->getUnsolvedRefactorings(new ReflectionClass($this->_classToInspect));
}

private function getUnsolvedRefactorings($class) {
foreach($class->getMethods() as $method) {
if($this->isRefactoringTagPresent($method->getDocComment())) {
$this->_methodsToRefactor[]['method'] = $method->getName();
$this->_methodsToRefactor[count($this->_methodsToRefactor) - 1]['startLine'] =
$method->getStartLine();
$this->_methodsToRefactor[count($this->_methodsToRefactor) - 1]['endLine'] =
$method->getEndLine();
}
}
}

private function isRefactoringTagPresent($docComment) {
$pattern = '/\s+@'.$this->_refactoringTag.'\b/';
if(preg_match($pattern, $docComment, $matches) > 0) {
return true;
}
return false;
}

public function getMethodsWithUnsolvedRefactorings() {
try {
$this->inspectClass();
} catch (ClassFileNotReadableException $e) {
return $e->getMessage();
}
if($this->classContainsUnsolvedRefactorings()) {
return $this->_methodsToRefactor;
}
return $this->_classToInspect . ' contains no open refactoring tasks.';
}

public function classContainsUnsolvedRefactorings() {
if(count($this->_methodsToRefactor) > 0) {
return true;
}
return false;
}

public function getInspectedClassName() {
return $this->_classToInspect;
}

}//end of class
The methods inspectClass and getUnsolvedRefactorings are using the reflection API to extract the methods of an class including their doc comments, start and end line. The doc comments are inspected against the defined meta tag and all methods with an unsolved refactoring are collected in an array which is used for further processing. My current solution simply prints all found unsolved refactoring to the screen. But it can easily be used to write it to an log or even better to automate the processing and logging of custom defined tags via an own Phing task. I guess that's a good exercise for my next phing exploration.

So here is my simple test driver for the above class which inspects the ExampleWithUnsolvedRefactorings class for unsolved refactorings.
include('UnsolvedRefactoringsProcessor.php');

$processor = new UnsolvedRefactoringsProcessor('ExampleWithUnsolvedRefactorings');

echo "Unsolved Refactorings for {$processor->getInspectedClassName()}:";

$openRefactorings = $processor->getMethodsWithUnsolvedRefactorings();

if(is_array($openRefactorings)) {
foreach($openRefactorings as $aRefactoring) {
echo "\n+ {$aRefactoring['method']} ";
echo "Line: {$aRefactoring['startLine']} - {$aRefactoring['endLine']}";
}
} else {
echo "\n" . $openRefactorings;
}
The result for the unsolved refactoring in the ExampleWithUnsolvedRefactorings class looks like this:
Unsolved Refactorings for ExampleWithUnsolvedRefactorings:
+ smellingMethod Line: 10 - 12
+ anOtherOdourfullMethod Line: 17 - 19
The solution I crafted in the above code snippets is far from beeing productive code but I quess you got the point and for me there is much potential for further explorations. I will pick up this topic again on my next session about Phing the PHP build tool. In work life a sophisticated solution might help you to keep track of smelly areas in your codebase and might support parts of a workflow or step in an process like code reviews.