PHP RemoteObjects

Czasem potrzebujemy użyć kawałka kodu PHP będącego na innej maszynie. Sięgamy wtedy po xmlrpc czy inne wynalazki takie jak CURL czy zdalne file_get_contents. Na nasze głowy spada wtedy przetworzenie danych przed wysłaniem i po odebraniu. Do tego jedno wywołanie z reguły oznacza całkowite wykonanie kodu po drugiej stronie. A czasem przydałoby się coś na raty zrobić.

Przedstawione poniżej klasy pozwalają na zdefiniowanie serwera obiektów. Obiekt taki można z poziomu klienta utworzyć na serwerze i korzystać potem z niego jak z obiektu lokalnego, wywoływać metody, odwoływać się do pól. Każda taka operacja okupiona jest wywołaniem HTTP do serwera, więc należy tego używać z umiarem.

Oto kod klasy klienta:

class RPC_Client
{
private $server = false;
private $token = false;

function __construct($server=false)
{
if($server)
{
$this->server = $server;
}
$ret=$this->__call('__construct',array());

return $ret;
}
function __call($method,$params)
{
$call['method'] = $method;
$call['params'] = $params;
$call['token'] = $this->token;

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->server);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, array('call'=>serialize($call)));
$ret_string = curl_exec($ch);
if(curl_errno($ch))
{
print curl_error($ch);
}
curl_close($ch);

$ret = unserialize($ret_string);

$this->token = $ret['token'];
if(isset($ret['exception']))
{
throw unserialize($ret['exception']);
}

return $ret['data'];
}
function __set($key,$value)
{
$params['key'] = $key;
$params['value'] = $value;

return $this->__call('__set',$params);
}
function __get($key)
{
$params['key'] = $key;

return $this->__call('__get',$params);
}
}

Kod klasy serwera:

class RPC_Server
{
private $server = false;
private $data = array();

function __construct()
{
$ret=array();

$query_str = trim($_SERVER['QUERY_STRING']);
$query = explode('=',$query_str);

$class = array_shift($query);

$call = unserialize(stripslashes($_POST['call']));

$method = $call['method'];
$params = $call['params'];
$token = $call['token'];

// odkomentowac, jesli nie jest zdefiniowana funkcja __autoload
// if(!class_exists($class))
// {
// require_once('class/'.$class.'.class.php');
// }

$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");

if(!$token)
{
$token = md5(getmypid().time());
$obj = new $class ();
}
else
{
$obj = $memcache->get($token);
}
$ret['token'] = $token;
try
{
$ret['data'] = @call_user_func_array(array(&$obj,$method),$params);
}
catch(Exception $e)
{
$ret['exception'] = serialize($e);
}

$memcache->set($token,$obj);

echo serialize($ret);
}
}

Klasa macierzysta dla klas serwowanych:

class RPC
{
private $data = array();

function __set($key,$value)
{
return $this->data[$key] = $value;
}
function __get($key)
{
return $this->data[$key];
}
}

Przykład klasy serwowanej:

class RPC_Test extends RPC
{
public $aaa = 10;

function __construct()
{
return 'RPC_Test ready';
}
function mul($x,$y)
{
return $x*$y;
}
function except()
{
throw new Exception('Hello!');
}
function spl($string)
{
return explode(' ',$string);
}
}

Oraz serwera:

$srv = new RPC_Server();

I klienta:

$srv = new RPC_Client('http://localhost/~zibi/src/RPCServer.php?RPC_Test');

echo $srv->mul(10,11));
$srv->val = 10;
echo $srv->val;

$srv->val += 20;
echo $srv->val;

$srv->tekst='jaki wiekszy tekscik coby bylo co obrabiac';
echo var_dump($srv->spl($srv->tekst));