File indexing completed on 2024-12-22 05:36:58
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Rest 0017 * @subpackage Server 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 /** 0024 * @see Zend_Server_Interface 0025 */ 0026 // require_once 'Zend/Server/Interface.php'; 0027 0028 /** 0029 * @see Zend_Server_Reflection 0030 */ 0031 // require_once 'Zend/Server/Reflection.php'; 0032 0033 /** 0034 * @see Zend_Server_Abstract 0035 */ 0036 // require_once 'Zend/Server/Abstract.php'; 0037 0038 /** 0039 * @category Zend 0040 * @package Zend_Rest 0041 * @subpackage Server 0042 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0043 * @license http://framework.zend.com/license/new-bsd New BSD License 0044 */ 0045 class Zend_Rest_Server implements Zend_Server_Interface 0046 { 0047 /** 0048 * Class Constructor Args 0049 * @var array 0050 */ 0051 protected $_args = array(); 0052 0053 /** 0054 * @var string Encoding 0055 */ 0056 protected $_encoding = 'UTF-8'; 0057 0058 /** 0059 * @var array An array of Zend_Server_Reflect_Method 0060 */ 0061 protected $_functions = array(); 0062 0063 /** 0064 * @var array Array of headers to send 0065 */ 0066 protected $_headers = array(); 0067 0068 /** 0069 * @var array PHP's Magic Methods, these are ignored 0070 */ 0071 protected static $magicMethods = array( 0072 '__construct', 0073 '__destruct', 0074 '__get', 0075 '__set', 0076 '__call', 0077 '__sleep', 0078 '__wakeup', 0079 '__isset', 0080 '__unset', 0081 '__tostring', 0082 '__clone', 0083 '__set_state', 0084 ); 0085 0086 /** 0087 * @var string Current Method 0088 */ 0089 protected $_method; 0090 0091 /** 0092 * @var Zend_Server_Reflection 0093 */ 0094 protected $_reflection = null; 0095 0096 /** 0097 * Whether or not {@link handle()} should send output or return the response. 0098 * @var boolean Defaults to false 0099 */ 0100 protected $_returnResponse = false; 0101 0102 /** 0103 * Constructor 0104 */ 0105 public function __construct() 0106 { 0107 set_exception_handler(array($this, "fault")); 0108 $this->_reflection = new Zend_Server_Reflection(); 0109 } 0110 0111 /** 0112 * Set XML encoding 0113 * 0114 * @param string $encoding 0115 * @return Zend_Rest_Server 0116 */ 0117 public function setEncoding($encoding) 0118 { 0119 $this->_encoding = (string) $encoding; 0120 return $this; 0121 } 0122 0123 /** 0124 * Get XML encoding 0125 * 0126 * @return string 0127 */ 0128 public function getEncoding() 0129 { 0130 return $this->_encoding; 0131 } 0132 0133 /** 0134 * Lowercase a string 0135 * 0136 * Lowercase's a string by reference 0137 * 0138 * @param string $value 0139 * @param string $key 0140 * @return string Lower cased string 0141 */ 0142 public static function lowerCase(&$value, &$key) 0143 { 0144 return $value = strtolower($value); 0145 } 0146 0147 /** 0148 * Whether or not to return a response 0149 * 0150 * If called without arguments, returns the value of the flag. If called 0151 * with an argument, sets the flag. 0152 * 0153 * When 'return response' is true, {@link handle()} will not send output, 0154 * but will instead return the response from the dispatched function/method. 0155 * 0156 * @param boolean $flag 0157 * @return boolean|Zend_Rest_Server Returns Zend_Rest_Server when used to set the flag; returns boolean flag value otherwise. 0158 */ 0159 public function returnResponse($flag = null) 0160 { 0161 if (null === $flag) { 0162 return $this->_returnResponse; 0163 } 0164 0165 $this->_returnResponse = ($flag) ? true : false; 0166 return $this; 0167 } 0168 0169 /** 0170 * Implement Zend_Server_Interface::handle() 0171 * 0172 * @param array $request 0173 * @throws Zend_Rest_Server_Exception 0174 * @return string|void 0175 */ 0176 public function handle($request = false) 0177 { 0178 $this->_headers = array('Content-Type: text/xml'); 0179 if (!$request) { 0180 $request = $_REQUEST; 0181 } 0182 if (isset($request['method'])) { 0183 $this->_method = $request['method']; 0184 if (isset($this->_functions[$this->_method])) { 0185 if ($this->_functions[$this->_method] instanceof 0186 Zend_Server_Reflection_Function 0187 || $this->_functions[$this->_method] instanceof 0188 Zend_Server_Reflection_Method 0189 && $this->_functions[$this->_method]->isPublic() 0190 ) { 0191 $requestKeys = array_keys($request); 0192 array_walk($requestKeys, array(__CLASS__, "lowerCase")); 0193 $request = array_combine($requestKeys, $request); 0194 0195 $funcArgs = $this->_functions[$this->_method]->getParameters(); 0196 0197 // calling_args will be a zero-based array of the parameters 0198 $callingArgs = array(); 0199 $missingArgs = array(); 0200 foreach ($funcArgs as $i => $arg) { 0201 if (isset($request[strtolower($arg->getName())])) { 0202 $callingArgs[$i] = $request[strtolower($arg->getName())]; 0203 } elseif ($arg->isOptional()) { 0204 $callingArgs[$i] = $arg->getDefaultValue(); 0205 } else { 0206 $missingArgs[] = $arg->getName(); 0207 } 0208 } 0209 0210 $anonymousArgs = array(); 0211 foreach ($request as $key => $value) { 0212 if (substr($key, 0, 3) == 'arg') { 0213 $key = str_replace('arg', '', $key); 0214 $anonymousArgs[$key] = $value; 0215 if (($index = array_search($key, $missingArgs)) !== false) { 0216 unset($missingArgs[$index]); 0217 } 0218 } 0219 } 0220 0221 // re-key the $anonymousArgs to be zero-based, and add in 0222 // any values already set in calling_args (optional defaults) 0223 ksort($anonymousArgs); 0224 $callingArgs = array_values($anonymousArgs) + $callingArgs; 0225 0226 // Sort arguments by key -- @see ZF-2279 0227 ksort($callingArgs); 0228 0229 $result = false; 0230 if (count($callingArgs) < count($funcArgs)) { 0231 // require_once 'Zend/Rest/Server/Exception.php'; 0232 $result = $this->fault( 0233 new Zend_Rest_Server_Exception( 0234 'Invalid Method Call to ' . $this->_method 0235 . '. Missing argument(s): ' . implode( 0236 ', ', $missingArgs 0237 ) . '.' 0238 ), 400 0239 ); 0240 } 0241 0242 if (!$result && $this->_functions[$this->_method] instanceof 0243 Zend_Server_Reflection_Method 0244 ) { 0245 // Get class 0246 $class = $this->_functions[$this->_method]->getDeclaringClass()->getName(); 0247 0248 if ($this->_functions[$this->_method]->isStatic()) { 0249 // for some reason, invokeArgs() does not work the same as 0250 // invoke(), and expects the first argument to be an object. 0251 // So, using a callback if the method is static. 0252 $result = $this->_callStaticMethod( 0253 $class, 0254 $callingArgs 0255 ); 0256 } else { 0257 // Object method 0258 $result = $this->_callObjectMethod( 0259 $class, 0260 $callingArgs 0261 ); 0262 } 0263 } elseif (!$result) { 0264 try { 0265 $result = call_user_func_array( 0266 $this->_functions[$this->_method]->getName(), 0267 $callingArgs 0268 ); 0269 } catch (Exception $e) { 0270 $result = $this->fault($e); 0271 } 0272 } 0273 } else { 0274 // require_once "Zend/Rest/Server/Exception.php"; 0275 $result = $this->fault( 0276 new Zend_Rest_Server_Exception( 0277 "Unknown Method '$this->_method'." 0278 ), 0279 404 0280 ); 0281 } 0282 } else { 0283 // require_once "Zend/Rest/Server/Exception.php"; 0284 $result = $this->fault( 0285 new Zend_Rest_Server_Exception( 0286 "Unknown Method '$this->_method'." 0287 ), 0288 404 0289 ); 0290 } 0291 } else { 0292 // require_once "Zend/Rest/Server/Exception.php"; 0293 $result = $this->fault( 0294 new Zend_Rest_Server_Exception("No Method Specified."), 0295 404 0296 ); 0297 } 0298 0299 if ($result instanceof SimpleXMLElement) { 0300 $response = $result->asXML(); 0301 } elseif ($result instanceof DOMDocument) { 0302 $response = $result->saveXML(); 0303 } elseif ($result instanceof DOMNode) { 0304 $response = $result->ownerDocument->saveXML($result); 0305 } elseif (is_array($result) || is_object($result)) { 0306 $response = $this->_handleStruct($result); 0307 } else { 0308 $response = $this->_handleScalar($result); 0309 } 0310 0311 if (!$this->returnResponse()) { 0312 if (!headers_sent()) { 0313 foreach ($this->_headers as $header) { 0314 header($header); 0315 } 0316 } 0317 0318 echo $response; 0319 return; 0320 } 0321 0322 return $response; 0323 } 0324 0325 /** 0326 * Implement Zend_Server_Interface::setClass() 0327 * 0328 * @param string $classname Class name 0329 * @param string $namespace Class namespace (unused) 0330 * @param array $argv An array of Constructor Arguments 0331 */ 0332 public function setClass($classname, $namespace = '', $argv = array()) 0333 { 0334 $this->_args = $argv; 0335 foreach ($this->_reflection->reflectClass($classname, $argv)->getMethods() as $method) { 0336 $this->_functions[$method->getName()] = $method; 0337 } 0338 } 0339 0340 /** 0341 * Handle an array or object result 0342 * 0343 * @param array|object $struct Result Value 0344 * @return string XML Response 0345 */ 0346 protected function _handleStruct($struct) 0347 { 0348 $function = $this->_functions[$this->_method]; 0349 if ($function instanceof Zend_Server_Reflection_Method) { 0350 $class = $function->getDeclaringClass()->getName(); 0351 } else { 0352 $class = false; 0353 } 0354 0355 $method = $function->getName(); 0356 0357 $dom = new DOMDocument('1.0', $this->getEncoding()); 0358 if ($class) { 0359 $root = $dom->createElement($class); 0360 $method = $dom->createElement($method); 0361 $root->appendChild($method); 0362 } else { 0363 $root = $dom->createElement($method); 0364 $method = $root; 0365 } 0366 $root->setAttribute('generator', 'zend'); 0367 $root->setAttribute('version', '1.0'); 0368 $dom->appendChild($root); 0369 0370 $this->_structValue($struct, $dom, $method); 0371 0372 $struct = (array) $struct; 0373 if (!isset($struct['status'])) { 0374 $status = $dom->createElement('status', 'success'); 0375 $method->appendChild($status); 0376 } 0377 0378 return $dom->saveXML(); 0379 } 0380 0381 /** 0382 * Recursively iterate through a struct 0383 * 0384 * Recursively iterates through an associative array or object's properties 0385 * to build XML response. 0386 * 0387 * @param mixed $struct 0388 * @param DOMDocument $dom 0389 * @param DOMElement $parent 0390 * @return void 0391 */ 0392 protected function _structValue( 0393 $struct, DOMDocument $dom, DOMElement $parent 0394 ) 0395 { 0396 $struct = (array)$struct; 0397 0398 foreach ($struct as $key => $value) { 0399 if ($value === false) { 0400 $value = 0; 0401 } elseif ($value === true) { 0402 $value = 1; 0403 } 0404 0405 if (ctype_digit((string)$key)) { 0406 $key = 'key_' . $key; 0407 } 0408 0409 if (is_array($value) || is_object($value)) { 0410 $element = $dom->createElement($key); 0411 $this->_structValue($value, $dom, $element); 0412 } else { 0413 $element = $dom->createElement($key); 0414 $element->appendChild($dom->createTextNode($value)); 0415 } 0416 0417 $parent->appendChild($element); 0418 } 0419 } 0420 0421 /** 0422 * Handle a single value 0423 * 0424 * @param string|int|boolean $value Result value 0425 * @return string XML Response 0426 */ 0427 protected function _handleScalar($value) 0428 { 0429 $function = $this->_functions[$this->_method]; 0430 if ($function instanceof Zend_Server_Reflection_Method) { 0431 $class = $function->getDeclaringClass()->getName(); 0432 } else { 0433 $class = false; 0434 } 0435 0436 $method = $function->getName(); 0437 0438 $dom = new DOMDocument('1.0', $this->getEncoding()); 0439 if ($class) { 0440 $xml = $dom->createElement($class); 0441 $methodNode = $dom->createElement($method); 0442 $xml->appendChild($methodNode); 0443 } else { 0444 $xml = $dom->createElement($method); 0445 $methodNode = $xml; 0446 } 0447 $xml->setAttribute('generator', 'zend'); 0448 $xml->setAttribute('version', '1.0'); 0449 $dom->appendChild($xml); 0450 0451 if ($value === false) { 0452 $value = 0; 0453 } elseif ($value === true) { 0454 $value = 1; 0455 } 0456 0457 if (isset($value)) { 0458 $element = $dom->createElement('response'); 0459 $element->appendChild($dom->createTextNode($value)); 0460 $methodNode->appendChild($element); 0461 } else { 0462 $methodNode->appendChild($dom->createElement('response')); 0463 } 0464 0465 $methodNode->appendChild($dom->createElement('status', 'success')); 0466 0467 return $dom->saveXML(); 0468 } 0469 0470 /** 0471 * Implement Zend_Server_Interface::fault() 0472 * 0473 * Creates XML error response, returning DOMDocument with response. 0474 * 0475 * @param string|Exception $fault Message 0476 * @param int $code Error Code 0477 * @return DOMDocument 0478 */ 0479 public function fault($exception = null, $code = null) 0480 { 0481 if (isset($this->_functions[$this->_method])) { 0482 $function = $this->_functions[$this->_method]; 0483 } elseif (isset($this->_method)) { 0484 $function = $this->_method; 0485 } else { 0486 $function = 'rest'; 0487 } 0488 0489 if ($function instanceof Zend_Server_Reflection_Method) { 0490 $class = $function->getDeclaringClass()->getName(); 0491 } else { 0492 $class = false; 0493 } 0494 0495 if ($function instanceof Zend_Server_Reflection_Function_Abstract) { 0496 $method = $function->getName(); 0497 } else { 0498 $method = $function; 0499 } 0500 0501 $dom = new DOMDocument('1.0', $this->getEncoding()); 0502 if ($class) { 0503 $xml = $dom->createElement($class); 0504 $xmlMethod = $dom->createElement($method); 0505 $xml->appendChild($xmlMethod); 0506 } else { 0507 $xml = $dom->createElement($method); 0508 $xmlMethod = $xml; 0509 } 0510 $xml->setAttribute('generator', 'zend'); 0511 $xml->setAttribute('version', '1.0'); 0512 $dom->appendChild($xml); 0513 0514 $xmlResponse = $dom->createElement('response'); 0515 $xmlMethod->appendChild($xmlResponse); 0516 0517 if ($exception instanceof Exception) { 0518 $element = $dom->createElement('message'); 0519 $element->appendChild( 0520 $dom->createTextNode($exception->getMessage()) 0521 ); 0522 $xmlResponse->appendChild($element); 0523 $code = $exception->getCode(); 0524 } elseif (($exception !== null) || 'rest' == $function) { 0525 $xmlResponse->appendChild( 0526 $dom->createElement( 0527 'message', 'An unknown error occured. Please try again.' 0528 ) 0529 ); 0530 } else { 0531 $xmlResponse->appendChild( 0532 $dom->createElement( 0533 'message', 'Call to ' . $method . ' failed.' 0534 ) 0535 ); 0536 } 0537 0538 $xmlMethod->appendChild($xmlResponse); 0539 $xmlMethod->appendChild($dom->createElement('status', 'failed')); 0540 0541 // Headers to send 0542 if ($code === null || (404 != $code)) { 0543 $this->_headers[] = 'HTTP/1.0 400 Bad Request'; 0544 } else { 0545 $this->_headers[] = 'HTTP/1.0 404 File Not Found'; 0546 } 0547 0548 return $dom; 0549 } 0550 0551 /** 0552 * Retrieve any HTTP extra headers set by the server 0553 * 0554 * @return array 0555 */ 0556 public function getHeaders() 0557 { 0558 return $this->_headers; 0559 } 0560 0561 /** 0562 * Implement Zend_Server_Interface::addFunction() 0563 * 0564 * @param string $function Function Name 0565 * @param string $namespace Function namespace (unused) 0566 */ 0567 public function addFunction($function, $namespace = '') 0568 { 0569 if (!is_array($function)) { 0570 $function = (array) $function; 0571 } 0572 0573 foreach ($function as $func) { 0574 if (is_callable($func) && !in_array($func, self::$magicMethods)) { 0575 $this->_functions[$func] = $this->_reflection->reflectFunction($func); 0576 } else { 0577 // require_once 'Zend/Rest/Server/Exception.php'; 0578 throw new Zend_Rest_Server_Exception( 0579 "Invalid Method Added to Service." 0580 ); 0581 } 0582 } 0583 } 0584 0585 /** 0586 * Implement Zend_Server_Interface::getFunctions() 0587 * 0588 * @return array An array of Zend_Server_Reflection_Method's 0589 */ 0590 public function getFunctions() 0591 { 0592 return $this->_functions; 0593 } 0594 0595 /** 0596 * Implement Zend_Server_Interface::loadFunctions() 0597 * 0598 * @todo Implement 0599 * @param array $functions 0600 */ 0601 public function loadFunctions($functions) 0602 { 0603 } 0604 0605 /** 0606 * Implement Zend_Server_Interface::setPersistence() 0607 * 0608 * @todo Implement 0609 * @param int $mode 0610 */ 0611 public function setPersistence($mode) 0612 { 0613 } 0614 0615 /** 0616 * Call a static class method and return the result 0617 * 0618 * @param string $class 0619 * @param array $args 0620 * @return mixed 0621 */ 0622 protected function _callStaticMethod($class, array $args) 0623 { 0624 try { 0625 $result = call_user_func_array( 0626 array( 0627 $class, 0628 $this->_functions[$this->_method]->getName() 0629 ), 0630 $args 0631 ); 0632 } catch (Exception $e) { 0633 $result = $this->fault($e); 0634 } 0635 return $result; 0636 } 0637 0638 /** 0639 * Call an instance method of an object 0640 * 0641 * @param string $class 0642 * @param array $args 0643 * @return mixed 0644 * @throws Zend_Rest_Server_Exception For invalid class name 0645 */ 0646 protected function _callObjectMethod($class, array $args) 0647 { 0648 try { 0649 if ($this->_functions[$this->_method]->getDeclaringClass()->getConstructor()) { 0650 $object = $this->_functions[$this->_method]->getDeclaringClass()->newInstanceArgs($this->_args); 0651 } else { 0652 $object = $this->_functions[$this->_method]->getDeclaringClass()->newInstance(); 0653 } 0654 } catch (Exception $e) { 0655 // require_once 'Zend/Rest/Server/Exception.php'; 0656 throw new Zend_Rest_Server_Exception( 0657 'Error instantiating class ' . $class . 0658 ' to invoke method ' 0659 . $this->_functions[$this->_method]->getName() . 0660 ' (' . $e->getMessage() . ') ', 0661 500, 0662 $e 0663 ); 0664 } 0665 0666 try { 0667 $result = $this->_functions[$this->_method]->invokeArgs( 0668 $object, 0669 $args 0670 ); 0671 } catch (Exception $e) { 0672 $result = $this->fault($e); 0673 } 0674 0675 return $result; 0676 } 0677 }