PHP, JS & Service layers: Blend like never before

By Freeaqingme on Monday 14 March 2011 20:15 - Comments (9)
Category: Zend Framework, Views: 13.801

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.



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:


PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
class Core_ServiceController extends Zend_Controller_Action
{
    public function callAction() {
        // Retrieve and parse the input
        $request = $this->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.

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    this.callService = function(servicemethodparams) {
        if(stripNull == undefined) {
            stripNull = true;
        }
        
        var xhrArgs = {
                url"/core/service/call/",
                content: {"service"service,
                        "method"method,
                        "params":  dojo.toJson(params),
                        
                },
                handleAs"json",
                synctrue
        };
       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);

JavaScript:
1
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.

Volgende: OpenVPN, IPv6 with ULA and NAT 08-'13 OpenVPN, IPv6 with ULA and NAT
Volgende: MariaDB: Replaces Mysql, gives you more (without effort!) 01-'11 MariaDB: Replaces Mysql, gives you more (without effort!)

Comments


By Tweakers user mithras, Monday 14 March 2011 23:51

Actually I wouldn't use the action controllers for an api service like a JSON one. In your action controller you have a kind-of dispatch process which is not part of an action controller's function. And for the Zend Framework, there are nice components which can help you out with this kind of service.

Matthew Weier O'Phinney wrote a good article about exposing service apis to the outer world: here.

Essentially you create a similar file to the public/index.php but you do not run the application, but create a Zend_Json_Server instance:

PHP:

1
2
3
<?php
$server = new Zend_Json_Server();
$server->setClass('Application_Service_Api');
echo $server->handle();
?>

Inside your Api service class you can proxy to other service classes.

[Comment edited on Monday 14 March 2011 23:51]


By Tweakers user Freeaqingme, Tuesday 15 March 2011 00:16

You're absolutely right that I could have. However, Zend_Json_Server relies on reflection and docblooks, and I don't want my code to rely on comments...

Furthermore, I could indeed use a separate point of entry to my app, and it may have been cleaner. But for a simple api like this I didn't think it would be worth the time/effort.

However, we're currently busy with the MVC part and Zend\App of ZF2, and you can be sure we integrate this stuff into the dispatcher. Also, with a project of mine about a year ago I had to use REST and that was when a prototype for zf2 was written because the current implementation sucks isn't so good.

By till, Tuesday 15 March 2011 00:44

I like your implementation, thanks for blogging about it.

I also used Zend_Json_Server a while, but the overhead was not worth it for me but I'm not sure how far it came along though, so maybe that changed. Your code looks clean in my book and it does exactly what it's supposed to. :-)

By Tweakers user mithras, Tuesday 15 March 2011 10:27

Freeaqingme wrote on Tuesday 15 March 2011 @ 00:16:
Furthermore, I could indeed use a separate point of entry to my app, and it may have been cleaner. But for a simple api like this I didn't think it would be worth the time/effort.

However, we're currently busy with the MVC part and Zend\App of ZF2, and you can be sure we integrate this stuff into the dispatcher. Also, with a project of mine about a year ago I had to use REST and that was when a prototype for zf2 was written because the current implementation sucks isn't so good.
Nice, I am curious for the examples and demos in this area :)

[Comment edited on Tuesday 15 March 2011 10:27]


By Spabby, Tuesday 15 March 2011 11:24

It's a nice idea Freeq, and one I will be looking to steal mercilessly from in my next project. I can't see me retrospectively applying it to the current project mind! Thanks.

Spabby

By Tweakers user DLGandalf, Tuesday 15 March 2011 19:09

I've been doing almost the exact same thing, albeit with a jQuery plugin which really maps the rpc methods to a JS object.

so you could do something like this:

code:
1
2
calculator = zend.jsonrpc(/* calculator example used in zend framework*/);
addWillBe4 = calculator.add(2,2);


It really makes it a lot more transparent.

However, I think it's a real schame that inherent to RPCs you lose information regarding classes/OOP-design, and also exceptions have to be proxied in some consistent way. However I've not yet seen some best practice.

the jquery plugin:
http://www.tanabi.com/project/zendjsonrpc

[Comment edited on Tuesday 15 March 2011 19:10]


By Tweakers user Freeaqingme, Tuesday 15 March 2011 19:49

Thank you for your comment.

One could indeed suggest that it's neater to map the results back to an object again that represents that specific domain. What I wanted to achieve though was to tie serverside and clientside together without duplicating any kind of code. And if I implemented my models on the clientside as well, I would have had to implement all my models twice everywhere.

In my case that doesn't hold any real value because my models are generally (in essence) a simple object that holds an array, whereas all the logic happens inside my mappers (which don't get returned in the first place). However if you were to implement a pattern like Active Record, I'd say you are kinda required to reimplement your models on the client side too.

Edit: your links don't work btw when hotlinking ;)

[Comment edited on Tuesday 15 March 2011 19:57]


By Tweakers user DLGandalf, Thursday 17 March 2011 00:38

There is no duplication, the zendjsonrpc package does this on the fly. So it's still a write once! But makes the javascript side a lot less hassle.
The zendjsonrpc lib makes use of the SMD (look at Zend_Json_Server for more details) from the server to recreate a similar object in javascript.

edit:
sorry about the link in my previous post, but the website doesn't use proper linking due to javascript.

[Comment edited on Thursday 17 March 2011 00:39]


By Tweakers user Freeaqingme, Thursday 17 March 2011 02:36

hmm, that is interesting. I must admit that all of Zend_Json I really know is ::encode() en ::decode(). I'm still not fond of having to add that extra class to "map" the public api to the internal methods, but it may be interesting for sure!

Comment form
(required)
(required, but will not be displayed)
(optional)

Please enter the code from the image below: