Commits

Anonymous committed bdb6561

Major upgrade to actually handle remote objects (not just Plain Old Data)

  • Participants
  • Parent commits ad57d81

Comments (0)

Files changed (6)

-/* 
-
-Copyright (c) 2011, Toby Davies
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-      
-* Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-* Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-   
-*/
-
 /*
- * JSON AJAX RPC framework, client-side code.
+ * JSON RPC framework, client-side code.
  * Makes remote procedure calls to PHP (see rpc.php)
  * PHP functions can effectively be called from javascript as methods on an
  * RpcService object initialized as RpcService(url). 
  * {success:{some:"JSON", encoded:["object"]}
  *
  */
-
 //wrap in anon function call to aviod polluting global name space
 (function(){
     //create RPCError class for errors thrown in PHP
-    window.RPCError=function(msg){
+    window.RPCError=function(msg,e){
 	if(this instanceof RPCError){
 	    this.msg=msg;
+	    this.error=e;
 	}else{
-	    throw new RPCError(msg);
+	    throw new RPCError(msg,e);
 	}
     }	
     //low-level XmlHttpRequest function which may or may not be asynchronous
     //parse text as json, calling s if successful, e on RPCError
     function parse(text,s,e){
 	var scb=s || function(v){return v;};
-	var ecb=e || function(e){RPCError(e);};
+	var ecb=e || function(m,e){RPCError(m,e);};
 	var x=JSON.parse(text);
 	if(typeof(x.error) != 'undefined')
-	    return ecb(x.error);
+	    return ecb(x.errormsg,x.error);
+	if(typeof(x.uri) != 'undefined')
+            return new RemoteObject(x.uri,x.methods);
 	return scb(x.success);
     }
     //make an RPC call to url calling func with argList.
     function rpc(url,func){
 	return function(){return do_rpc(url,func,arguments);};
     }
-   
-    //constructor for RpcService class
-    //call the RPC server with GET - it returns a JSON array of the method names to add to the service object
-    //(using normal json RPC format i.e. {error:"error msg"} or {success:["JSON","Object"]})
-    window.RpcService=function(url){
-	var data=parse(xhr(url));
-	for(i=0;i<data.length;i++){
-	    this[data[i]]=rpc(url,data[i]);
+
+    window.RPC_init= function(obj,url,methods){
+	for(i=0;i<methods.length;i++){
+	    obj[methods[i]]=rpc(url,methods[i]);
 	}
-    };
-})();
+    }
+
+    if(! window.RemoteObject){
+	//constructor for RemoteObject class
+	//call the RPC server with GET - it returns a JSON array of the method names to add to the service object
+	//(using normal json RPC format i.e. {error:"error msg"} or {success:["JSON","Object"]})
+	window.RemoteObject=function(url,methods){
+	    if(this instanceof RemoteObject){
+		if(!methods)
+		    methods=parse(xhr(url));
+		RPC_init(this,url,methods);
+	    }else{
+		return new RemoteObject(url);
+	    }
+	};
+
+	//for compatibility
+	window.RpcService=window.RemoteObject;
+    }
+})();
+<?php
+class RPC{
+  private $url;
+
+  public static function publicMethods($obj){
+    $ignored=is_a($obj,'RemoteObject')?get_class_methods('RemoteObject'):array();
+    return array_values(array_diff(get_class_methods($obj),$ignored));
+  }
+
+  public static function rpcException($exception,$msg=null){
+   return json_encode(array('errormsg'=>($msg?$msg:$exception->getMessage()),
+			     'error'=>$exception,
+			     'output'=>ob_get_clean()));
+  }
+  public static function rpcError($errno,$errstring,$errfile=NULL,$errline=NULL){
+    if(error_reporting()){
+      die(RPC::rpcException($errno,$errstring . " $errfile:$errline"));
+    }
+    return true;
+  }
+
+  public static function rpcSuccess($obj,$baseURL){
+    $success=array();
+    if($obj instanceof RemoteObject){
+      $success['methods']=$obj->getRemoteMethods();
+      $success['uri']=$baseURL.'?'.http_build_query(array_merge($_GET,array('obj_id'=>$obj->save($_SESSION))));
+    }
+    $success['success']=$obj;
+    $success['output']=ob_get_clean();
+
+    return json_encode($success);
+  }
+  public function toJS($obj,$j='window'){
+    return "if(typeof($j) === 'undefined')$j={}; RPC_init($j,".json_encode($this->url).",".json_encode($obj->getRemoteMethods()).");";
+  }
+
+  public function __construct(){
+    $this->url=$_SERVER['PHP_SELF'];
+  }
+
+  public function serve($rootObj){
+    ob_start();
+    session_start();
+    set_error_handler('RPC::rpcError');
+    if(get_magic_quotes_gpc()){
+      $_POST=array_map('stripslashes',$_POST);
+    }
+
+    $rpc=new RPC();
+    $obj=$rootObj;
+
+    $result=null;
+    $rpc->url=$_SERVER['PHP_SELF'];
+
+    if(isset($_GET['obj_id'])){
+      $obj=$_SESSION[$_GET['obj_id']];
+      if(! $obj instanceof RemoteObject){
+	RPC::rpcError(0,"Cannot find object");
+	return;
+      }
+    }
+
+    //return JSON
+    header("Content-Type: application/json");
+
+    if($_SERVER['REQUEST_METHOD']==="GET"){
+      if(isset($_GET['js']) && $_GET['js']){
+	//returns JS
+	header("Content-Type: text/javascript");
+	echo $rpc->toJS($obj,$_GET['js']);
+	return;
+      }
+      $result=$obj->getRemoteMethods();
+    }else{
+      $func=$_POST['f'];
+      $args=json_decode($_POST['a']);
+      try{
+	$result=call_user_func_array(array($obj,$func),$args);
+      }catch(Exception $e){
+	echo RPC::rpcException($e);
+	return;
+      }
+    }
+    echo RPC::rpcSuccess($result,$rpc->url);
+      
+  }
+  
+
+}
+

