📜 ⬆️ ⬇️

PHP, JavaScript, RPC and other scary words

We all gathered here are smart, educated, beautiful and experienced. And today, it seems to me, almost everyone uses one or another type of RPC between JavaScript and PHP, which works on the API of one or another framework. Some who even develop some of their crutches and props. I am no exception, of course. True, I followed the path of least resistance and, in fact, the discussion in this article will go about this - the next implementation of XML \ JSON RPC for JavaScript and PHP.

The background is such that I needed to develop some kind of data management system. Naturally, the data is stored in a DBMS, and it is necessary to manage them through the web. I really didn’t want to get attached to ready-made frameworks, because the choice was not great - PHP-bydlokoding, or MVC with rendering based on ready-made developments like smarty. However, at about the same time, I drew attention to projects such as extJS (aka Sencha now) and qooxdoo, which allow you to create full-fledged web applications without tedious HTML layout, HTML / XML generation, XSLT transformations and many other “scary »MVC and PHP bydlokoda stuff. Therefore, the following plan of action matured:
Thus, I don’t even know what the model turned out to be, but this is rather a thick client, not MVC. In general, what happened, in the end I really like. For the interface, the choice fell on qooxoo - this is the GNU license and, in my opinion, its code is still more focused on OOP and OP than on declarative JSON-style programming of the interface in extJS, and so it happened that I am closer and more understandable. But with the RPC had to tinker a bit.

I wanted to find something lightweight, independent, understandable, simple, and working from the floor of a kick. Such a project was found at code.google.com/p/json-xml-rpc - this is a fairly flexible and simple RPC implementation with a PHP server and a JavaScript client that works with both XML and JSON and supports both synchronous, and asynchronous calls, which was not unimportant in consequence. The next step was the expansion of RPC, as well as the system of distribution of roles and access rights. I want to tell you more about this. So.
')
To begin with, I am an adherent of the OOP school and generally write in C ++ in the original, in the end, as such, PHP itself and JavaScript sometimes insanely bother me in terms of what they allow or do not allow to do in the code. In spite of this, I think that it is not only possible but necessary to write code that will be reused on OOP. And it does not matter what to write on. Therefore, for this system a set of interfaces and their implementations were developed, which reflected such aspects of work as sessions, authorization, user and his role, as well as the RPC proxy system, which allows not only to fully transfer PHP classes to JavaScript but also allocate access rights to these classes up to method call.

To begin, I will show you how it looks from the point of view of problem statement and code in PHP and JavaScript. Suppose we have a simple class:
class Test {
public function mul ($param1, $param2) {
return $param1 * $param2;
}
}


* This source code was highlighted with Source Code Highlighter .
We want to have the same exact class with exactly the same functionality in our client-side JavaScript and write something like:
var test = new Test();
var result = test.foo(1,2);

