Note: I'm migrating from gonzalo123.com to here. When I finish I'll swap the DNS to here. The "official" blog will be always gonzalo123.com

      Clean way to call to multiple xmlrpc remote servers

      The problem:

      I’ve got a class library in PHP. This class library is distributed in a several servers and I want to call them synchronously. The class library has a XMLRPC interface with Zend Framework XMLRPC server. There is an example class

      class Gam_Dummy
      {
          /**
           * foo
           *
           * @param integer $arg1
           * @param integer $arg2
           * @return integer
           */
          function foo($arg1, $arg2)
          {
              return $arg1 + $arg2;
          }
      }
      

      and the xmlrpc server:

      $class = (string) $_GET['class'];
      $server = new Zend_XmlRpc_Server();
      $server->setClass($class);
      echo $server->handle();
      

      First solution

      An easy a fast solution for calling remote interfaces is:

      $class = "Gam_Dummy";
      $client = new Zend_XmlRpc_Client("http://location/of/xmlrpc/server?class={class}");
      echo $client->call('foo', array($arg1, $arg2));
      
      

      and if we have several remote servers:

      $servers = array(
          'server1' => 'http://location/of/xmlrpc/server1',
          'server2' => 'http://location/of/xmlrpc/server2',
          'server3' => 'http://location/of/xmlrpc/server3'
          );
       
      $class = "Gam_Dummy";
      foreach (array_values($servers) as $_server) {
          $server = "{_server}?class={$class}";
          $client = new Zend_XmlRpc_Client($server);
          echo $client->call('foo', array($arg1, $arg2));
      }
      

      Second solution (one remote server):

      I want to use the following interface to call my remote class

      $class = "Gam_Dummy";
      Gam_Dummy::remote("Gam_Dummy", 'server1')->foo($arg1, $arg2);
      

      Why? The answer is because I like to coding with the help of the IDE. If I use the first solution I must remember Gam_Dummy class has a foo function with two parameters. With the second solution if I place the PHPDoc code correctly my IDE will help me showing me the function list of the Gam_Dummy class and even when I type Gam_ IDE will show me all the classes of my repository starting with Gam_. That issue could sound irrelevant for a lot of people but for me is really useful

      To get this interface I will change my Gam_Dummy class to:

      class Gam_Dummy
      {
          /**
           * foo
           *
           * @param integer $arg1
           * @param integer $arg2
           * @return integer
           */
          function foo($arg1, $arg2)
          {
              return $arg1 + $arg2;
          }
       
          /**
           * Remote interface
           *
           * @param string|array $server
           * @return Gam_Dummy
           */
          static function remote($server)
          {
              return new Remote(get_called_class(), $server);
          }
      }
      

      And of course Remote class:

      class Remote
      {
          private $_class  = null;
          private $_server = null;
       
          function __construct($class, $server)
          {
              $this->_class  = $class;
              $this->_server = $server;
          }
       
          function __call($method, $arguments)
          {
              if (class_exists($this->_class)) {
                  $server = "{$this->_server}?class={$this->_class}";
                  $client = new Zend_XmlRpc_Client($server);
                  return $client->call($method, array($arg1, $arg2));
              }
          }
      }
      

      Cool. Isn’t it?. But there is a problem if I want to work with two or more remote servers I must write one line of code for each server:

      Gam_Dummy::remote('http://location/of/xmlrpc/server1')->foo($arg1, $arg2);
      Gam_Dummy::remote('http://location/of/xmlrpc/server2')->foo($arg1, $arg2);
      Gam_Dummy::remote('http://location/of/xmlrpc/server3')->foo($arg1, $arg2);
      
      

      or may better with the array $servers

      foreach (array_values($servers) as $server) {
          Gam_Dummy::remote($server)->foo($arg1, $arg2);
      }
      

      Third solution for multiple remote servers:

      I would like to use this interface instead of solution two with a foreach for multiple servers:

      $servers = array(
          'server1' => 'http://location/of/xmlrpc/server1',
          'server2' => 'http://location/of/xmlrpc/server2',
          'server3' => 'http://location/of/xmlrpc/server3'
          );
       
      Gam_Dummy::remote($servers)->foo($arg1, $arg2);
      

      so I change Remote class to:

      class Remote
      {
          private $_class  = null;
          private $_server = null;
       
          function __construct($class, $server)
          {
              $this->_class  = $class;
              $this->_server = $server;
          }
       
          function __call($method, $arguments)
          {
              $out = array();
              if (is_array($this->_server)) {
                  foreach ($this->_server as $key => $_server) {
                      $server = "{$_server}?class={$this->_class}";
                      $client = new Zend_XmlRpc_Client($server);
                      $out[$key] = $client->call($method, $arguments);
                  }
              } else {
                  $server = "{$this->_server}?class={$this->_class}";
                  $client = new Zend_XmlRpc_Client($server);
                  $out = $client->call($method, $arguments);
              }
              return $out;
          }
      }
      

      comments powered by Disqus