Converting your Zend Framework MVC application into an XML webservice using one single plugin

Thijs Feryn writes an excel­lent arti­cle on how to con­vert your entire MVC app or one or more con­trollers into a XML ser­vice. I actu­ally have this one in a pro­duc­tion envi­ron­ment and it works like a charm.

That’s right folks, in this blog post I’ll show you how you can con­vert your entire MVC appli­ca­tion into a REST-style XML web­ser­vice. And I’m not talk­ing about refac­tor­ing tons of code … NO, we’ll plug this option in with­out chang­ing a sin­gle thing to your action controllers.

This post will con­tain a detailed descrip­tion of the con­cepts used. The source could of the plu­gin is also shown and finally how it will look like when using it.

The con­cept

Thanks to those lovely hooks in Zend Frame­work you can sim­ply inter­vene in nearly every aspect of the MVC work­flow. The image below shows you the workflow.

Zend Controller Basics

Zend Con­troller work­flow The hooks I’m talk­ing about are actu­ally just meth­ods that are called by the plu­gin bro­ker sys­tem. They already exist as empty meth­ods in the Zend_Controller_Plugin_Abstract which we inherit from. So we over­ride them in order to hook into the MVC flow.

Alle infor­ma­tion on Zend Frame­work plu­g­ins can be found in the ref­er­ence pages. A lot of you Zend Frame­work experts will now say: “why don’t you just use the con­text switch­ing action helper?”. I could have done that, but this requires mod­i­fy­ing your appli­ca­tion. This post is about plug­ging it in, remember?

How am I doing it then? Well … I hook into the work­flow at “post dis­patch” time, this means that de dis­patcher has already processed al con­trollers and is about to send all about back to the browser by using a Zend Con­troller Response object.

Before the front con­troller sends the out­put back to the browser, we empty the output’s body and add our own con­tent. This con­tent is retrieved from the view object which would nor­mally parse and ren­der the view object. The view has a set of prop­er­ties which are set by the con­trollers. I use the reflec­tion API to get a hold of all these items. Finally I seri­al­ize them and out­put it all.

The plu­gin


