- allow to avoid deps on shared extensions on build time
[m6w6/ext-http] / lib / XmlRpcServer.php
diff --git a/lib/XmlRpcServer.php b/lib/XmlRpcServer.php
new file mode 100644 (file)
index 0000000..e77910f
--- /dev/null
@@ -0,0 +1,237 @@
+<?php
+
+XmlRpcServer::setContentType("text/xml");
+XmlRpcServer::capture();
+
+/**
+ * XMLRPC Server, very KISS
+ * $Id$
+ * 
+ * NOTE: requires ext/xmlrpc
+ * 
+ * Usage:
+ * <code>
+ * <?php
+ *     class Handler extends XmlRpcRequestHandlerStub {
+ *             public function xmlrpcPing(array $values) {
+ *                     return true;
+ *             }
+ *     }
+ *     try {
+ *             XmlRpcServer::factory("namespace")->registerHandler(new Handler);
+ *             XmlRpcServer::run();
+ *     } catch (Exception $ex) {
+ *             XmlRpcServer::error($ex->getCode(), $ex->getMessage());
+ *  }
+ * </code>
+ * 
+ * @copyright   Michael Wallner, <mike@iworks.at>
+ * @license     BSD, revised
+ * @package     pecl/http
+ * @version     $Revision$
+ */
+
+class XmlRpcServer extends HttpResponse
+{
+       /**
+        * Server charset
+        *
+        * @var string
+        */
+       public static $encoding = "iso-8859-1";
+       
+       /**
+        * RPC namespace
+        *
+        * @var string
+        */
+       public $namespace;
+       
+       /**
+        * RPC handler attached to this server instance
+        *
+        * @var XmlRpcRequestHandler
+        */
+       protected $handler;
+       
+       private static $xmlreq;
+       private static $xmlrpc;
+       private static $refcnt = 0;
+       private static $handle = array();
+       
+       /**
+        * Create a new XmlRpcServer instance
+        *
+        * @param string $namespace
+        * @param string $encoding
+        */
+       public function __construct($namespace) {
+               $this->namespace = $namespace;
+               self::initialize();
+       }
+       
+       /**
+        * Destructor
+        */
+       public function __destruct() {
+               if (self::$refcnt && !--self::$refcnt) {
+                       xmlrpc_server_destroy(self::$xmlrpc);
+               }
+       }
+       
+       /**
+        * Static factory
+        *
+        * @param string $namespace
+        * @return XmlRpcServer
+        */
+       public static function factory($namespace) {
+               return new XmlRpcServer($namespace);
+       }
+       
+       /**
+        * Run all servers and send response
+        *
+        * @param array $options
+        */
+       public static function run(array $options = null) {
+               self::initialize(false, true);
+               HttpResponse::setContentType("text/xml; charset=". self::$encoding);
+               echo xmlrpc_server_call_method(self::$xmlrpc, self::$xmlreq, null, 
+                       array("encoding" => self::$encoding) + (array) $options);
+       }
+       
+       /**
+        * Test hook; call instead of XmlRpcServer::run()
+        *
+        * @param string $method
+        * @param array $params
+        * @param array $options
+        */
+       public static function test($method, array $params, array $options = null) {
+               self::$xmlreq = xmlrpc_encode_request($method, $params);
+               self::run();
+       }
+       
+       /**
+        * Optional XMLRPC error handler
+        *
+        * @param int $code
+        * @param string $msg
+        */
+       public static function error($code, $msg) {
+               echo xmlrpc_encode(array("faultCode" => $code, "faultString" => $msg));
+       }
+       
+       /**
+        * Register a single method
+        *
+        * @param string $name
+        * @param mixed $callback
+        * @param mixed $dispatch
+        * @param array $spec
+        */
+       public function registerMethod($name, $callback, $dispatch = null, array $spec = null) {
+               if (!is_callable($callback, false, $cb_name)) {
+                       throw new Exception("$cb_name is not a valid callback");
+               }
+               if (isset($dispatch)) {
+                       if (!is_callable($dispatch, false, $cb_name)) {
+                               throw new Exception("$cb_name is not a valid callback");
+                       }
+                       xmlrpc_server_register_method(self::$xmlrpc, $name, $dispatch);
+                       self::$handle[$name] = $callback;
+               } else {
+                       xmlrpc_server_register_method(self::$xmlrpc, $name, $callback);
+               }
+               
+               if (isset($spec)) {
+                       xmlrpc_server_add_introspection_data(self::$xmlrpc, $spec);
+               }
+       }
+       
+       /**
+        * Register an XmlRpcRequestHandler for this server instance
+        *
+        * @param XmlRpcRequestHandler $handler
+        */
+       public function registerHandler(XmlRpcRequestHandler $handler) {
+               $this->handler = $handler;
+               
+               foreach (get_class_methods($handler) as $method) {
+                       if (!strncmp($method, "xmlrpc", 6)) {
+                               $this->registerMethod(
+                                       $this->method($method, $handler->getNamespace()), 
+                                       array($handler, $method), array($this, "dispatch"));
+                       }
+               }
+               
+               $handler->getIntrospectionData($spec);
+               if (is_array($spec)) {
+                       xmlrpc_server_add_introspection_data(self::$xmlrpc, $spec);
+               }
+       }
+       
+       private function method($method, $namespace = null) {
+               if (!strlen($namespace)) {
+                       $namespace = strlen($this->namespace) ? $this->namespace : "xmlrpc";
+               }
+               return $namespace .".". strtolower($method[6]) . substr($method, 7);
+       }
+       
+       private function dispatch($method, array $params = null) {
+               if (array_key_exists($method, self::$handle)) {
+                       return call_user_func(self::$handle[$method], $params);
+               }
+               throw new Exception("Unknown XMLRPC method: $method");
+       }
+       
+       private static function initialize($server = true, $data = false) {
+               if ($data) {
+                       if (!self::$xmlreq && !(self::$xmlreq = http_get_request_body())) {
+                               throw new Exception("Failed to fetch XMLRPC request body");
+                       }
+               }
+               if ($server) {
+                       if (!self::$xmlrpc && !(self::$xmlrpc = xmlrpc_server_create())) {
+                               throw new Exception("Failed to initialize XMLRPC server");
+                       }
+                       ++self::$refcnt;
+               }
+       }
+}
+
+/**
+ * XmlRpcRequestHandler
+ * 
+ * Define XMLRPC methods with an "xmlrpc" prefix, eg:
+ * <code>
+ *     class IntOp implements XmlRpcRequestHandler {
+ *             public function getNamespace() {
+ *                     return "int";
+ *             }
+ *             public function getInstrospectionData(array &$spec = null) {
+ *             }
+ *             // XMLRPC method name: int.sumValues
+ *             public function xmlrpcSumValues(array $values) {
+ *                      return array_sum($values);
+ *             }
+ *     }
+ * </code>
+ */
+interface XmlRpcRequestHandler {
+       public function getNamespace();
+       public function getIntrospectionData(array &$spec = null);
+}
+
+/**
+ * XmlRpcRequestHandlerStub
+ */
+abstract class XmlRpcRequestHandlerStub implements XmlRpcRequestHandler {
+       public function getNamespace() {
+       }
+       public function getIntrospectionData(array &$spec = null) {
+       }
+}
+
+?>