php/RPCFunctions.php

+<?php
+class RPCFunctions extends RemoteObject {
+  private $funcs;
+  private $files;
+  public function __construct($funcs){
+    $this->remoteMethods=array_keys($funcs);
+    foreach($funcs as $func=>$files){
+      $this->funcs[$func]=$func;
+      $this->files[$func]=$files;
+    }
+  }
+  public function __call($name,$args){
+    if( isset($this->files[$name]) && $this->files[$name] ){
+      include_once $this->files[$name];
+    }
+    return call_user_func_array($this->funcs[$name],$args);
+  }
+
+  //Use the associative array $funcs (functionName => fileName) or (functionName=>anonFunc)
+  //(allows call-time loading of subsets of functions)
+  //$func=$_POST['f'] is the rpc function name to use. 
+  //$file=$rpc[$func] is the file in which the function named $func lives 
+  // (if False, function $func already loaded)
+  //$_POST['a'] is a JSON encoded array to apply to $func
+  //see rpc.js for client-side usage.
+  public static function serveRPC($funcs){
+    $wrapper = new RPCFunctions($funcs);
+    return $wrapper->serve();
+  }
+}

php/RemoteObject.php

+<?php
+abstract class RemoteObject {
+  protected $id = null;
+  protected $remoteMethods=null;
+
+  public function __construct($id=null){
+    if($id===null){
+      $this->id=uniqid(get_class($this).'::',true);
+    }else{
+      $this->id=$id;
+    }
+  }
+
+  public function save(&$store){
+    $store[$this->id]=$this;
+    return $this->id;
+  }
+
+  public function getRemoteMethods(){
+    return $this->remoteMethods;
+  }
+
+  public function serve(){
+    return RPC::serve($this);
+  }
+}
+<?php
+  if(!function_exists('__autoload')){
+    function __autoload($class){
+      include_once dirname(__FILE__).'/'.$class.".php";
+    }
+  }
 <?
-/* 
-
-Copyright (c) 2011, Toby Davies
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-      
-* Redistributions of source code must retain the above copyright
-  notice, this list of conditions and the following disclaimer.
-* Redistributions in binary form must reproduce the above copyright
-  notice, this list of conditions and the following disclaimer in the
-  documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-   
-*/
-
-
 /*
  * JSON RPC Framework Server Side code 
  * 
  * {success:{some:"JSON", encoded:["object"]}
  *
  */
+include_once "../classes/autoload.php";
 
-//handle errors and exceptions such that they can be caught client side.
-function rpcError($errno,$errstring,$errfile=NULL,$errline=NULL){
-  if(error_reporting()){
-    die(json_encode(array('error'=>$errstring." :: $errfile @ $errline")));
-  }
-  return true;
-}
-
-//Use the associative array $rpcs (functionName => fileName) or (functionName=>anonFunc)
-//(allows call-time loading of subsets of functions)
-//$func=$_POST['f'] is the rpc function name to use. 
-//$file=$rpc[$func] is the file in which the function named $func lives 
-// (if False, function $func already loaded)
-//$_POST['a'] is a JSON encoded array to apply to $func
-//see rpc.js for client-side usage.
 function rpcServer($rpcs){
-  header("Content-Type: application/json");
-  set_error_handler('rpcError');
-  try{
-    $f=($postp=($_SERVER['REQUEST_METHOD']=='POST'))?$_POST['f']:'array_keys';
-    $args=($postp?json_decode((get_magic_quotes_gpc())?
-			      stripslashes($_POST['a']):$_POST['a']):array($rpcs));
-    if($postp && !isset($rpcs[$f])) rpcError(-1,"Undefined Function $f");
-    if($postp && ($file=$rpcs[$f]) && is_string($file)) include_once $file; //import necessary file if $f requires it
-    if($postp && !is_string($file) && is_callable($file)) $f=$file; //if $rpcs[$f] is an anonymous function, use that
-    $result=array('success'=>call_user_func_array($f,$args));
-  }catch (Exception $e){
-    $result=array('error'=>$e->getMessage()); // catch and encode any exceptions in the JSON RPC format described above
-  }
-  echo json_encode($result);
+  RPCFunctions::serveRPC($rpcs);
 }