<?php /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@zend.com so we can send you a copy immediately. * * @category Zend * @package Zend_Rest * @subpackage Server * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * @see Zend_Server_Interface */ require_once 'Zend/Server/Interface.php'; /** * @see Zend_Server_Reflection */ require_once 'Zend/Server/Reflection.php'; /** * @see Zend_Server_Abstract */ require_once 'Zend/Server/Abstract.php'; /** * @category Zend * @package Zend_Rest * @subpackage Server * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Rest_Server implements Zend_Server_Interface { /** * Class Constructor Args * @var array */ protected $_args = array(); /** * @var string Encoding */ protected $_encoding = 'UTF-8'; /** * @var array An array of Zend_Server_Reflect_Method */ protected $_functions = array(); /** * @var array Array of headers to send */ protected $_headers = array(); /** * @var array PHP's Magic Methods, these are ignored */ protected static $magicMethods = array( '__construct', '__destruct', '__get', '__set', '__call', '__sleep', '__wakeup', '__isset', '__unset', '__tostring', '__clone', '__set_state', ); /** * @var string Current Method */ protected $_method; /** * @var Zend_Server_Reflection */ protected $_reflection = null; /** * Whether or not {@link handle()} should send output or return the response. * @var boolean Defaults to false */ protected $_returnResponse = false; /** * Constructor */ public function __construct() { set_exception_handler(array($this, "fault")); $this->_reflection = new Zend_Server_Reflection(); } /** * Set XML encoding * * @param string $encoding * @return Zend_Rest_Server */ public function setEncoding($encoding) { $this->_encoding = (string) $encoding; return $this; } /** * Get XML encoding * * @return string */ public function getEncoding() { return $this->_encoding; } /** * Lowercase a string * * Lowercase's a string by reference * * @param string $value * @param string $key * @return string Lower cased string */ public static function lowerCase(&$value, &$key) { return $value = strtolower($value); } /** * Whether or not to return a response * * If called without arguments, returns the value of the flag. If called * with an argument, sets the flag. * * When 'return response' is true, {@link handle()} will not send output, * but will instead return the response from the dispatched function/method. * * @param boolean $flag * @return boolean|Zend_Rest_Server Returns Zend_Rest_Server when used to set the flag; returns boolean flag value otherwise. */ public function returnResponse($flag = null) { if (null === $flag) { return $this->_returnResponse; } $this->_returnResponse = ($flag) ? true : false; return $this; } /** * Implement Zend_Server_Interface::handle() * * @param array $request * @throws Zend_Rest_Server_Exception * @return string|void */ public function handle($request = false) { $this->_headers = array('Content-Type: text/xml'); if (!$request) { $request = $_REQUEST; } if (isset($request['method'])) { $this->_method = $request['method']; if (isset($this->_functions[$this->_method])) { if ($this->_functions[$this->_method] instanceof Zend_Server_Reflection_Function || $this->_functions[$this->_method] instanceof Zend_Server_Reflection_Method && $this->_functions[$this->_method]->isPublic()) { $request_keys = array_keys($request); array_walk($request_keys, array(__CLASS__, "lowerCase")); $request = array_combine($request_keys, $request); $func_args = $this->_functions[$this->_method]->getParameters(); $calling_args = array(); $missing_args = array(); foreach ($func_args as $arg) { if (isset($request[strtolower($arg->getName())])) { $calling_args[] = $request[strtolower($arg->getName())]; } elseif ($arg->isOptional()) { $calling_args[] = $arg->getDefaultValue(); } else { $missing_args[] = $arg->getName(); } } foreach ($request as $key => $value) { if (substr($key, 0, 3) == 'arg') { $key = str_replace('arg', '', $key); $calling_args[$key] = $value; if (($index = array_search($key, $missing_args)) !== false) { unset($missing_args[$index]); } } } // Sort arguments by key -- @see ZF-2279 ksort($calling_args); $result = false; if (count($calling_args) < count($func_args)) { require_once 'Zend/Rest/Server/Exception.php'; $result = $this->fault(new Zend_Rest_Server_Exception('Invalid Method Call to ' . $this->_method . '. Missing argument(s): ' . implode(', ', $missing_args) . '.'), 400); } if (!$result && $this->_functions[$this->_method] instanceof Zend_Server_Reflection_Method) { // Get class $class = $this->_functions[$this->_method]->getDeclaringClass()->getName(); if ($this->_functions[$this->_method]->isStatic()) { // for some reason, invokeArgs() does not work the same as // invoke(), and expects the first argument to be an object. // So, using a callback if the method is static. $result = $this->_callStaticMethod($class, $calling_args); } else { // Object method $result = $this->_callObjectMethod($class, $calling_args); } } elseif (!$result) { try { $result = call_user_func_array($this->_functions[$this->_method]->getName(), $calling_args); //$this->_functions[$this->_method]->invokeArgs($calling_args); } catch (Exception $e) { $result = $this->fault($e); } } } else { require_once "Zend/Rest/Server/Exception.php"; $result = $this->fault( new Zend_Rest_Server_Exception("Unknown Method '$this->_method'."), 404 ); } } else { require_once "Zend/Rest/Server/Exception.php"; $result = $this->fault( new Zend_Rest_Server_Exception("Unknown Method '$this->_method'."), 404 ); } } else { require_once "Zend/Rest/Server/Exception.php"; $result = $this->fault( new Zend_Rest_Server_Exception("No Method Specified."), 404 ); } if ($result instanceof SimpleXMLElement) { $response = $result->asXML(); } elseif ($result instanceof DOMDocument) { $response = $result->saveXML(); } elseif ($result instanceof DOMNode) { $response = $result->ownerDocument->saveXML($result); } elseif (is_array($result) || is_object($result)) { $response = $this->_handleStruct($result); } else { $response = $this->_handleScalar($result); } if (!$this->returnResponse()) { if (!headers_sent()) { foreach ($this->_headers as $header) { header($header); } } echo $response; return; } return $response; } /** * Implement Zend_Server_Interface::setClass() * * @param string $classname Class name * @param string $namespace Class namespace (unused) * @param array $argv An array of Constructor Arguments */ public function setClass($classname, $namespace = '', $argv = array()) { $this->_args = $argv; foreach ($this->_reflection->reflectClass($classname, $argv)->getMethods() as $method) { $this->_functions[$method->getName()] = $method; } } /** * Handle an array or object result * * @param array|object $struct Result Value * @return string XML Response */ protected function _handleStruct($struct) { $function = $this->_functions[$this->_method]; if ($function instanceof Zend_Server_Reflection_Method) { $class = $function->getDeclaringClass()->getName(); } else { $class = false; } $method = $function->getName(); $dom = new DOMDocument('1.0', $this->getEncoding()); if ($class) { $root = $dom->createElement($class); $method = $dom->createElement($method); $root->appendChild($method); } else { $root = $dom->createElement($method); $method = $root; } $root->setAttribute('generator', 'zend'); $root->setAttribute('version', '1.0'); $dom->appendChild($root); $this->_structValue($struct, $dom, $method); $struct = (array) $struct; if (!isset($struct['status'])) { $status = $dom->createElement('status', 'success'); $method->appendChild($status); } return $dom->saveXML(); } /** * Recursively iterate through a struct * * Recursively iterates through an associative array or object's properties * to build XML response. * * @param mixed $struct * @param DOMDocument $dom * @param DOMElement $parent * @return void */ protected function _structValue($struct, DOMDocument $dom, DOMElement $parent) { $struct = (array) $struct; foreach ($struct as $key => $value) { if ($value === false) { $value = 0; } elseif ($value === true) { $value = 1; } if (ctype_digit((string) $key)) { $key = 'key_' . $key; } if (is_array($value) || is_object($value)) { $element = $dom->createElement($key); $this->_structValue($value, $dom, $element); } else { $element = $dom->createElement($key); $element->appendChild($dom->createTextNode($value)); } $parent->appendChild($element); } } /** * Handle a single value * * @param string|int|boolean $value Result value * @return string XML Response */ protected function _handleScalar($value) { $function = $this->_functions[$this->_method]; if ($function instanceof Zend_Server_Reflection_Method) { $class = $function->getDeclaringClass()->getName(); } else { $class = false; } $method = $function->getName(); $dom = new DOMDocument('1.0', $this->getEncoding()); if ($class) { $xml = $dom->createElement($class); $methodNode = $dom->createElement($method); $xml->appendChild($methodNode); } else { $xml = $dom->createElement($method); $methodNode = $xml; } $xml->setAttribute('generator', 'zend'); $xml->setAttribute('version', '1.0'); $dom->appendChild($xml); if ($value === false) { $value = 0; } elseif ($value === true) { $value = 1; } if (isset($value)) { $element = $dom->createElement('response'); $element->appendChild($dom->createTextNode($value)); $methodNode->appendChild($element); } else { $methodNode->appendChild($dom->createElement('response')); } $methodNode->appendChild($dom->createElement('status', 'success')); return $dom->saveXML(); } /** * Implement Zend_Server_Interface::fault() * * Creates XML error response, returning DOMDocument with response. * * @param string|Exception $fault Message * @param int $code Error Code * @return DOMDocument */ public function fault($exception = null, $code = null) { if (isset($this->_functions[$this->_method])) { $function = $this->_functions[$this->_method]; } elseif (isset($this->_method)) { $function = $this->_method; } else { $function = 'rest'; } if ($function instanceof Zend_Server_Reflection_Method) { $class = $function->getDeclaringClass()->getName(); } else { $class = false; } if ($function instanceof Zend_Server_Reflection_Function_Abstract) { $method = $function->getName(); } else { $method = $function; } $dom = new DOMDocument('1.0', $this->getEncoding()); if ($class) { $xml = $dom->createElement($class); $xmlMethod = $dom->createElement($method); $xml->appendChild($xmlMethod); } else { $xml = $dom->createElement($method); $xmlMethod = $xml; } $xml->setAttribute('generator', 'zend'); $xml->setAttribute('version', '1.0'); $dom->appendChild($xml); $xmlResponse = $dom->createElement('response'); $xmlMethod->appendChild($xmlResponse); if ($exception instanceof Exception) { $element = $dom->createElement('message'); $element->appendChild($dom->createTextNode($exception->getMessage())); $xmlResponse->appendChild($element); $code = $exception->getCode(); } elseif (($exception !== null) || 'rest' == $function) { $xmlResponse->appendChild($dom->createElement('message', 'An unknown error occured. Please try again.')); } else { $xmlResponse->appendChild($dom->createElement('message', 'Call to ' . $method . ' failed.')); } $xmlMethod->appendChild($xmlResponse); $xmlMethod->appendChild($dom->createElement('status', 'failed')); // Headers to send if ($code === null || (404 != $code)) { $this->_headers[] = 'HTTP/1.0 400 Bad Request'; } else { $this->_headers[] = 'HTTP/1.0 404 File Not Found'; } return $dom; } /** * Retrieve any HTTP extra headers set by the server * * @return array */ public function getHeaders() { return $this->_headers; } /** * Implement Zend_Server_Interface::addFunction() * * @param string $function Function Name * @param string $namespace Function namespace (unused) */ public function addFunction($function, $namespace = '') { if (!is_array($function)) { $function = (array) $function; } foreach ($function as $func) { if (is_callable($func) && !in_array($func, self::$magicMethods)) { $this->_functions[$func] = $this->_reflection->reflectFunction($func); } else { require_once 'Zend/Rest/Server/Exception.php'; throw new Zend_Rest_Server_Exception("Invalid Method Added to Service."); } } } /** * Implement Zend_Server_Interface::getFunctions() * * @return array An array of Zend_Server_Reflection_Method's */ public function getFunctions() { return $this->_functions; } /** * Implement Zend_Server_Interface::loadFunctions() * * @todo Implement * @param array $functions */ public function loadFunctions($functions) { } /** * Implement Zend_Server_Interface::setPersistence() * * @todo Implement * @param int $mode */ public function setPersistence($mode) { } /** * Call a static class method and return the result * * @param string $class * @param array $args * @return mixed */ protected function _callStaticMethod($class, array $args) { try { $result = call_user_func_array(array($class, $this->_functions[$this->_method]->getName()), $args); } catch (Exception $e) { $result = $this->fault($e); } return $result; } /** * Call an instance method of an object * * @param string $class * @param array $args * @return mixed * @throws Zend_Rest_Server_Exception For invalid class name */ protected function _callObjectMethod($class, array $args) { try { if ($this->_functions[$this->_method]->getDeclaringClass()->getConstructor()) { $object = $this->_functions[$this->_method]->getDeclaringClass()->newInstanceArgs($this->_args); } else { $object = $this->_functions[$this->_method]->getDeclaringClass()->newInstance(); } } catch (Exception $e) { require_once 'Zend/Rest/Server/Exception.php'; throw new Zend_Rest_Server_Exception('Error instantiating class ' . $class . ' to invoke method ' . $this->_functions[$this->_method]->getName() . ' (' . $e->getMessage() . ') ', 500, $e); } try { $result = $this->_functions[$this->_method]->invokeArgs($object, $args); } catch (Exception $e) { $result = $this->fault($e); } return $result; } }