XML to JSON in PHP

Zimuel writes; Last friday, in occasion of the April Zend Framework Bug-Hunt, I started to look at this bug: ZF-3257. This is an issue related to the Zend_Json class that occurs during the conversion from XML to JSON for some specific XML documents, like this one:

$xml= 'bar';

The result using Zend_Json::fromXml($xml, false) , where false indicated the usage of XML attributes, was:

{"a":{"b":{"@attributes":{"id":"foo"}}}}

As you can see the bar value, of the a element, is not represented in JSON. This issue comes also with other XML documents, and in general when an XML node has a single character data child, any attributes are lost.

For instance, the following code:

$xml = 'bar';
echo Zend_Json::fromXml($xml, false);

Produced the output:

{"a":{"b":"bar"}}

in this case the attribute id and the value foo are lost.
Find a solution @ Zimuel’s blog.

App_Controller_Helper_Params for JSON and XML POSTs

Matthew Weier O’Phinney shares a bit of very useful code to inject request params into a Zend Framework request object from a JSON or XML POST request.

“Below is a plugin I use to translate JSON or XML raw post request data to request user parameters.
Note that it expects a “Content-Type” header of either “application/json” or “application/xml”. If those are detected, it then does the translation and injection.
Once it has, you can then simply access the parameters from your request object like any others.
I actually use this with dojox.data.JsonRestStore already, so you should be set. :)”

class App_Controller_Helper_Params extends Zend_Controller_Action_Helper_Abstract { 
/**
 * @var array Parameters detected in raw content body
 */
 protected $_bodyParams = array(); 
/**
 * Do detection of content type, and retrieve parameters from raw body if
 * present
 *
 * @return void
 */
 public function init() {
 $request = $this->getRequest();
 $contentType = $request->getHeader('Content-Type');
 $rawBody = $request->getRawBody();
 if (!$rawBody) {
   return;
 }
switch (true) {
 case (strstr($contentType, 'application/json')):
   $this->setBodyParams(Zend_Json::decode($rawBody));
   break;
 case (strstr($contentType, 'application/xml')):
   $config = new Zend_Config_Xml($rawBody);
   $this->setBodyParams($config->toArray());
   break;
 default:
   if ($request->isPut()) {
    parse_str($rawBody, $params);
   $this->setBodyParams($params);
 }
 break;
 }
}

/**
* Set body params
*
* @param array $params
* @return Scrummer_Controller_Action
*/
public function setBodyParams(array $params)
{
  $this->_bodyParams = $params;
  return $this;
}

/**
* Retrieve body parameters
*
* @return array
*/
public function getBodyParams()
{
  return $this->_bodyParams;
}

/**
* Get body parameter
*
* @param string $name
* @return mixed
*/
public function getBodyParam($name)
{
  if ($this->hasBodyParam($name)) {
    return $this->_bodyParams[$name];
  }
  return null;
}

/**
* Is the given body parameter set?
*
* @param string $name
* @return bool
*/
public function hasBodyParam($name)
{
  if (isset($this->_bodyParams[$name])) {
    return true;
  }
  return false;
}

/**
 * Do we have any body parameters?
 *
 * @return bool
 */
 public function hasBodyParams()
 {
   if (!empty($this->_bodyParams)) {
     return true;
   }
   return false;
 }
 
 /**
 * Get submit parameters
 *
 * @return array
 */
 public function getSubmitParams()
 {
   if ($this->hasBodyParams()) {
     return $this->getBodyParams();
    }
   return $this->getRequest()->getPost();
 } 

 public function direct()
 {
   return $this->getSubmitParams();
 }
}

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

Thijs Feryn writes an excellent article on how to convert your entire MVC app or one or more controllers into a XML service. I actually have this one in a production environment and it works like a charm.

That’s right folks, in this blog post I’ll show you how you can convert your entire MVC application into a REST-style XML webservice. And I’m not talking about refactoring tons of code … NO, we’ll plug this option in without changing a single thing to your action controllers.

This post will contain a detailed description of the concepts used. The source could of the plugin is also shown and finally how it will look like when using it.

The concept

