- allow to avoid deps on shared extensions on build time
[m6w6/ext-http] / lib / XmlRpcServer.php
1 <?php
2
3 XmlRpcServer::setContentType("text/xml");
4 XmlRpcServer::capture();
5
6 /**
7 * XMLRPC Server, very KISS
8 * $Id$
9 *
10 * NOTE: requires ext/xmlrpc
11 *
12 * Usage:
13 * <code>
14 * <?php
15 * class Handler extends XmlRpcRequestHandlerStub {
16 * public function xmlrpcPing(array $values) {
17 * return true;
18 * }
19 * }
20 * try {
21 * XmlRpcServer::factory("namespace")->registerHandler(new Handler);
22 * XmlRpcServer::run();
23 * } catch (Exception $ex) {
24 * XmlRpcServer::error($ex->getCode(), $ex->getMessage());
25 * }
26 * </code>
27 *
28 * @copyright Michael Wallner, <mike@iworks.at>
29 * @license BSD, revised
30 * @package pecl/http
31 * @version $Revision$
32 */
33
34 class XmlRpcServer extends HttpResponse
35 {
36 /**
37 * Server charset
38 *
39 * @var string
40 */
41 public static $encoding = "iso-8859-1";
42
43 /**
44 * RPC namespace
45 *
46 * @var string
47 */
48 public $namespace;
49
50 /**
51 * RPC handler attached to this server instance
52 *
53 * @var XmlRpcRequestHandler
54 */
55 protected $handler;
56
57 private static $xmlreq;
58 private static $xmlrpc;
59 private static $refcnt = 0;
60 private static $handle = array();
61
62 /**
63 * Create a new XmlRpcServer instance
64 *
65 * @param string $namespace
66 * @param string $encoding
67 */
68 public function __construct($namespace) {
69 $this->namespace = $namespace;
70 self::initialize();
71 }
72
73 /**
74 * Destructor
75 */
76 public function __destruct() {
77 if (self::$refcnt && !--self::$refcnt) {
78 xmlrpc_server_destroy(self::$xmlrpc);
79 }
80 }
81
82 /**
83 * Static factory
84 *
85 * @param string $namespace
86 * @return XmlRpcServer
87 */
88 public static function factory($namespace) {
89 return new XmlRpcServer($namespace);
90 }
91
92 /**
93 * Run all servers and send response
94 *
95 * @param array $options
96 */
97 public static function run(array $options = null) {
98 self::initialize(false, true);
99 HttpResponse::setContentType("text/xml; charset=". self::$encoding);
100 echo xmlrpc_server_call_method(self::$xmlrpc, self::$xmlreq, null,
101 array("encoding" => self::$encoding) + (array) $options);
102 }
103
104 /**
105 * Test hook; call instead of XmlRpcServer::run()
106 *
107 * @param string $method
108 * @param array $params
109 * @param array $options
110 */
111 public static function test($method, array $params, array $options = null) {
112 self::$xmlreq = xmlrpc_encode_request($method, $params);
113 self::run();
114 }
115
116 /**
117 * Optional XMLRPC error handler
118 *
119 * @param int $code
120 * @param string $msg
121 */
122 public static function error($code, $msg) {
123 echo xmlrpc_encode(array("faultCode" => $code, "faultString" => $msg));
124 }
125
126 /**
127 * Register a single method
128 *
129 * @param string $name
130 * @param mixed $callback
131 * @param mixed $dispatch
132 * @param array $spec
133 */
134 public function registerMethod($name, $callback, $dispatch = null, array $spec = null) {
135 if (!is_callable($callback, false, $cb_name)) {
136 throw new Exception("$cb_name is not a valid callback");
137 }
138 if (isset($dispatch)) {
139 if (!is_callable($dispatch, false, $cb_name)) {
140 throw new Exception("$cb_name is not a valid callback");
141 }
142 xmlrpc_server_register_method(self::$xmlrpc, $name, $dispatch);
143 self::$handle[$name] = $callback;
144 } else {
145 xmlrpc_server_register_method(self::$xmlrpc, $name, $callback);
146 }
147
148 if (isset($spec)) {
149 xmlrpc_server_add_introspection_data(self::$xmlrpc, $spec);
150 }
151 }
152
153 /**
154 * Register an XmlRpcRequestHandler for this server instance
155 *
156 * @param XmlRpcRequestHandler $handler
157 */
158 public function registerHandler(XmlRpcRequestHandler $handler) {
159 $this->handler = $handler;
160
161 foreach (get_class_methods($handler) as $method) {
162 if (!strncmp($method, "xmlrpc", 6)) {
163 $this->registerMethod(
164 $this->method($method, $handler->getNamespace()),
165 array($handler, $method), array($this, "dispatch"));
166 }
167 }
168
169 $handler->getIntrospectionData($spec);
170 if (is_array($spec)) {
171 xmlrpc_server_add_introspection_data(self::$xmlrpc, $spec);
172 }
173 }
174
175 private function method($method, $namespace = null) {
176 if (!strlen($namespace)) {
177 $namespace = strlen($this->namespace) ? $this->namespace : "xmlrpc";
178 }
179 return $namespace .".". strtolower($method[6]) . substr($method, 7);
180 }
181
182 private function dispatch($method, array $params = null) {
183 if (array_key_exists($method, self::$handle)) {
184 return call_user_func(self::$handle[$method], $params);
185 }
186 throw new Exception("Unknown XMLRPC method: $method");
187 }
188
189 private static function initialize($server = true, $data = false) {
190 if ($data) {
191 if (!self::$xmlreq && !(self::$xmlreq = http_get_request_body())) {
192 throw new Exception("Failed to fetch XMLRPC request body");
193 }
194 }
195 if ($server) {
196 if (!self::$xmlrpc && !(self::$xmlrpc = xmlrpc_server_create())) {
197 throw new Exception("Failed to initialize XMLRPC server");
198 }
199 ++self::$refcnt;
200 }
201 }
202 }
203
204 /**
205 * XmlRpcRequestHandler
206 *
207 * Define XMLRPC methods with an "xmlrpc" prefix, eg:
208 * <code>
209 * class IntOp implements XmlRpcRequestHandler {
210 * public function getNamespace() {
211 * return "int";
212 * }
213 * public function getInstrospectionData(array &$spec = null) {
214 * }
215 * // XMLRPC method name: int.sumValues
216 * public function xmlrpcSumValues(array $values) {
217 * return array_sum($values);
218 * }
219 * }
220 * </code>
221 */
222 interface XmlRpcRequestHandler {
223 public function getNamespace();
224 public function getIntrospectionData(array &$spec = null);
225 }
226
227 /**
228 * XmlRpcRequestHandlerStub
229 */
230 abstract class XmlRpcRequestHandlerStub implements XmlRpcRequestHandler {
231 public function getNamespace() {
232 }
233 public function getIntrospectionData(array &$spec = null) {
234 }
235 }
236
237 ?>