In a project I'm working on at the moment, I had the need to quickly get together some JSON webservices. I wanted to add these on to actions that I had already created, to output the data the page would have shown, thus making the pages that already exist an API structure. The MVC pattern of symfony makes this somewhat easier to do. Since I had a bunch of methods to create services for, I came up with this quick technique :
I created a quick method to do this without having to create extra views for each action. I added a method to the controller called 'returnApi', which looks like this :
/**
* returnApi - take data and do magic to return data as an API
*
* @return string
* @author Mark Ng
**/
private function returnApi($data, $apiType = 'json')
{
$this->logMessage('returnApi called');
// do some magic with the data here
$outputData = null;
if ($data instanceof BaseObject)
{
$outputData = $data->toArray();
}
elseif(is_array($data))
{
$outputData = array();
foreach ($data as $key => $object)
{
if ($object instanceof BaseObject)
{
$outputData[] = $object->toArray();
}
}
}
elseif($data instanceof sfPropelPager)
{
foreach ($data->getResults() as $key => $object)
{
if ($object instanceof BaseObject)
{
$outputData[] = $object->toArray();
}
}
}
else
{
throw new Exception('unable to translate data to API');
}
switch ($apiType)
{
case 'json':
// do json encoding and return here
header('Content-Type: application/json');
echo(json_encode($outputData));
return(sfView::NONE);
break;
case 'yaml':
// do yaml encoding and return here
header('Content-Type: text/yaml');
$yaml = new sfYaml();
echo($yaml->dump($outputData));
return(sfView::NONE);
break;
default:
// return a 404
$this->forward404();
break;
}
}
This will take :
- Any propel model object instance
- Any sfPropelPager instance
- Any array of propel model objects
Passed from a action to create a view in various formats. It's left as an exercise for the reader to create alternatives to json and yaml (XML, for example). For each action you wish to enable this, add this snippet to the bottom of the action (remembering to replace the $this->data) with wherever you have placed your data for the action :
if ($this->hasRequestParameter('api'))
{
return($this->returnApi($this->data, $this->getRequestParameter('api')));
}
As always, this is blogged as soon as I have written the code, so if you notice any bugs, please don't hesitate to contact me so I can correct them.
Once you have done this, all your actions are available as these api types by adding ?api=json or ?api=yaml to your URL strings. You can also, of course, use routes with prettier urls to do this, too.
One comment so far
What about opting to explicitly render the output rather than just dumping the object? What are the advantages and disadvantages to an explicit render and this render as? I like the Idea!