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