PHP-O-Lait

News

PHP-O-Lait version 0.5.1 is released, with a bug fix that caused the jsolait.js JavaScript library not to be included correctly in certain circumstances. Many thanks to Olaf Bottek for locating the bug and assisting in correcting it. A few documentation changes have also been made, including an example of using PHP-O-Lait for asynchronous communication: thanks to Keith Powell for the idea.

If you enjoy PHP-O-Lait, please drop me a line at craig@lateral.co.za

What is PHP-O-Lait?

PHP-O-Lait, in two boiler-plate lines of code (three if you include the 'require_once') provides a transparent bridge between server-side PHP methods and client-side JavaScript code.

Here's an example of PHP-O-Lait in action:

examples/simple.php
<?php

require_once("phpolait/phpolait.php");  /* First line */

class MyClass {
  function 
hi() {
    return 
"Hello, World!";
    }
}
$server = new JSONRpcServer(new MyClass());    /* Second Line */

?>
<html>
<head>
<?php $server->javascript("test");    /* Third Line */  ?>    
</head>
<body onLoad="alert(test.hi());">
That's it!
</body>
</html>


Under the hood, PHP-O-Lait uses Ajax to send the request and receive the response, and JSON for the data encoding / decoding. You don't need to know what either of these are to use PHP-O-Lait, but Ajax is a bag-of-tricks using client-side JavaScript to communicate with a server without loading a new page in the browser, and JSON is a light-weight data transfer protocol that is much more bandwidth-friendly than XML, and, I think, better suited to loosely-typed languages such as PHP and JavaScript.

Licence

PHP-O-Lait is licenced under the LGPL, as is jsolait (which is included in the PHP-O-Lait distribution). JSON-PHP is also included in the PHP-O-Lait distribution, and it is licenced under a BSD licence.

Download

All files are on the SourceForge Download Page.

Version History

VersionDateNotes
0.5.122 February 2006 Bug fix for javascript library inclusion in certain circumstances. Some documentation changes.
0.5.0012 January 2006 First public release.

Installation

Installation should be straightforward: unzip the zip file into your webserver directory. I presume, for this documentation, that it's in the root of your webserver, but it should work correctly anywhere. You will have three directories: /doc, which contains this documentation, /phpolait, which contains everything you need to write PHP-O-Lait enabled applications, and /examples, which contains the some examples.

Configuring PHP-O-Lait

PHP-O-Lait should require no configuration at all (thanks to the kindness of Jan-Klaas Kollhof, author of jsolait, and Michal Migurski, author of JSON-PHP), since all required packages are bundled in the phpolait directory.

Optional Libraries

If you want to use JSON-RPC calls to other JSON-RPC servers, PHP-O-Lait has built-in support for native PHP http requests and for the Curl library. If you have the Curl extension installed, PHP-O-Lait will use is automatically. You can also use your own Http classes, if you have particular http request requirements on your server. This process is described in JSONRpcProxy.

You can also install the PHP-JSON C extension for PHP. This is much faster than the bundled JSON-PHP library, but you need to be able to install extensions on your PHP server. Once installed, PHP-O-Lait will automatically detect and use this library in preference to the bundled library.

Usage

Transparent Browser-Server Proxies

Using PHP-O-Lait is straightforward. Both the server-side PHP code and the client-side JavaScript code are placed in the same page. The page is divided into three sections:
  1. Code a class that provides all the server-side functionality your page will need.
  2. Three boilerplate steps:
    1. Instantiation of JSONRpcServer to JSON-RPC enable the PHP class: $server=new JSONRpcServer(new MyClass());
    2. Start of HTML page: <html><head>
    3. Include PHP-O-Lait generated JavaScript proxy to access your server-side class: <?php $server->javascript('proxy'); ?>
  3. Write the rest of your HTML page, in JavaScript and HTML.
The first step, defining the PHP class that provides the server-side functionality (call it MyClass), is the developers, as is the third step, defining the client-side HTML and JavaScript. The middle second step is boilerplate, except for some configuration or access-restrictions you might like to add:

$server = new JSONRpcServer(new MyClass());
?>
<html>
<head>
<?php $server->javascript("myproxy");?>
After this code, you can access your server-side code by calling the methods on the global client-side JavaScript object myproxy.

PHP 4.x: Case-Sensitivity