<?php
/**
* My_Plugin_Xml component
* Turns an Zend Framework MVC website into an XML webservice
*/
/**
* My_Plugin_Xml class
*
* @author Thijs Feryn <thijs@feryn.eu>
*/
class My_Plugin_Xml extends Zend_Controller_Plugin_Abstract
{
/**
* Stores the front controller
*
* @var Zend_Controller_Front
*/
private $_front;
/**
* Stores the XML output in DOMDocument format
*
* @var DOMDocument
*/
private $_xml;
/**
* Class constructor
*/
public function __construct()
{
$this->_front = Zend_Controller_Front::getInstance();
$layout = Zend_Layout::getMvcInstance();
$layout->disableLayout();
}
/**
* Build DOMDocument to convert output to XML
*
* @param mixed $return
* @param Exception $exception
* @return string
*/
private function _getXML($return = null,Exception $exception = null)
{
$this->_xml = new DOMDocument('1.0', 'UTF-8');
$this->_xml->formatOutput = true;

$respon­seN­ode = $this->_xml->createElement(‘response’);

$excep­tionN­ode = $this->_xml->createElement(‘exception’);
if(null !== $excep­tion && $excep­tion == instanceof( Excep­tion ){
$exceptionNode->appendChild(
$this->_xml->createElement(‘message’,
$exception->getMessage()
)
);
$exceptionNode->appendChild(
$this->_xml->createElement(‘code’,
$exception->getCode()
)
);
$exceptionNode->appendChild(
$this->_xml->createElement(‘type’,
get_class($exception)
)
);
}

$responseNode->appendChild($exceptionNode);
if(null !== $return){
$responseNode->appendChild(
$this->_serialize(‘return’,$return)
);
} else {
$responseNode->appendChild(
$this->_xml->createElement(‘return’)
);
}

$this->_xml->appendChild($responseNode);
return $this->_xml->saveXML();
}
/**
* Mod­ify the HTTP response object
* Remove the HTML body, replace with XML and change the content-type
*
* @param mixed $return
* @param Excep­tion $excep­tion
*/
pri­vate func­tion _setResponse($return = false,Exception $excep­tion = null)
{
$this->getResponse()->setHeader(‘Content-Type’,‘text/xml; charset=UTF-8′);
$this->getResponse()->clearBody();
$this->getResponse()->setBody(
$this->_getXML($return,$exception)
);
}
/**
* Seri­al­ize a mixed value to XML in DOMEle­ment for­mat
* This method can be used recur­sively in case of objects and arrays
*
* @param string $name
* @param mixed $value
* @return DOMEle­ment
*/
pri­vate func­tion _serialize($name,$value)
{
if(is_array($value)){
$ele­ment = $this->_xml->createElement($name);
fore­ach ($value as $k=>$v){
if(is_numeric($k)){
$k = ‘item’;
}
$element->appendChild($this->_serialize($k,$v));
}
} elseif(is_object($value)){
$ele­ment = $this->_xml->createElement($name);
$reflec­tion = new ReflectionObject($value);
$prop­er­ties = $reflection->getProperties();
fore­ach ($prop­er­ties as $prop­erty){
if($property->isPublic()){
$element->appendChild(
$this->_serialize(
$property->getName(),
$property->getValue($value)
)
);
}
}
}else{
$ele­ment = $this->_xml->createElement(
$name,
(string)$value
);
}
return $ele­ment;
}
/**
* pre­Dis­patch hook that retrieves if an Excep­tion was thrown in the appli­ca­tion
* If an excep­tion is thrown, the excep­tion is passed to the excep­tion part of the XML out­put and script exe­cu­tion is ter­mi­nated
*
* @param Zend_Controller_Request_Abstract $request
*/
pub­lic func­tion preDispatch(Zend_Controller_Request_Abstract $request)
{
if($this->getResponse()->isException()){
$exAr­ray = $this->getResponse()->getException();
$this->_setResponse(null,$exArray[0]);
$this->getResponse()->sendResponse();
exit();
}
}
/**
* post­Dis­patch hook that seri­al­izes the view object to XML by mod­i­fy­ing the HTTP response
* If no excep­tion was thrown script exe­cu­tion con­tin­ues and the post­Dis­patch method will be called
*
* @param Zend_Controller_Request_Abstract $request
*/
pub­lic func­tion postDispatch(Zend_Controller_Request_Abstract $request)
{
$view = Zend_Controller_Action_HelperBroker::getExistingHelper(‘ViewRenderer’)->view;
$this->_setResponse($view);
}
}

The plu­gin registration

To acti­vate the plu­gin, just call the reg­is­ter­Plu­gin method from the front con­troller. In my case this is done in my Ini­tial­izer class which is all­ready a plu­gin.

$this->_front->registerPlugin(new My_Plugin_Xml());

The app

This appli­ca­tion has one sin­gle con­troller which is the Index­Con­troller. There are 2 actions:

* Index­Ac­tion: the default action which assigns an object to the view
* Excep­tion­Ac­tion: an action which throws an exception

Pretty sim­ple, pretty basic, but mind the excep­tion: my plu­gin can catch it by hook­ing into the flow at pre­Dis­patch time. At that time we can already deter­mine if the response con­tains an excep­tion. Luck­ily, in this stage the front con­troller hasn’t yet sent addi­tional error out­put to the view.

<?php
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$obj = new stdClass();
$obj2 = new stdClass();
$obj2->c = 'xyz';
$obj2->d = '123';
$obj->a = 'abc';
$obj->b = $obj2;
$this->view->content= $obj;
}
public function exceptionAction()
{
throw new Exception('It all goes wrong!');
}
}

The out­put

index­Ac­tion

<?xml version="1.0" encoding="UTF-8"?>
<response>
<exception/>
<return>
<content>
<a>abc</a>
<b>
<c>xyz</c>
<d>123</d>
</b>
</content>
</return>
</response>

excep­tion­Ac­tion

<?xml version="1.0" encoding="UTF-8"?>
<response>
<exception>
<message>It all goes wrong!</message>
<code>0</code>
<type>Exception</type>
</exception>
<return/>
</response>

Tags: , , , ,

Leave a Comment

*

Get Adobe Flash playerPlugin by wpburn.com wordpress themes