Thanks to those lovely hooks in Zend Framework you can simply intervene in nearly every aspect of the MVC workflow. The image below shows you the workflow.

Zend Controller Basics

Zend Controller workflow The hooks I’m talking about are actually just methods that are called by the plugin broker system. They already exist as empty methods in the Zend_Controller_Plugin_Abstract which we inherit from. So we override them in order to hook into the MVC flow.

Alle information on Zend Framework plugins can be found in the reference pages. A lot of you Zend Framework experts will now say: “why don’t you just use the context switching action helper?”. I could have done that, but this requires modifying your application. This post is about plugging it in, remember?

How am I doing it then? Well … I hook into the workflow at “post dispatch” time, this means that de dispatcher has already processed al controllers and is about to send all about back to the browser by using a Zend Controller Response object.

Before the front controller sends the output back to the browser, we empty the output’s body and add our own content. This content is retrieved from the view object which would normally parse and render the view object. The view has a set of properties which are set by the controllers. I use the reflection API to get a hold of all these items. Finally I serialize them and output it all.

The plugin


<?php
/**
* My_Plugin_Xml component
* Turns an Zend Framework MVC website into an XML webservice
*/
/**
* My_Plugin_Xml class
*
* @author Thijs Feryn <[email protected]>
*/
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;

$responseNode = $this->_xml->createElement('response');

$exceptionNode = $this->_xml->createElement('exception');
if(null !== $exception && $exception == instanceof( Exception ){
$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();
}
/**
* Modify the HTTP response object
* Remove the HTML body, replace with XML and change the content-type
*
* @param mixed $return
* @param Exception $exception
*/
private function _setResponse($return = false,Exception $exception = null)
{
$this->getResponse()->setHeader('Content-Type','text/xml; charset=UTF-8');
$this->getResponse()->clearBody();
$this->getResponse()->setBody(
$this->_getXML($return,$exception)
);
}
/**
* Serialize a mixed value to XML in DOMElement format
* This method can be used recursively in case of objects and arrays
*
* @param string $name
* @param mixed $value
* @return DOMElement
*/
private function _serialize($name,$value)
{
if(is_array($value)){
$element = $this->_xml->createElement($name);
foreach ($value as $k=>$v){
if(is_numeric($k)){
$k = 'item';
}
$element->appendChild($this->_serialize($k,$v));
}
} elseif(is_object($value)){
$element = $this->_xml->createElement($name);
$reflection = new ReflectionObject($value);
$properties = $reflection->getProperties();
foreach ($properties as $property){
if($property->isPublic()){
$element->appendChild(
$this->_serialize(
$property->getName(),
$property->getValue($value)
)
);
}
}
}else{
$element = $this->_xml->createElement(
$name,
(string)$value
);
}
return $element;
}
/**
* preDispatch hook that retrieves if an Exception was thrown in the application
* If an exception is thrown, the exception is passed to the exception part of the XML output and script execution is terminated
*
* @param Zend_Controller_Request_Abstract $request
*/
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
if($this->getResponse()->isException()){
$exArray = $this->getResponse()->getException();
$this->_setResponse(null,$exArray[0]);
$this->getResponse()->sendResponse();
exit();
}
}
/**
* postDispatch hook that serializes the view object to XML by modifying the HTTP response
* If no exception was thrown script execution continues and the postDispatch method will be called
*
* @param Zend_Controller_Request_Abstract $request
*/
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
$view = Zend_Controller_Action_HelperBroker::getExistingHelper('ViewRenderer')->view;
$this->_setResponse($view);
}
}

The plugin registration

To activate the plugin, just call the registerPlugin method from the front controller. In my case this is done in my Initializer class which is allready a plugin.

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

The app

This application has one single controller which is the IndexController. There are 2 actions:

* IndexAction: the default action which assigns an object to the view
* ExceptionAction: an action which throws an exception

Pretty simple, pretty basic, but mind the exception: my plugin can catch it by hooking into the flow at preDispatch time. At that time we can already determine if the response contains an exception. Luckily, in this stage the front controller hasn’t yet sent additional error output 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 output

indexAction

<?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>

exceptionAction

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