branch off v1 as R_1_7
[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 {
70 $this->namespace = $namespace;
71 self::initialize();
72 }
73
74 /**
75 * Destructor
76 */
77 public function __destruct()
78 {
79 if (self::$refcnt && !--self::$refcnt) {
80 xmlrpc_server_destroy(self::$xmlrpc);
81 }
82 }
83
84 /**
85 * Static factory
86 *
87 * @param string $namespace
88 * @return XmlRpcServer
89 */
90 public static function factory($namespace)
91 {
92 return new XmlRpcServer($namespace);
93 }
94
95 /**
96 * Run all servers and send response
97 *
98 * @param array $options
99 */
100 public static function run(array $options = null)
101 {
102 self::initialize(false, true);
103 self::setContentType("text/xml; charset=". self::$encoding);
104 echo xmlrpc_server_call_method(self::$xmlrpc, self::$xmlreq, null,
105 array("encoding" => self::$encoding) + (array) $options);
106 }
107
108 /**
109 * Test hook; call instead of XmlRpcServer::run()
110 *
111 * @param string $method
112 * @param array $params
113 * @param array $request_options
114 * @param array $response_options
115 */
116 public static function test($method, array $params, array $request_options = null, array $response_options = null)
117 {
118 self::$xmlreq = xmlrpc_encode_request($method, $params, $request_options);
119 self::run($response_options);
120 }
121
122 /**
123 * Optional XMLRPC error handler
124 *
125 * @param int $code
126 * @param string $msg
127 */
128 public static function error($code, $msg, array $options = null)
129 {
130 echo xmlrpc_encode(array("faultCode" => $code, "faultString" => $msg),
131 array("encoding" => self::$encoding) + (array) $options);
132 }
133
134 /**
135 * Register a single method
136 *
137 * @param string $name
138 * @param mixed $callback
139 * @param mixed $dispatch
140 * @param array $spec
141 */
142 public function registerMethod($name, $callback, $dispatch = null, array $spec = null)
143 {
144 if (!is_callable($callback, false, $cb_name)) {
145 throw new Exception("$cb_name is not a valid callback");
146 }
147 if (isset($dispatch)) {
148 if (!is_callable($dispatch, false, $cb_name)) {
149 throw new Exception("$cb_name is not a valid callback");
150 }
151 xmlrpc_server_register_method(self::$xmlrpc, $name, $dispatch);
152 self::$handle[$name] = $callback;
153 } else {
154 xmlrpc_server_register_method(self::$xmlrpc, $name, $callback);
155 }
156
157 if (isset($spec)) {
158 xmlrpc_server_add_introspection_data(self::$xmlrpc, $spec);
159 }
160 }
161
162 /**
163 * Register an XmlRpcRequestHandler for this server instance
164 *
165 * @param XmlRpcRequestHandler $handler
166 */
167 public function registerHandler(XmlRpcRequestHandler $handler)
168 {
169 $this->handler = $handler;
170
171 foreach (get_class_methods($handler) as $method) {
172 if (!strncmp($method, "xmlrpc", 6)) {
173 $this->registerMethod(
174 $this->method($method, $handler->getNamespace()),
175 array($handler, $method), array($this, "dispatch"));
176 }
177 }
178
179 $handler->getIntrospectionData($spec);
180 if (is_array($spec)) {
181 xmlrpc_server_add_introspection_data(self::$xmlrpc, $spec);
182 }
183 }
184
185 private function method($method, $namespace = null)
186 {
187 if (!strlen($namespace)) {
188 $namespace = strlen($this->namespace) ? $this->namespace : "xmlrpc";
189 }
190 return $namespace .".". strtolower($method[6]) . substr($method, 7);
191 }
192
193 private function dispatch($method, array $params = null)
194 {
195 if (array_key_exists($method, self::$handle)) {
196 return call_user_func(self::$handle[$method], $params);
197 }
198 throw new Exception("Unknown XMLRPC method: $method");
199 }
200
201 private static function initialize($server = true, $data = false)
202 {
203 if ($data) {
204 if (!self::$xmlreq && !(self::$xmlreq = http_get_request_body())) {
205 throw new Exception("Failed to fetch XMLRPC request body");
206 }
207 }
208 if ($server) {
209 if (!self::$xmlrpc && !(self::$xmlrpc = xmlrpc_server_create())) {
210 throw new Exception("Failed to initialize XMLRPC server");
211 }
212 ++self::$refcnt;
213 }
214 }
215 }
216
217 /**
218 * XmlRpcRequestHandler
219 *
220 * Define XMLRPC methods with an "xmlrpc" prefix, eg:
221 * <code>
222 * class IntOp implements XmlRpcRequestHandler {
223 * public function getNamespace() {
224 * return "int";
225 * }
226 * public function getInstrospectionData(array &$spec = null) {
227 * }
228 * // XMLRPC method name: int.sumValues
229 * public function xmlrpcSumValues(array $values) {
230 * return array_sum($values);
231 * }
232 * }
233 * </code>
234 */
235 interface XmlRpcRequestHandler
236 {
237 public function getNamespace();
238 public function getIntrospectionData(array &$spec = null);
239 }
240
241 /**
242 * XmlRpcRequestHandlerStub
243 */
244 abstract class XmlRpcRequestHandlerStub implements XmlRpcRequestHandler
245 {
246 public function getNamespace()
247 {
248 }
249 public function getIntrospectionData(array &$spec = null)
250 {
251 }
252 }
253
254 ?>