* This source code was highlighted with Source Code Highlighter .
For this, in the current version, I need to create a file (let's call it for decency aip / Test.php) with the following contents:
include '../base/IRPCProxy.inc' ;

class Test extends IRPCProxy {
public function mul ($param1, $param2) {
return $param1 * $param2;
}
}

IRPCProxy::populate( new Test());

* This source code was highlighted with Source Code Highlighter .
And we will create test JavaScript in another file (without further ado we call it test.html) of such a plan:
var test = new rpc.ServiceProxy( 'api/Test.php' , { asynchronous: false });
var result = test.mul (19, 7);

* This source code was highlighted with Source Code Highlighter .
As you can see, nothing has changed much either in terms of PHP or in terms of JavaScripta. You just need to inherit the class we need in PHP from IRPCProxy and “in a special way” initialize the test variable in JavaScript. Naturally, the JavaScript code is not complete, but in the end, the use of such remote objects looks like this in the code.

Now what concerns access rights. By itself, the RPC system has no idea about sessions, or about users, or, naturally, about their roles. This is all imperceptibly and simply managed by the classes and interfaces I have developed. Basic entities:It should be noted that when requesting PHP, the code for RPC is not generated and parsed each time, but is cached, depending on the session parameters. Moreover, in the current implementation, the code is cached as session data, but perhaps it would be more advantageous to store them in separate files or somehow, I did not test this option. So, as a result, in order to get a complete system, we need to implement a user code and session code. This is not necessary, since the base classes do their job well without concrete refinements in the implementation. That is, the code written above will work exactly as you expect.

Let's start with the user. We will have it lazy and its authorization will consist only of checking for the correct login and changing the role (file impl / TestUser.inc):
/**
* RPC user implementation
* @author alexander.voronin@gmail.com
*/
class TestUser extends IRPCUser {
/**
* @see IRPCUser::authorize()
*/
public function authorize ( $login, $password ) {
if ( $login == "test" ) {
$ this ->login = $login;
$ this ->role = "admin" ;
return true ;
} else {
IRPCProxy::logText( "Authorization failed for user '$login' - invalid login" );
return false ;
}
}
};

* This source code was highlighted with Source Code Highlighter .
Next we need a session. In order not to confuse data with other projects, our session will be initialized with a special prefix “test” (file impl / TestSession.inc):
require_once '../base/IRPCSession.inc' ;

/**
* RPC session implementation
* @author alexander.voronin@gmail.com
*/
class TestSession extends IRPCSession {
/**
* Reload CTOR and create own session namespace
*/
function __construct () {
parent::__construct( "test" );
}
};

* This source code was highlighted with Source Code Highlighter .
And finally, the proxy implementation itself should “know” that we are using not our standard users and sessions, but our own (impl / RPCTestProxy.inc file):
// base
require_once '../base/IRPCProxy.inc' ;
// implementations
require_once 'TestSession.inc' ;
require_once 'TestUser.inc' ;

/**
* RPC proxy implementation
* @author alexander.voronin@gmail.com
*/
class TestProxy extends IRPCProxy {
/**
* @see IRPCProxy::createSession()
* @return TestSession
*/
public function createSession() {
return new TestSession();
}
/**
* @see IRPCProxy::createUser()
* @return TestUser
*/
public function createUser() {
return new TestUser();
}
}

* This source code was highlighted with Source Code Highlighter .
Now we are ready to assign permissions to roles in our RPC code in PHP. From the point of view of the current implementation, this is done once at the request, and the resulting code is cached in the session data. The key to the cached code is complex and consists of the session prefix and user role, so that if the user changes his role during the work, the missing code will be generated automatically. It should be noted that if the code already exists, you can only reset the cache by destroying the session or affect the cached code "directly", which, of course, is better not to do so that the code remains clear and readable. So, we distribute the rights (api / Test.php file):
<?php

include '../impl/RPCTestProxy.inc' ;

class Test extends RPCTestProxy {
function haveAccess ( $method ) {
switch ( $ this ->getUser()->getRole()) {
case "anonymous" :
switch ( $method ) {
case "mul" :
case "getRole" :
case "sessionLogin" :
return true ;
default :
return false ;
}
case "admin" :
return true ;
default :
return false ;
}
}

public function mul ($param1, $param2) {
return $param1 * $param2;
}

public function getRole () {
return $ this ->getUser()->getRole();
}

public function sessionLogin ($login) {
if ( $ this ->authorize($login, "" )) {
return true ;
} else {
return false ;
}
}

public function sessionLogout () {
$ this ->logout();
}

public function forAdmins () {
return "hello admin" ;
}
}

IRPCProxy::populate( new Test());

?>


* This source code was highlighted with Source Code Highlighter .
As can be seen from the code, we simply define access to the class methods by their name and based on the current role of the user. We also added the sessionLogin method, which allows us to log in without a password and sessionLogout, which obviously ends our session. You should not worry about what is happening inside - everything just works there and, after successful authorization, the role will change for the user, as we agreed above. This is all we need for further work. Now we extend our JavaScript test to show how it all works:
// simple log
function log ( msg ) {
document .write( 'LOG: ' + msg + '\n' );
}
var start = new Date();

// RPC init
var test = new rpc.ServiceProxy( 'api/Test.php' , { asynchronous: false });

log( 'Test calls: ' + test.system.listMethods());
log( 'Role: ' + test.getRole());

var result = test.mul (19, 7);
log ( 'Test result: ' + result );

// try to login with invalid credentials
if ( !test.sessionLogin ( 'vasya' )) {
log ( 'Login failed!' );
} else {
log ( 'Login success!' );
}
// check role after login
log( 'Role: ' + test.getRole());

// now try to login with valid credentials
if ( !test.sessionLogin ( 'test' )) {
log ( 'Login failed!' );
} else {
log ( 'Login success!' );
}
// check role after login
log( 'Role: ' + test.getRole());

// must reinit RPC to get access to new methods
test = new rpc.ServiceProxy( 'api/Test.php' , { asynchronous: false });
log( 'Test calls: ' + test.system.listMethods());
log( 'Admin test: ' + test.forAdmins());

// logout now
log ( 'Logout now...' );
test.sessionLogout();
// check role after login
log( 'Role: ' + test.getRole());

// check timing
var stop = new Date();
var testTime = stop.getTime() - start.getTime ();
log ( 'Test time: ' + testTime + 'ms' );

* This source code was highlighted with Source Code Highlighter .
As can be seen from the code, during its execution, the user changes his role twice. In this case, after authorization, you need to re-create an RPC object in order to update its list of methods. After logout, the session with all data is destroyed and the user role is changed to anonymous. Here is a typical result of the execution of such code in the browser:

LOG: Test calls: mul,getRole,sessionLogin,system.setJSONDateFormat,system.setDBResultIndexType
LOG: Role: anonymous
LOG: Test result: 133
LOG: Login failed!
LOG: Role: anonymous
LOG: Login success!
LOG: Role: admin
LOG: Test calls: mul,getRole,sessionLogin,sessionLogout,forAdmins,system.setJSONDateFormat,system.setDBResultIndexType
LOG: Admin test: hello admin
LOG: Logout now...
LOG: Role: anonymous
LOG: Test time: 212ms


As a result, I got a working web system with no HTML layout at all. I write the code of the data models and their processing in pure, unclouded PHP and allow access to them through the RPC system described above, and the client is a normal qooxdoo application, with its windows, ruffles and frills. For such systems, the century MVC has ended, and glory, as they say, good Lord!

In conclusion, I would like to say a few words about performance. Sure - RPC is slow. However, using asynchronous RPC requests, we can get rid of browser hangs at the time of RPC calls, and of course, pay attention to the fact that the code is written. Perhaps a dozen RPC calls should be merged into one, or think about the fact that you return as a result of the method.

The example described above in its entirety, along with the wrapper RPC code, can be downloaded at depositfiles.com/files/xsnid5wg9 - please refer to the author if you think it’s possible to use it somewhere. Thank.

Good, beautiful and stable code to you!

Source: https://habr.com/ru/post/110727/


All Articles