Server.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Rest
  17. * @subpackage Server
  18. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. */
  21. /**
  22. * Zend_Server_Interface
  23. */
  24. require_once 'Zend/Server/Interface.php';
  25. /**
  26. * Zend_Server_Reflection
  27. */
  28. require_once 'Zend/Server/Reflection.php';
  29. /**
  30. * Zend_Server_Abstract
  31. */
  32. require_once 'Zend/Server/Abstract.php';
  33. /**
  34. * @category Zend
  35. * @package Zend_Rest
  36. * @subpackage Server
  37. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  38. * @license http://framework.zend.com/license/new-bsd New BSD License
  39. */
  40. class Zend_Rest_Server implements Zend_Server_Interface
  41. {
  42. /**
  43. * Class Constructor Args
  44. * @var array
  45. */
  46. protected $_args = array();
  47. /**
  48. * @var string Encoding
  49. */
  50. protected $_encoding = 'UTF-8';
  51. /**
  52. * @var array An array of Zend_Server_Reflect_Method
  53. */
  54. protected $_functions = array();
  55. /**
  56. * @var array Array of headers to send
  57. */
  58. protected $_headers = array();
  59. /**
  60. * @var array PHP's Magic Methods, these are ignored
  61. */
  62. protected static $magicMethods = array(
  63. '__construct',
  64. '__destruct',
  65. '__get',
  66. '__set',
  67. '__call',
  68. '__sleep',
  69. '__wakeup',
  70. '__isset',
  71. '__unset',
  72. '__tostring',
  73. '__clone',
  74. '__set_state',
  75. );
  76. /**
  77. * @var string Current Method
  78. */
  79. protected $_method;
  80. /**
  81. * @var Zend_Server_Reflection
  82. */
  83. protected $_reflection = null;
  84. /**
  85. * Whether or not {@link handle()} should send output or return the response.
  86. * @var boolean Defaults to false
  87. */
  88. protected $_returnResponse = false;
  89. /**
  90. * Constructor
  91. */
  92. public function __construct()
  93. {
  94. set_exception_handler(array($this, "fault"));
  95. $this->_reflection = new Zend_Server_Reflection();
  96. }
  97. /**
  98. * Set XML encoding
  99. *
  100. * @param string $encoding
  101. * @return Zend_Rest_Server
  102. */
  103. public function setEncoding($encoding)
  104. {
  105. $this->_encoding = (string) $encoding;
  106. return $this;
  107. }
  108. /**
  109. * Get XML encoding
  110. *
  111. * @return string
  112. */
  113. public function getEncoding()
  114. {
  115. return $this->_encoding;
  116. }
  117. /**
  118. * Lowercase a string
  119. *
  120. * Lowercase's a string by reference
  121. *
  122. * @param string $value
  123. * @param string $key
  124. * @return string Lower cased string
  125. */
  126. public static function lowerCase(&$value, &$key)
  127. {
  128. return $value = strtolower($value);
  129. }
  130. /**
  131. * Whether or not to return a response
  132. *
  133. * If called without arguments, returns the value of the flag. If called
  134. * with an argument, sets the flag.
  135. *
  136. * When 'return response' is true, {@link handle()} will not send output,
  137. * but will instead return the response from the dispatched function/method.
  138. *
  139. * @param boolean $flag
  140. * @return boolean|Zend_Rest_Server Returns Zend_Rest_Server when used to set the flag; returns boolean flag value otherwise.
  141. */
  142. public function returnResponse($flag = null)
  143. {
  144. if (null === $flag) {
  145. return $this->_returnResponse;
  146. }
  147. $this->_returnResponse = ($flag) ? true : false;
  148. return $this;
  149. }
  150. /**
  151. * Implement Zend_Server_Interface::handle()
  152. *
  153. * @param array $request
  154. * @throws Zend_Rest_Server_Exception
  155. * @return string|void
  156. */
  157. public function handle($request = false)
  158. {
  159. $this->_headers = array('Content-Type: text/xml');
  160. if (!$request) {
  161. $request = $_REQUEST;
  162. }
  163. if (isset($request['method'])) {
  164. $this->_method = $request['method'];
  165. if (isset($this->_functions[$this->_method])) {
  166. if ($this->_functions[$this->_method] instanceof Zend_Server_Reflection_Function || $this->_functions[$this->_method] instanceof Zend_Server_Reflection_Method && $this->_functions[$this->_method]->isPublic()) {
  167. $request_keys = array_keys($request);
  168. array_walk($request_keys, array(__CLASS__, "lowerCase"));
  169. $request = array_combine($request_keys, $request);
  170. $func_args = $this->_functions[$this->_method]->getParameters();
  171. $calling_args = array();
  172. foreach ($func_args as $arg) {
  173. if (isset($request[strtolower($arg->getName())])) {
  174. $calling_args[] = $request[strtolower($arg->getName())];
  175. } elseif ($arg->isOptional()) {
  176. $calling_args[] = $arg->getDefaultValue();
  177. }
  178. }
  179. foreach ($request as $key => $value) {
  180. if (substr($key, 0, 3) == 'arg') {
  181. $key = str_replace('arg', '', $key);
  182. $calling_args[$key] = $value;
  183. }
  184. }
  185. // Sort arguments by key -- @see ZF-2279
  186. ksort($calling_args);
  187. $result = false;
  188. if (count($calling_args) < count($func_args)) {
  189. require_once 'Zend/Rest/Server/Exception.php';
  190. $result = $this->fault(new Zend_Rest_Server_Exception('Invalid Method Call to ' . $this->_method . '. Requires ' . count($func_args) . ', ' . count($calling_args) . ' given.'), 400);
  191. }
  192. if (!$result && $this->_functions[$this->_method] instanceof Zend_Server_Reflection_Method) {
  193. // Get class
  194. $class = $this->_functions[$this->_method]->getDeclaringClass()->getName();
  195. if ($this->_functions[$this->_method]->isStatic()) {
  196. // for some reason, invokeArgs() does not work the same as
  197. // invoke(), and expects the first argument to be an object.
  198. // So, using a callback if the method is static.
  199. $result = $this->_callStaticMethod($class, $calling_args);
  200. } else {
  201. // Object method
  202. $result = $this->_callObjectMethod($class, $calling_args);
  203. }
  204. } elseif (!$result) {
  205. try {
  206. $result = call_user_func_array($this->_functions[$this->_method]->getName(), $calling_args); //$this->_functions[$this->_method]->invokeArgs($calling_args);
  207. } catch (Exception $e) {
  208. $result = $this->fault($e);
  209. }
  210. }
  211. } else {
  212. require_once "Zend/Rest/Server/Exception.php";
  213. $result = $this->fault(
  214. new Zend_Rest_Server_Exception("Unknown Method '$this->_method'."),
  215. 404
  216. );
  217. }
  218. } else {
  219. require_once "Zend/Rest/Server/Exception.php";
  220. $result = $this->fault(
  221. new Zend_Rest_Server_Exception("Unknown Method '$this->_method'."),
  222. 404
  223. );
  224. }
  225. } else {
  226. require_once "Zend/Rest/Server/Exception.php";
  227. $result = $this->fault(
  228. new Zend_Rest_Server_Exception("No Method Specified."),
  229. 404
  230. );
  231. }
  232. if ($result instanceof SimpleXMLElement) {
  233. $response = $result->asXML();
  234. } elseif ($result instanceof DOMDocument) {
  235. $response = $result->saveXML();
  236. } elseif ($result instanceof DOMNode) {
  237. $response = $result->ownerDocument->saveXML($result);
  238. } elseif (is_array($result) || is_object($result)) {
  239. $response = $this->_handleStruct($result);
  240. } else {
  241. $response = $this->_handleScalar($result);
  242. }
  243. if (!$this->returnResponse()) {
  244. if (!headers_sent()) {
  245. foreach ($this->_headers as $header) {
  246. header($header);
  247. }
  248. }
  249. echo $response;
  250. return;
  251. }
  252. return $response;
  253. }
  254. /**
  255. * Implement Zend_Server_Interface::setClass()
  256. *
  257. * @param string $classname Class name
  258. * @param string $namespace Class namespace (unused)
  259. * @param array $argv An array of Constructor Arguments
  260. */
  261. public function setClass($classname, $namespace = '', $argv = array())
  262. {
  263. $this->_args = $argv;
  264. foreach ($this->_reflection->reflectClass($classname, $argv)->getMethods() as $method) {
  265. $this->_functions[$method->getName()] = $method;
  266. }
  267. }
  268. /**
  269. * Handle an array or object result
  270. *
  271. * @param array|object $struct Result Value
  272. * @return string XML Response
  273. */
  274. protected function _handleStruct($struct)
  275. {
  276. $function = $this->_functions[$this->_method];
  277. if ($function instanceof Zend_Server_Reflection_Method) {
  278. $class = $function->getDeclaringClass()->getName();
  279. } else {
  280. $class = false;
  281. }
  282. $method = $function->getName();
  283. $dom = new DOMDocument('1.0', $this->getEncoding());
  284. if ($class) {
  285. $root = $dom->createElement($class);
  286. $method = $dom->createElement($method);
  287. $root->appendChild($method);
  288. } else {
  289. $root = $dom->createElement($method);
  290. $method = $root;
  291. }
  292. $root->setAttribute('generator', 'zend');
  293. $root->setAttribute('version', '1.0');
  294. $dom->appendChild($root);
  295. $this->_structValue($struct, $dom, $method);
  296. $struct = (array) $struct;
  297. if (!isset($struct['status'])) {
  298. $status = $dom->createElement('status', 'success');
  299. $method->appendChild($status);
  300. }
  301. return $dom->saveXML();
  302. }
  303. /**
  304. * Recursively iterate through a struct
  305. *
  306. * Recursively iterates through an associative array or object's properties
  307. * to build XML response.
  308. *
  309. * @param mixed $struct
  310. * @param DOMDocument $dom
  311. * @param DOMElement $parent
  312. * @return void
  313. */
  314. protected function _structValue($struct, DOMDocument $dom, DOMElement $parent)
  315. {
  316. $struct = (array) $struct;
  317. foreach ($struct as $key => $value) {
  318. if ($value === false) {
  319. $value = 0;
  320. } elseif ($value === true) {
  321. $value = 1;
  322. }
  323. if (ctype_digit((string) $key)) {
  324. $key = 'key_' . $key;
  325. }
  326. if (is_array($value) || is_object($value)) {
  327. $element = $dom->createElement($key);
  328. $this->_structValue($value, $dom, $element);
  329. } else {
  330. $element = $dom->createElement($key);
  331. $element->appendChild($dom->createTextNode($value));
  332. }
  333. $parent->appendChild($element);
  334. }
  335. }
  336. /**
  337. * Handle a single value
  338. *
  339. * @param string|int|boolean $value Result value
  340. * @return string XML Response
  341. */
  342. protected function _handleScalar($value)
  343. {
  344. $function = $this->_functions[$this->_method];
  345. if ($function instanceof Zend_Server_Reflection_Method) {
  346. $class = $function->getDeclaringClass()->getName();
  347. } else {
  348. $class = false;
  349. }
  350. $method = $function->getName();
  351. $dom = new DOMDocument('1.0', $this->getEncoding());
  352. if ($class) {
  353. $xml = $dom->createElement($class);
  354. $methodNode = $dom->createElement($method);
  355. $xml->appendChild($methodNode);
  356. } else {
  357. $xml = $dom->createElement($method);
  358. $methodNode = $xml;
  359. }
  360. $xml->setAttribute('generator', 'zend');
  361. $xml->setAttribute('version', '1.0');
  362. $dom->appendChild($xml);
  363. if ($value === false) {
  364. $value = 0;
  365. } elseif ($value === true) {
  366. $value = 1;
  367. }
  368. if (isset($value)) {
  369. $element = $dom->createElement('response');
  370. $element->appendChild($dom->createTextNode($value));
  371. $methodNode->appendChild($element);
  372. } else {
  373. $methodNode->appendChild($dom->createElement('response'));
  374. }
  375. $methodNode->appendChild($dom->createElement('status', 'success'));
  376. return $dom->saveXML();
  377. }
  378. /**
  379. * Implement Zend_Server_Interface::fault()
  380. *
  381. * Creates XML error response, returning DOMDocument with response.
  382. *
  383. * @param string|Exception $fault Message
  384. * @param int $code Error Code
  385. * @return DOMDocument
  386. */
  387. public function fault($exception = null, $code = null)
  388. {
  389. if (isset($this->_functions[$this->_method])) {
  390. $function = $this->_functions[$this->_method];
  391. } elseif (isset($this->_method)) {
  392. $function = $this->_method;
  393. } else {
  394. $function = 'rest';
  395. }
  396. if ($function instanceof Zend_Server_Reflection_Method) {
  397. $class = $function->getDeclaringClass()->getName();
  398. } else {
  399. $class = false;
  400. }
  401. if ($function instanceof Zend_Server_Reflection_Function_Abstract) {
  402. $method = $function->getName();
  403. } else {
  404. $method = $function;
  405. }
  406. $dom = new DOMDocument('1.0', $this->getEncoding());
  407. if ($class) {
  408. $xml = $dom->createElement($class);
  409. $xmlMethod = $dom->createElement($method);
  410. $xml->appendChild($xmlMethod);
  411. } else {
  412. $xml = $dom->createElement($method);
  413. $xmlMethod = $xml;
  414. }
  415. $xml->setAttribute('generator', 'zend');
  416. $xml->setAttribute('version', '1.0');
  417. $dom->appendChild($xml);
  418. $xmlResponse = $dom->createElement('response');
  419. $xmlMethod->appendChild($xmlResponse);
  420. if ($exception instanceof Exception) {
  421. $element = $dom->createElement('message');
  422. $element->appendChild($dom->createTextNode($exception->getMessage()));
  423. $xmlResponse->appendChild($element);
  424. $code = $exception->getCode();
  425. } elseif (($exception !== null) || 'rest' == $function) {
  426. $xmlResponse->appendChild($dom->createElement('message', 'An unknown error occured. Please try again.'));
  427. } else {
  428. $xmlResponse->appendChild($dom->createElement('message', 'Call to ' . $method . ' failed.'));
  429. }
  430. $xmlMethod->appendChild($xmlResponse);
  431. $xmlMethod->appendChild($dom->createElement('status', 'failed'));
  432. // Headers to send
  433. if ($code === null || (404 != $code)) {
  434. $this->_headers[] = 'HTTP/1.0 400 Bad Request';
  435. } else {
  436. $this->_headers[] = 'HTTP/1.0 404 File Not Found';
  437. }
  438. return $dom;
  439. }
  440. /**
  441. * Retrieve any HTTP extra headers set by the server
  442. *
  443. * @return array
  444. */
  445. public function getHeaders()
  446. {
  447. return $this->_headers;
  448. }
  449. /**
  450. * Implement Zend_Server_Interface::addFunction()
  451. *
  452. * @param string $function Function Name
  453. * @param string $namespace Function namespace (unused)
  454. */
  455. public function addFunction($function, $namespace = '')
  456. {
  457. if (!is_array($function)) {
  458. $function = (array) $function;
  459. }
  460. foreach ($function as $func) {
  461. if (is_callable($func) && !in_array($func, self::$magicMethods)) {
  462. $this->_functions[$func] = $this->_reflection->reflectFunction($func);
  463. } else {
  464. require_once 'Zend/Rest/Server/Exception.php';
  465. throw new Zend_Rest_Server_Exception("Invalid Method Added to Service.");
  466. }
  467. }
  468. }
  469. /**
  470. * Implement Zend_Server_Interface::getFunctions()
  471. *
  472. * @return array An array of Zend_Server_Reflection_Method's
  473. */
  474. public function getFunctions()
  475. {
  476. return $this->_functions;
  477. }
  478. /**
  479. * Implement Zend_Server_Interface::loadFunctions()
  480. *
  481. * @todo Implement
  482. * @param array $functions
  483. */
  484. public function loadFunctions($functions)
  485. {
  486. }
  487. /**
  488. * Implement Zend_Server_Interface::setPersistence()
  489. *
  490. * @todo Implement
  491. * @param int $mode
  492. */
  493. public function setPersistence($mode)
  494. {
  495. }
  496. /**
  497. * Call a static class method and return the result
  498. *
  499. * @param string $class
  500. * @param array $args
  501. * @return mixed
  502. */
  503. protected function _callStaticMethod($class, array $args)
  504. {
  505. try {
  506. $result = call_user_func_array(array($class, $this->_functions[$this->_method]->getName()), $args);
  507. } catch (Exception $e) {
  508. $result = $this->fault($e);
  509. }
  510. return $result;
  511. }
  512. /**
  513. * Call an instance method of an object
  514. *
  515. * @param string $class
  516. * @param array $args
  517. * @return mixed
  518. * @throws Zend_Rest_Server_Exception For invalid class name
  519. */
  520. protected function _callObjectMethod($class, array $args)
  521. {
  522. try {
  523. if ($this->_functions[$this->_method]->getDeclaringClass()->getConstructor()) {
  524. $object = $this->_functions[$this->_method]->getDeclaringClass()->newInstanceArgs($this->_args);
  525. } else {
  526. $object = $this->_functions[$this->_method]->getDeclaringClass()->newInstance();
  527. }
  528. } catch (Exception $e) {
  529. echo $e->getMessage();
  530. require_once 'Zend/Rest/Server/Exception.php';
  531. throw new Zend_Rest_Server_Exception('Error instantiating class ' . $class . ' to invoke method ' . $this->_functions[$this->_method]->getName(), 500);
  532. }
  533. try {
  534. $result = $this->_functions[$this->_method]->invokeArgs($object, $args);
  535. } catch (Exception $e) {
  536. $result = $this->fault($e);
  537. }
  538. return $result;
  539. }
  540. }