Daily Archive for August 7th, 2009

JSOS : JS-PHP mapping

I’ve been working on Javascript-PHP remote call mapping techniques. A set of PHP classes with static functions are selected as a public interface and automatically exported so that the Javascript can call them. Usually, this kind of mapping involves three difficult points:

  • Detecting what functions are exported and building the appropriate JS code.
  • Detecting what function the JS is calling.
  • Transforming data between JS and PHP.

I solve these with glob(), __autoload and json_encode (respectively) in a little prototype I called JSOS (JavaScript Or Something). jQuery on the client side provides me with synchronous AJAX queries. The code (PHP with echoed Javascript) looks like this, and is placed in a controller:

<?php
 function __autoload($classname)
 {
   require_once($classname . '.inc.php');
 }

 if (!isset($_POST['cls'])) {
   echo '<html><head><title>JSOS</title>';
   echo '<script type="text/javascript" src="jquery.js"></script>';
   echo '<script type="text/javascript">var jsos={};';
   echo '$.ajaxSetup({async:false,timeout:5000});';
   echo 'jsos.$=function(c,f,a){';
   echo 'var o={exception:"Server disconnect"},';
   echo 't={cls:c,func:f};';
   echo 'for(var i in a)t[""+i]=a[i];$.post(".",t,';
   echo 'function(d){o=d},"json");if("exception" in o)throw o.exception;';
   echo 'return o.result};';

   foreach (glob('*.inc.php') as $file)
   {
     $class = str_replace('.inc.php', '', $file);
     echo 'jsos.' . strtolower($class) . '={};';
     $text = file_get_contents($file);

     preg_match_all('/public static function ([A-Za-z_0-9]+)\(([^)]*)\)/',
                    $text, $func);

     foreach ($func[1] as $id => $value) {
       echo 'jsos.'.strtolower($class).'.'.strtolower($value).'=function(';

       $args = explode(',', $func[2][$id]);

       foreach (array_keys($args) as $key) {
         $args[$key] = str_replace(array(' ', '$'), '', $args[$key]);
       }

       $args = array_filter($args);

       echo implode(',', $args).'){return jsos.$';
       echo '("'.$class.'","'.strtolower($value).'",['.implode(',',$args).'])};';
     }
   }

   echo '</script></head><body></body></html>';
   exit;
 }

 $args = array();
 foreach ($_POST as $key => $val) {
   if ($key === 'cls')
     $cls = $val;
   elseif ($key === 'func')
     $func = $val;
   else
     $args[(int)$key] = $val;
 }

 ksort($args);

 if (!class_exists($cls)) {
   echo '{exception:"No such package!"}';
   exit;
 }

 if (!method_exists($cls, $func)) {
   echo '{exception:"No such method!"}';
   exit;
 }

 try {
   $result = call_user_func_array(array($cls, $func), $args);
   echo json_encode(compact('result'));
   exit;
 }
 catch (Exception $e) {
   $exception = "$e";
   echo json_encode(compact('exception'));
   exit;
 }
 

All files in the current directory with a ‘.inc.php’ extension are assumed to contain a similarly-named class, and all public static functions of that class are exported. For example, suppose Test.inc.php contains the following definition:

class Test
{
  public static function Run($a) { return "$a$a"; }
}

Then, to call the Test::Run function from the above page, one would simply type in Javascript:

jsos.test.run('Hello');

And indeed, with only five lines of code, the result to a client request is computed on the server and displayed on the client again, without issues. Here’s the result running in Firebug:

jsos

Further work should include handling errors (right now, if the server encounters an error or outputs non-JSON data, the call will die with a “Server disconnect” exception), which makes debugging easier than having to wade through the Net tab of Firebug.



1150 feed subscribers
(readers who polled a feed this week)