Warning: pretty advanced Symfony ahead: if you’re not familiar with the framework, this wouldn’t make sense.
I recently developed a bare-bones API for Mobshare (it’s not yet live), and to keep everything clean, I abstracted away the rendering bit from the controller to an external class. It ended up being a sweet solution, so here it is!
I wanted this to be a JSONP API (the major use case would be a JS client, and parsing XML etc. via JS is a pain. Besides JSON is much shorter over the wire). I didn’t want to rewrite a lot of code: checking for a callback parameter and wrapping the returned string around the JSON output was just begging to be refactored away. So here it is, a generic Symfony JSON API wrapper class:
<?php
class JSONPAPI {
const CALLBACK_PARAMETER = 'callback';
var $status;
var $data;
var $callback;
public function __construct($status, $data) {
$this->status = $status;
$this->data = $data;
$callback_parameter_value = $this->getCurrentAction()->getRequestParameter(self::CALLBACK_PARAMETER);
if($callback_parameter_value)
$this->callback = $callback_parameter_value;
}
public function render() {
$render_text = json_encode(array($this->status => $this->data));
if($this->callback)
$render_text = $this->callback . '(' . $render_text . ');';
$this->setJavascriptHeaders();
return $this->getCurrentAction()->renderText($render_text);
}
//hack to get the current action
private function getCurrentAction() {
return sfContext::getInstance()->getActionStack()->getLastEntry()->getActionInstance();
}
private function setJavascriptHeaders() {
sfContext::getInstance()->getResponse()->setParameter('Content-Type', 'application/javascript', 'symfony/response/http/headers');
}
}
The bits of magic here are the getCurrentAction()
function and the render()
call. It works on one very simple idea: everything in Symfony can be accessed from the sfContext::getInstance()
object, you just need to dig deep enough.
Once you write this boiler-plate code, using it is very elegant. First, subclass it for your API:
<?php
require_once('JSONPAPI.class.php');
class MobshareAPI extends JSONPAPI {
}
And then, use it like so within your controller:
<?php
class userActions extends sfActions
{
public function executeAuthenticate() {
$valid_user = UserPeer::authenticate($this->getRequestParameter('alias'),
$this->getRequestParameter('mobile_no'), $this->getRequestParameter('password'));
if($valid_user instanceOf User) {
$success = new MobshareAPI('success', $valid_user->toArray());
return $success->render();
} else {
$error = new MobshareAPI('error', 'Authentication failed: Alias, mobile number or password invalid.');
return $error->render();
}
}
}
Note: the rendering has been delegated to the $success
and $error
MobshareAPI objects. This allows for a really maintainable API. Adding functionality is much simpler since you don’t have to worry about the boilerplate.
You end up calling the API like this:
http://api.mobshare/user/authenticate?alias=vish&password=xxx&callback=handler
and you get back data which looks like this:
handler({"success":{"alias":"vish","name":"Vishnu Gopal","photo_mini": ...);
Note that callback handling is done entirely by the API and the controller needn’t worry about this parameter at all!
Leave a Reply