PHP 4.x function and method names, unlike JavaScript, are case-insensitive. Because of this, in PHP 4.x, all the proxy methods generated for JavaScript are lower-case, irrespective of their case in PHP. If you really don't like this, you can use the $methodMap parameter to rename your methods.

If you're reading this for the first time, I suggest you try some Examples now to see how simple PHP-O-Lait can be. The next two sections are more advanced functionality of the PHP-O-Lait library.

Asynchronous method calling

Once you have the client side Javascript object, any method can be called asynchronously on your client side by passing a callback function of the form function callback(result, error) {..} as the last parameter to the method. Consider this example:
$server = new JSONRpcServer(new MyServer());
?>
<html>
<head>
<?php $server->javascript('server');?>
<script language="javascript">

function callback(res, error) {
  alert('res');
}

function sync() {
  alert('1');
  alert(server.myecho(2));
  alert('3');
}

function async() {
  alert('1');
  server.myecho(2,callback);
  alert('3');
}
</script>
In this example, the sync() function will show three alerts in the order 1, 2, 3. The async() function, on the other hand, will show the three alerts in the order 1, 3, 2, and, since the call is asynchronous, control will revert to the browser between the 3 and the 2.

Using PHP-O-Lait to JSON-Rpc Enable any class

JSON-RPC is a protocol enabling any client to make a remote call to a method on a server. The transfer protocol that PHP-O-Lait supports is, obviously, http, and the encoding of the request and the response is done in JSON (JavaScript Object Notation). The JSONRpcServer class, besides the javascript-generating sugar, can JSON-RPC enable any PHP class. Simply pass an instantiation of the class to a new instance of the JSONRpcServer class.

Example 1

new JSONRpcServer( new MyServerClass() );
That's it. Any method on MyServerClass can now be accessed over JSON-RPC by making a JSON-RPC request to the url of your page.

Note that, if the JSONRpcServer class finds a JSON-RPC request that requires processing, it processes that request and terminates the script. The script will only continue if no such request was found.

You can only have one JSONRpcServer per page, both because JSONRpcServer terminates the script after it has processed a request, and because multiple classes on a page would not make sense from a caller's perspective.

See JSONRpcServer for details of additional parameters.

Calling remote JSON-RPC Servers from PHP

PHP-O-Lait also supports the ability to make a remote JSON-RPC call from PHP code. This is done using the JSONRpcProxy class.

To call a remote JSON-RPC server, simply create an instance of JSONRpcProxy with the Url of the server.

$proxy = new JSONRpcProxy("http://jsolait.net/testj.py");
list ($result, $error) = $proxy->echo("Testing JSONRPC on Jan-Klaas Kollhof's server!");
?>

Any method called through JSONRpcProxy returns 3 results in an array, a result that is the value the call returned, an error, that will be null if no error occurred, and extended error information that is the actual response from the remote server. This is useful for debugging, since, if there is an error in a JSON-RPC server code, no JSON result might be returned, but an error code could be generated. In this case, result would be null, error would indicate that the call failed, and the extended error information would contain the actual response received from the server.

Note that a null result does not mean that an error occurred: a method can quite legitimately return null. An error occurred only if the error value returned is non-null.

Examples

The code for these examples is in the /examples directory.

Hello, World-O-Lait!

Very simple example of using JSON-RPC to retrieve a value from the server. [Demo]

examples/helloworld.php
<?php 

require_once("../phpolait/phpolait.php");

class 
MyClass {
  function 
hi() {
    return 
"Hello, World-O-Lait!";
    }
}
$server = new JSONRpcServer(new MyClass());

?>
<html>
<head>
<?php $server->javascript("test"); ?>
</head>
<body onLoad="alert(test.hi());">
</body>
</html>

Calculator

Demonstrates integration of Server-Side PHP execution and Client-Side JavaScript again, this time with a slightly more interesting example. [Demo]

examples/calculator.php
<?php

require_once ("../phpolait/phpolait.php");

class 
MyServer {
  function 
add ($a$b) {
    return 
$a+$b;
  }
}
$server = new JSONRpcServer(new MyServer());

