a9f7505ed807205e1bfd5db945d09884bee709c6
[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 $options
114 */
115 public static function test($method, array $params, array $options = null)
116 {
117 self::$xmlreq = xmlrpc_encode_request($method, $params);
118 self::run();
119 }
120
121 /**
122 * Optional XMLRPC error handler
123 *
124 * @param int $code
125 * @param string $msg
126 */
127 public static function error($code, $msg)
128 {
129 echo xmlrpc_encode(array("faultCode" => $code, "faultString" => $msg));
130 }
131
132 /**
133 * Register a single method
134 *
135 * @param string $name
136 * @param mixed $callback
137 * @param mixed $dispatch
138 * @param array $spec
139 */
140 public function registerMethod($name, $callback, $dispatch = null, array $spec = null)
141 {
142 if (!is_callable($callback, false, $cb_name)) {
143 throw new Exception("$cb_name is not a valid callback");
144 }
145 if (isset($dispatch)) {
146 if (!is_callable($dispatch, false, $cb_name)) {
147 throw new Exception("$cb_name is not a valid callback");
148 }
149 xmlrpc_server_register_method(self::$xmlrpc, $name, $dispatch);
150 self::$handle[$name] = $callback;
151 } else {
152 xmlrpc_server_register_method(self::$xmlrpc, $name, $callback);
153 }
154
155 if (isset($spec)) {
156 xmlrpc_server_add_introspection_data(self::$xmlrpc, $spec);
157 }
158 }
159
160 /**
161 * Register an XmlRpcRequestHandler for this server instance
162 *
163 * @param XmlRpcRequestHandler $handler
164 */
165 public function registerHandler(XmlRpcRequestHandler $handler)
166 {
167 $this->handler = $handler;
168
169 foreach (get_class_methods($handler) as $method) {
170 if (!strncmp($method, "xmlrpc", 6)) {
171 $this->registerMethod(
172 $this->method($method, $handler->getNamespace()),
173 array($handler, $method), array($this, "dispatch"));
174 }
175 }
176
177 $handler->getIntrospectionData($spec);
178 if (is_array($spec)) {
179 xmlrpc_server_add_introspection_data(self::$xmlrpc, $spec);
180 }
181 }
182
183 private function method($method, $namespace = null)
184 {
185 if (!strlen($namespace)) {
186 $namespace = strlen($this->namespace) ? $this->namespace : "xmlrpc";
187 }
188 return $namespace .".". strtolower($method[6]) . substr($method, 7);
189 }
190
191 private function dispatch($method, array $params = null)
192 {
193 if (array_key_exists($method, self::$handle)) {
194 return call_user_func(self::$handle[$method], $params);
195 }
196 throw new Exception("Unknown XMLRPC method: $method");
197 }
198
199 private static function initialize($server = true, $data = false)
200 {
201 if ($data) {
202 if (!self::$xmlreq && !(self::$xmlreq = http_get_request_body())) {
203 throw new Exception("Failed to fetch XMLRPC request body");
204 }
205 }
206 if ($server) {
207 if (!self::$xmlrpc && !(self::$xmlrpc = xmlrpc_server_create())) {
208 throw new Exception("Failed to initialize XMLRPC server");
209 }
210 ++self::$refcnt;
211 }
212 }
213 }
214
215 /**
216 * XmlRpcRequestHandler
217 *
218 * Define XMLRPC methods with an "xmlrpc" prefix, eg:
219 * <code>
220 * class IntOp implements XmlRpcRequestHandler {
221 * public function getNamespace() {
222 * return "int";
223 * }
224 * public function getInstrospectionData(array &$spec = null) {
225 * }
226 * // XMLRPC method name: int.sumValues
227 * public function xmlrpcSumValues(array $values) {
228 * return array_sum($values);
229 * }
230 * }
231 * </code>
232 */
233 interface XmlRpcRequestHandler
234 {
235 public function getNamespace();
236 public function getIntrospectionData(array &$spec = null);
237 }
238
239 /**
240 * XmlRpcRequestHandlerStub
241 */
242 abstract class XmlRpcRequestHandlerStub implements XmlRpcRequestHandler
243 {
244 public function getNamespace()
245 {
246 }
247 public function getIntrospectionData(array &$spec = null)
248 {
249 }
250 }
251
252 ?>