File indexing completed on 2024-05-12 06:02:55

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 }