?>
<html>
<head>
<? $server->javascript("proxy"); ?>
<script language="javascript">
function doCalculation() {
  var firstValue = document.getElementById('firstValue').value;
  var secondValue = document.getElementById('secondValue').value;
  document.getElementById('result').value = proxy.add( firstValue, secondValue );
}
</script>
</head>
<body>
<form>
Server-Side Calculator:<br />
<input type="text" id="firstValue" size="5"> + 
<input type="text" id="secondValue" size="5"> 
<input type="button" onClick="doCalculation();" value="=">
<input type="text" id="result" size="5"> 
</form>
</body>
</html>

Renaming Methods

This demonstrates renaming and restricting method-access. Notice that renaming is being used to introduce capitalisation in JavaScript (the Echo method), and also that renaming permits a method to have a name that PHP would not permit (Echo is a reserved word in PHP).

The version method is available because it appears as a key in the $methodMap array. Renaming only occurs if the value is a string. If the value is any other type, the method will be accessible by its PHP name (which will be lowercased if you're using PHP 4.x). [Demo]

examples/rename.php
<?php

  
require_once ("../phpolait/phpolait.php");

  
/**
   * Simple test class for the JSON RPC server
   */
  
class RestrictedServer {
        
    function 
myecho($msg) {
      return 
$msg;
    }

    function 
hidden() {
      return 
"Inaccessible method";
    }

    function 
version() {
      return 
"PHP-O-Lait Version 0.5";
    }
  }

  
$server = new JSONRpcServer( new RestrictedServer(), 
        array (
"version"=>true"Echo"=>"myecho")
    );
?>
<html>
<head>
<?php $server->javascript('proxy'); ?>
</head>
<html>
<body>
<a href="javascript:alert(proxy.Echo('Echo from server'));">
Echo('Echo from server')</a><br />
<a href="javascript:alert(proxy.version());">
version()</a><br />
<a href="javascript:alert(proxy.hidden());">
hidden()</a> 
(Won't produce an error since the JavaScript method does not exist.)<br />
</body>
</html>

Asynchronous Method Calls

Passing a final parameter that is a callback function to a method call will cause the method to be called asynchronously. When the result is available, the callback function is called. [Demo]

examples/async.php
<?php 

require_once("../phpolait/phpolait.php");

class 
MyClass {
  function 
myecho($msg) {
    return 
$msg;
    }
}
$server = new JSONRpcServer(new MyClass());

?>
<html>
<head>
<?php $server->javascript("test"); ?>
<script language="javascript">
function syncCall() {
  alert('syncCall()');
    alert( test.myecho('Hello from synchronous call') );
    alert('leaving syncCall()');
}

function asyncCall() {
  alert('asyncCall()');
    test.myecho('Hello from synchronous call', asyncCallback);
    alert('leaving asyncCall()');
}

function asyncCallback(res, err) {
  alert(res);
}
    
</script>
</head>
<body>
<a href="javascript:syncCall();">Synchronous call</a><br />
<a href="javascript:asyncCall();">Asynchronous call</a><br />
</body>
</html>

Remote-Server Access

Because a browser restricts our connections to our originating server, we can't fetch arbitrary websites. This examples uses JSON-RPC and an http proxy of sorts on the server to fetch an arbitrary website. [Demo]

examples/webfetch.php
<?php

  
require_once ("../phpolait/phpolait.php");

  
/**
   * Proxy reads a URL and returns data to the client. Client-side security restrictions
     * prevent this being done client-side.
   */
  
class WebFetch {
    function 
get($url) {
          if (
substr($url,0,7)!="http://") {
              
// Otherwise, could read system files!
                
return "Access denied";    
            }
            return 
file_get_contents($url);
    }
  }

  
$server = new JSONRpcServer( new WebFetch());
?>
<html>
<head>
<?php $server->javascript("webfetch"); ?>
<script language="javascript">
function fetchWebSource() {
  var url = document.getElementById('url').value;
    webfetch.showErrors=true;
    document.getElementById('html').value = webfetch.get(url);
}
</script>
</head>
<body>
<form>
URL: <input type="text" id="url" size="60" 
                value="http://www.sourceforge.net/projects/phpolait/">
<input type="button" 
                onClick="javascript:fetchWebSource();" value="Fetch">
<br />
<textarea rows="25" cols="60" wrap="true" id="html"></textarea><br />
</form>
</body>
</html>

JSON Rpc Services

This demonstrates accessing remote JSON Rpc Services from PHP using JSONRpcProxy. [Demo]

examples/echo.php
<?php

  
require_once("../phpolait/phpolait.php");
    
  class 
EchoServer {
    function 
myecho($msg) { return $msg; }
  }
    
  
$server = new JSONRpcServer(new EchoServer(), array("echo"=>"myecho"));
?>
<html>
<head><?php $server->javascript("proxy"); ?>
<script language="javascript">
function doEcho() {
  msg = document.getElementById('msg').value;
    document.getElementById('returned').value = proxy.echo(msg);
}
</script>
<body>
<form>
<input type="text" id="msg" value="This echoes"> 
<input type="button" value="send" onClick="doEcho()" >
<input type="text" id="returned" value="">
</form>
</body>
</html>


examples/jsonrpc.php
<?php

require ("../phpolait/phpolait.php");

$proxy = new JSONRpcProxy(
   
"echo.php"
);
list (
$result$err$ext) = 
    
$proxy->echo("Testing JSONRPC echo on PHP-O-Lait's server!");
if (
$err!=null) {
  echo 
"ERROR $err: $ext";
} else {
  echo 
$result;
}

?>

Proxy to Another Page

Suppose that I have used PHP-O-Lait to access some class on one web-page and I want to use the same functionality on another page. Obviously, I don't want to duplicate the code. Since the first page uses a class, I could simply re-use the class on another page, or use the class as a base class and add new functionality for the current page, or create proxy methods on the current page's class that calls methods on the first page's class, or JSON-RPC Proxy my way to the third page.

This example demonstrates the latter method (which it not the recommended method), since the other methods are standard OO approaches. [Demo]

examples/adduser.php
<?php

require("../phpolait/phpolait.php");

class 
AddUserServer {
  function 
AddUser($name) {
        return 
"Added user [$name]";
    }
};

$server = new JSONRpcServer(new AddUserServer());

?>
<html>
<body>
Regular PHP-O-Lait functionality to add a user.
</body>
</html>


examples/manageuser.php
<?php

require("../phpolait/phpolait.php");

class 
UserManagementServer {
  function 
AddUser($name) {
        
$proxy = new JSONRpcProxy("adduser.php");
        list (
$result$error) = $proxy->AddUser($name);
        return 
$result;
    }
    function 
DeleteUser($name) {
        return 
"Deleted user [$name]";
    }
};

$server = new JSONRpcServer(new UserManagementServer(),
    array(
"adduser"=>"AddUser","deleteuser"=>"DeleteUser")
);

?>
<html>
<head>
<?php $server->javascript('server'); ?>
<script language="javascript">
function doClick(act) {
    var name = document.getElementById('name').value;
    if (act == 'add') alert(server.adduser(name));
    if (act == 'del') alert(server.deleteuser(name));
}
</script>
<body>
<form>
<input type="text" id="name">
<input type="button" onClick="doClick('add');" value="Add" >
<input type="button" onClick="doClick('del');" value="Delete" >
</body>
</html>

Reference

new JSONRpcProxy ($server_url [, $HttpWrapper=null [ , $jsonlib=null]] )
A JSONRpcProxy provides a proxy object to make calls on a remote (or local to the current server) JSON RPC server. The $server_url is the url of the server to be accessed. It can be an absolute or a relative path.

Example 1

MyServer.php
class MyServer { function add($a,$b) { return $a+$b; } }; new JSONRpcServer(new MyServer());
MyClient.php
$proxy = new JSONRpcProxy("MyServer.php"); echo "1 + 2 = " . $proxy->add(1,2);
The optional $httpWrapper parameter can specify a class to provide Http functionality. The HttpWrapper classes HttpWrapper_CURL and HttpWrapper_PHP classes are defined in httpwrap.php, and use the CURL and native PHP sockets respectively. Any HttpWrapper class is expected to provide a single function:

class HttpWrapper_X {
  function post($url, $data, $referrer=null) {
	  // Perform the HTTP Post and return ONLY the body of the HTTP result
	};
}

You can use any class of your own making to provide this functionality, and simply pass an instance of your own class as the second parameter to the constructor of the JSONRpcProxy class.

If no HttpWrapper is provided in the constructor, JSONRpcProxy uses the HttpWrapper::GetWrapper() method defined in httpwrap.php to select a suitable wrapper (CURL if you have the Curl extension installed, native PHP otherwise). If you want to customize PHP-O-Lait for your site, it can be easily achieved by changing this method to return the particular HttpWrapper type class you would like to use.

The optional third parameter, $jsonlib, points to the location of the JSON.php JSON-PHP library file. You should never need to use this parameter unless you are restructuring the location of your files for some reason.

new JSONRpcServer ( $object [, $methodMaps=null [, $config=null ]])
Constructs a new JSONRpcServer to provide access to the methods of $object.

Example 1: Basic Usage

require_once ("jsonrpc.php");

class MyServer {
  function add($a, $b) {
    return $a+$b;
  }
};

$server = new JSONRpcServer(new MyServer());
By default, every method of the object passed to JSONRpcServer will be available as a JSON-RPC Method. Since this might not be ideal, for security or other reasons, the $methodMap parameter can be used to:
  1. Restrict access to particular methods, or
  2. Rename methods.
The $methodMap parameter consists of an associative array that maps JSON-RPC method names to the actual methods that will be called on the object. If a method name exists as a key in $methodMap, a method by that name will be accessible to the client code. If a name does not exist as a key, the method will not be accessible. This is useful for security. If a method name in $methodMap is mapped to a string, then the method will be given the key-name on the client side, but the value-name will be the actual method called. This might seem like a strange idea (why offer method renaming?), but it is in fact useful: it offers the ability to have methods that are PHP-reserved words. The method in Example 2 is called echo, but you can't write a function called echo.

Example 2: Renaming Methods and Restricting Access

class ProtectedServer {

	function myecho($msg) {
		return $msg;
	}

	function privateFunction($code) {
		eval($code);
	}

	function version() {
		return "Version 1.0 alpha";
	}
}

$server =  JSONRpcServer( new ProtectedServer(), array ("version"=>true, "echo"=>"myecho"));
The final, optional parameter, can be used to specify the locations of the JSON-PHP library (the PHP library) or the jsolait library files.

The full file location of the JSON-PHP library can be specified by passing a jsonlib key-value in the $config parameter, and the root directory where the jsolait directory is located can be specified in a jsolait key-value. If either is absent, the default is used.

It is extremely unlikely you will ever need to use this parameter.

Example 3: Configuring locations of JSON-PHP and jsolait libraries

$server =  JSONRpcServer( new MyServer()
			, null /* Full access, no renaming */
			, array ("jsonlib"=>"/libs/jsonlib.php", "jsolait"=>"/libs")
);
JSONRpcServer::javascript ( $varName )
Once the JSONRpcServer has been constructed, the javascript method inserts the appropriate JavaScript code into the webpage. It takes a single parameter, the name of the global variable to hold the proxy object.

It is recommended to call the javascript method in the <head> tag of your web page, but do not put the call between <script> tags: the method will insert these itself.

Note: If you're using PHP 4.x, unless you rename methods using the $methodMap parameter in the constructor of the JSONRpcServer, all methods will be lowercase in JavaScript. This is because methods in PHP 4.x are case-insensitive.

Example 1

<html>
<head>
<?php echo $server->javascript("proxy"); ?>
</head>
<html>
...
After this code has been inserted, you can code your own Javascript functions, calling you PHP methods on the JavaScript proxy object. PHP-O-Lait will route these calls to the server, execute the method on your PHP object, and return the results as JavaScript values (strings, bools, ints, Arrays or Objects).

Roadmap

For the 0.6 release of PHP-O-Lait I am planning improved error handling and debugging tools, an area where JavaScript is not particularly strong. I am also considering adding the ability to provide JavaScript side connections to other web pages (ie. not just back to one's own page), so that one can access server objects elsewhere on ones site. As discussed above, this might not actually be necessary, since one can achieve this easily using OO techniques.

Thanks

  • Special thanks to Jan Kollhof for jsolait and for graciously allowing me to use the PHP-O-Lait name for this project, and to bundle jsolait with this distribution.
  • Special thanks to Michal Migurski for developing JSON-PHP and allowing me to bundle JSON-PHP with this distribution.
  • Thanks to the folks developing PHP-JSON, which can also be used to provide server-side JSON encoding.
  • Thanks to the folks at Sajax, where I shamelessly stole the idea of wrapping all functionality into a single page, and having the JSONRpcServer object generate all the appropriate Javascript proxies.