XRL  latest
Simple XML-RPC Library (both client and server)
CapableServer.php
1 <?php
2 /*
3  * This file is part of XRL, a simple XML-RPC Library for PHP.
4  *
5  * Copyright (c) 2012, XRL Team. All rights reserved.
6  * XRL is licensed under the 3-clause BSD License.
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11 
12 namespace fpoirotte\XRL;
13 
30 {
32  protected $server;
33 
35  protected $whitelist;
36 
49  protected function __construct(\fpoirotte\XRL\Server $server, array $whitelist = null)
50  {
51  $this->server = $server;
52  $this->whitelist = $whitelist;
53  }
54 
71  public static function enable(\fpoirotte\XRL\Server $server, array $whitelist = null)
72  {
73  $wrapper = new static($server, $whitelist);
74  $server->expose($wrapper, 'system');
75  return $server;
76  }
77 
92  protected static function extractTypes($doc)
93  {
94  $doc = trim(substr($doc, 3, -2), " \t\r\n*");
95  $doc = str_replace(array("\r\n", "\r"), "\n", $doc);
96  $lines = explode("\n", $doc . "\n");
97 
98  $tag = null;
99  $tags = array(
100  'params' => array(),
101  'retval' => 'null',
102  );
103  $buffer = '';
104 
105  foreach ($lines as $line) {
106  $line = trim($line, " \r\n\t*");
107 
108  if ($tag !== null && $line === '') {
109  switch ($tag) {
110  case 'param':
111  $type = (string) substr($buffer, 0, strcspn($buffer, " \r\n\t"));
112  $buffer = ltrim(substr($buffer, strcspn($buffer, " \r\n\t")));
113  if (strncmp($buffer, '$', 1)) {
114  break;
115  }
116 
117  $name = (string) substr($buffer, 1, strcspn($buffer, " \r\n\t") - 1);
118  $tags['params'][$name] = $type;
119  break;
120 
121  case 'retval':
122  $type = (string) substr($buffer, 0, strcspn($buffer, " \r\n\t"));
123  $tags['retval'] = $type;
124  break;
125  }
126 
127  $tag = null;
128  $buffer = '';
129  continue;
130  }
131 
132  if ($tag === null) {
133  // \command or @command.
134  if (!strncmp($line, '\\', 1) || !strncmp($line, '@', 1)) {
135  $tag = (string) substr($line, 1, strcspn($line, " \r\n\t") - 1);
136  $buffer = ltrim(substr($line, strcspn($line, " \r\n\t")));
137  }
138  } else {
139  // Continuation of previous paragraph.
140  $buffer .= "\n$line";
141  }
142  }
143  return $tags;
144  }
145 
159  protected static function adaptType($type)
160  {
161  switch ($type) {
162  case 'integer':
163  return 'int';
164 
165  case 'float':
166  return 'double';
167 
168  case 'null':
169  return 'nil';
170 
171  case 'DateTime':
172  return 'dateTime.iso8601';
173 
174  case 'boolean':
175  return 'bool';
176 
177  case 'int':
178  case 'double':
179  case 'string':
180  case 'bool':
181  case 'array':
182  return $type;
183  }
184  return null;
185  }
186 
196  public function getCapabilities()
197  {
198  return array(
199  'xmlrpc' => array(
200  'specUrl' => 'http://www.xmlrpc.com/spec',
201  'specVersion' => 1,
202  ),
203 
204  'introspect' => array(
205  'specUrl' => 'http://xmlrpc-c.sourceforge.net/xmlrpc-c/introspection.html',
206  'specVersion' => 1,
207  ),
208 
209  'faults_interop' => array(
210  'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
211  'specVersion' => 20010516,
212  ),
213  );
214  }
215 
222  public function listMethods()
223  {
224  $methods = array_keys($this->server->getIterator()->getArrayCopy());
225  if ($this->whitelist !== null) {
226  $methods = array_values(array_intersect($methods, $this->whitelist));
227  }
228  return $methods;
229  }
230 
242  public function methodSignature($method)
243  {
244  if (!is_string($method) || !isset($this->server[$method])) {
245  throw new \InvalidArgumentException('Invalid method');
246  }
247 
248  $reflector = $this->server[$method]->getReflector();
249  $doc = $reflector->getDocComment();
250  if ($doc === false) {
251  return 'undef';
252  }
253 
254  $tags = static::extractTypes($doc);
255  $returnType = static::adaptType($tags['retval']);
256  if ($returnType === null) {
257  return 'undef';
258  }
259 
260  $params = array();
261  foreach ($reflector->getParameters() as $param) {
262  if (!isset($tags['params'][$param->getName()])) {
263  return 'undef';
264  }
265  $type = static::adaptType($tags['params'][$param->getName()]);
266  if ($type === null) {
267  return 'undef';
268  }
269  $params[] = $type;
270  }
271 
272  return array(array_merge(array($returnType), $params));
273  }
274 
284  public function methodHelp($method)
285  {
286  if (!is_string($method) || !isset($this->server[$method])) {
287  throw new \InvalidArgumentException('Invalid method');
288  }
289 
290  $reflector = $this->server[$method]->getReflector();
291  $doc = $reflector->getDocComment();
292  if ($doc === false) {
293  return '';
294  }
295 
296  // Remove comment delimiters.
297  $doc = substr($doc, 2, -2);
298 
299  // Normalize line endings.
300  $doc = str_replace(array("\r\n", "\r"), "\n", $doc);
301 
302  // Trim leading/trailing whitespace and '*' for every line.
303  $help = array_map(
304  function ($l) {
305  return trim(trim($l), '*');
306  },
307  explode("\n", $doc)
308  );
309 
310  // Count number of empty columns on non-empty lines
311  // before the actual start of the text.
312  $cols = min(
313  array_map(
314  function ($l) {
315  return strspn($l, " \t");
316  },
317  array_filter($help, 'strlen')
318  )
319  );
320 
321  // Remove those columns from the result.
322  $help = array_map(
323  function ($l) use ($cols) {
324  return (string) substr($l, $cols);
325  },
326  $help
327  );
328 
329  // Produce the final output.
330  return implode("\n", $help);
331  }
332 
352  public function multicall(array $requests)
353  {
354  $responses = array();
355  foreach ($requests as $request) {
356  try {
357  if (!is_array($request)) {
358  throw new \BadFunctionCallException('Expected struct');
359  }
360  if (!isset($request['methodName'])) {
361  throw new \BadFunctionCallException('Missing methodName');
362  }
363  if (!isset($request['params'])) {
364  throw new \BadFunctionCallException('Missing params');
365  }
366  if (!is_array($request['params'])) {
367  throw new \BadFunctionCallException('Invalid params');
368  }
369  if ($request['methodName'] === 'system.multicall') {
370  throw new \BadFunctionCallException('Recursive call');
371  }
372 
373  $result = $this->server->call($request['methodName'], $request['params']);
374  // Results are wrapped in an array to make it possible
375  // to distinguish between faults and regular structs.
376  $responses[] = array($result);
377  } catch (\Exception $error) {
378  $responses[] = $error;
379  }
380  }
381  return $responses;
382  }
383 }
__construct(\fpoirotte\XRL\Server $server, array $whitelist=null)
An exception that is used to represent XML-RPC errors.
Definition: Exception.php:21
$whitelist
Whitelist of XML-RPC methods to announce.
A simple XML-RPC server.
Definition: Server.php:65
static enable(\fpoirotte\XRL\Server $server, array $whitelist=null)
A class that adds various capabilities to an existing XML-RPC server.
$server
Original XML-RPC server.