PHP, JS & Service layers: Blend like never before
The past week I’ve been only programming (clientside) Javascript, and last night I finally got to tying it all to the serverside app, which is written in PHP. While adding some functionality to my Service Layer, it came to mind how much slower this process was in the past. Tweeted about that realization, and was asked to blog about it, so here I am.
As mentioned, I use service layers (stricly spoken that should probably be singular). All they do is proxy requests to my mappers, and in the mean time log the request and check if the user performing it is actually allowed to do so.
When adding a new feature to the clientside part of my app I used to add a function to my javascript that performs an xmlhttprequest to a custom url, parse the output, and then update one or more elements based on the result. Serverside, I would then add a route to the file where all urls are mapped to their relevant module/controller/view, created a controller or action that would parse the data as was received from the javascript, call the appropriate method in the relevant service class, parse the result from the service layer call, and send it back to the client. Yes, you’re right: That’s quite a tedious job indeed.
[more]
However, one of the nice things of Service Layers and performing your Access Control there, is that you can have multiple points of entry. So, to lessen the amount of tediousness, one can simply use one controller for all service requests coming from the client.
The codesample below is based on Zend Framework based code. Also, as you may notice, all my service layers implement the Singleton design pattern. That may be dirty, but since they’re stateless, I can’t really be bothered. The controller/action used:
getRequest();
$serviceName = $request->getParam(‘service’);
$method = $request->getParam(‘method’);
$params = $request->getParam(‘params’)!=null?Zend_Json::decode($request->getParam(‘params’)):array();
// Check if the class invoked is actually allowed to be invoked.
// This check should probably be improved
if(!strpos($serviceName, ‘_Service_’)) {
throw new Exception(‘Illegal access’);
}
$service = $serviceName::getInstance();
$out = array(‘error’ => false);
// If there are any unexpected errors, make sure to catch them.
try {
$out[‘result’] = call_user_func_array(array($service, $method), $params);
// Clientside doesn’t have my model class,
// so if possible, convert it to an array first
if($out[‘result’] instanceof Freak_Model) {
$out[‘result’] = $out[‘result’]->toArray();
}
} catch(exception $e) {
$out[‘error’] = true;
$out[‘errorMsg’] = $e->getMessage();
}
echo $this->_helper->json($out);
}
}
That’s 50% of the solution, the other 50% is the javascript. I chose not to make it asynchronous, but of course you could also do that and simply use a callback instead.
this.callService = function(service, method, params) {
if(stripNull == undefined) {
stripNull = true;
}
var xhrArgs = {
url: “/core/service/call/”,
content: {“service”: service,
“method”: method,
“params”: dojo.toJson(params),
},
handleAs: “json”,
sync: true
};
return dojo.xhrPost(xhrArgs).results[0];
}
Lastly, to use this code, only one line of javascript is needed. This code will call: Comms_Service_PbxIvr::getInstance()->addElement($foo, $var);
var id = App.callService(“Comms_Service_PbxIvr”, “addElement”, [“foo”, “bar”]).result;
Obviously, you should add some errorhandling here.
If you have any suggestions, questions, etc, feel free to contact me or drop them below in the comments.