<?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_Json * @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_Abstract */ require_once 'Zend/Server/Abstract.php'; /** * @category Zend * @package Zend_Json * @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_Json_Server extends Zend_Server_Abstract { /**#@+ * Version Constants */ const VERSION_1 = '1.0'; const VERSION_2 = '2.0'; /**#@-*/ /** * Flag: whether or not to auto-emit the response * @var bool */ protected $_autoEmitResponse = true; /** * @var bool Flag; allow overwriting existing methods when creating server definition */ protected $_overwriteExistingMethods = true; /** * Request object * @var Zend_Json_Server_Request */ protected $_request; /** * Response object * @var Zend_Json_Server_Response */ protected $_response; /** * SMD object * @var Zend_Json_Server_Smd */ protected $_serviceMap; /** * SMD class accessors * @var array */ protected $_smdMethods; /** * @var Zend_Server_Description */ protected $_table; /** * Attach a function or callback to the server * * @param string|array $function Valid PHP callback * @param string $namespace Ignored * @return Zend_Json_Server */ public function addFunction($function, $namespace = '') { if (!is_string($function) && (!is_array($function) || (2 > count($function)))) { require_once 'Zend/Json/Server/Exception.php'; throw new Zend_Json_Server_Exception('Unable to attach function; invalid'); } if (!is_callable($function)) { require_once 'Zend/Json/Server/Exception.php'; throw new Zend_Json_Server_Exception('Unable to attach function; does not exist'); } $argv = null; if (2 < func_num_args()) { $argv = func_get_args(); $argv = array_slice($argv, 2); } require_once 'Zend/Server/Reflection.php'; if (is_string($function)) { $method = Zend_Server_Reflection::reflectFunction($function, $argv, $namespace); } else { $class = array_shift($function); $action = array_shift($function); $reflection = Zend_Server_Reflection::reflectClass($class, $argv, $namespace); $methods = $reflection->getMethods(); $found = false; foreach ($methods as $method) { if ($action == $method->getName()) { $found = true; break; } } if (!$found) { $this->fault('Method not found', -32601); return $this; } } $definition = $this->_buildSignature($method); $this->_addMethodServiceMap($definition); return $this; } /** * Register a class with the server * * @param string $class * @param string $namespace Ignored * @param mixed $argv Ignored * @return Zend_Json_Server */ public function setClass($class, $namespace = '', $argv = null) { $argv = null; if (3 < func_num_args()) { $argv = func_get_args(); $argv = array_slice($argv, 3); } require_once 'Zend/Server/Reflection.php'; $reflection = Zend_Server_Reflection::reflectClass($class, $argv, $namespace); foreach ($reflection->getMethods() as $method) { $definition = $this->_buildSignature($method, $class); $this->_addMethodServiceMap($definition); } return $this; } /** * Indicate fault response * * @param string $fault * @param int $code * @return false */ public function fault($fault = null, $code = 404, $data = null) { require_once 'Zend/Json/Server/Error.php'; $error = new Zend_Json_Server_Error($fault, $code, $data); $this->getResponse()->setError($error); return $error; } /** * Handle request * * @param Zend_Json_Server_Request $request * @return null|Zend_Json_Server_Response */ public function handle($request = false) { if ((false !== $request) && (!$request instanceof Zend_Json_Server_Request)) { require_once 'Zend/Json/Server/Exception.php'; throw new Zend_Json_Server_Exception('Invalid request type provided; cannot handle'); } elseif ($request) { $this->setRequest($request); } // Handle request $this->_handle(); // Get response $response = $this->_getReadyResponse(); // Emit response? if ($this->autoEmitResponse()) { echo $response; return; } // or return it? return $response; } /** * Load function definitions * * @param array|Zend_Server_Definition $definition * @return void */ public function loadFunctions($definition) { if (!is_array($definition) && (!$definition instanceof Zend_Server_Definition)) { require_once 'Zend/Json/Server/Exception.php'; throw new Zend_Json_Server_Exception('Invalid definition provided to loadFunctions()'); } foreach ($definition as $key => $method) { $this->_table->addMethod($method, $key); $this->_addMethodServiceMap($method); } } public function setPersistence($mode) { } /** * Set request object * * @param Zend_Json_Server_Request $request * @return Zend_Json_Server */ public function setRequest(Zend_Json_Server_Request $request) { $this->_request = $request; return $this; } /** * Get JSON-RPC request object * * @return Zend_Json_Server_Request */ public function getRequest() { if (null === ($request = $this->_request)) { require_once 'Zend/Json/Server/Request/Http.php'; $this->setRequest(new Zend_Json_Server_Request_Http()); } return $this->_request; } /** * Set response object * * @param Zend_Json_Server_Response $response * @return Zend_Json_Server */ public function setResponse(Zend_Json_Server_Response $response) { $this->_response = $response; return $this; } /** * Get response object * * @return Zend_Json_Server_Response */ public function getResponse() { if (null === ($response = $this->_response)) { require_once 'Zend/Json/Server/Response/Http.php'; $this->setResponse(new Zend_Json_Server_Response_Http()); } return $this->_response; } /** * Set flag indicating whether or not to auto-emit response * * @param bool $flag * @return Zend_Json_Server */ public function setAutoEmitResponse($flag) { $this->_autoEmitResponse = (bool) $flag; return $this; } /** * Will we auto-emit the response? * * @return bool */ public function autoEmitResponse() { return $this->_autoEmitResponse; } // overloading for SMD metadata /** * Overload to accessors of SMD object * * @param string $method * @param array $args * @return mixed */ public function __call($method, $args) { if (preg_match('/^(set|get)/', $method, $matches)) { if (in_array($method, $this->_getSmdMethods())) { if ('set' == $matches[1]) { $value = array_shift($args); $this->getServiceMap()->$method($value); return $this; } else { return $this->getServiceMap()->$method(); } } } return null; } /** * Retrieve SMD object * * @return Zend_Json_Server_Smd */ public function getServiceMap() { if (null === $this->_serviceMap) { require_once 'Zend/Json/Server/Smd.php'; $this->_serviceMap = new Zend_Json_Server_Smd(); } return $this->_serviceMap; } /** * Add service method to service map * * @param Zend_Server_Reflection_Function $method * @return void */ protected function _addMethodServiceMap(Zend_Server_Method_Definition $method) { $serviceInfo = array( 'name' => $method->getName(), 'return' => $this->_getReturnType($method), ); $params = $this->_getParams($method); $serviceInfo['params'] = $params; $serviceMap = $this->getServiceMap(); if (false !== $serviceMap->getService($serviceInfo['name'])) { $serviceMap->removeService($serviceInfo['name']); } $serviceMap->addService($serviceInfo); } /** * Translate PHP type to JSON type * * @param string $type * @return string */ protected function _fixType($type) { return $type; } /** * Get default params from signature * * @param array $args * @param array $params * @return array */ protected function _getDefaultParams(array $args, array $params) { $defaultParams = array_slice($params, count($args)); foreach ($defaultParams as $param) { $value = null; if (array_key_exists('default', $param)) { $value = $param['default']; } array_push($args, $value); } return $args; } /** * Get method param type * * @param Zend_Server_Reflection_Function_Abstract $method * @return string|array */ protected function _getParams(Zend_Server_Method_Definition $method) { $params = array(); foreach ($method->getPrototypes() as $prototype) { foreach ($prototype->getParameterObjects() as $key => $parameter) { if (!isset($params[$key])) { $params[$key] = array( 'type' => $parameter->getType(), 'name' => $parameter->getName(), 'optional' => $parameter->isOptional(), ); if (null !== ($default = $parameter->getDefaultValue())) { $params[$key]['default'] = $default; } $description = $parameter->getDescription(); if (!empty($description)) { $params[$key]['description'] = $description; } continue; } $newType = $parameter->getType(); if (!is_array($params[$key]['type'])) { if ($params[$key]['type'] == $newType) { continue; } $params[$key]['type'] = (array) $params[$key]['type']; } elseif (in_array($newType, $params[$key]['type'])) { continue; } array_push($params[$key]['type'], $parameter->getType()); } } return $params; } /** * Set response state * * @return Zend_Json_Server_Response */ protected function _getReadyResponse() { $request = $this->getRequest(); $response = $this->getResponse(); $response->setServiceMap($this->getServiceMap()); if (null !== ($id = $request->getId())) { $response->setId($id); } if (null !== ($version = $request->getVersion())) { $response->setVersion($version); } return $response; } /** * Get method return type * * @param Zend_Server_Reflection_Function_Abstract $method * @return string|array */ protected function _getReturnType(Zend_Server_Method_Definition $method) { $return = array(); foreach ($method->getPrototypes() as $prototype) { $return[] = $prototype->getReturnType(); } if (1 == count($return)) { return $return[0]; } return $return; } /** * Retrieve list of allowed SMD methods for proxying * * @return array */ protected function _getSmdMethods() { if (null === $this->_smdMethods) { $this->_smdMethods = array(); require_once 'Zend/Json/Server/Smd.php'; $methods = get_class_methods('Zend_Json_Server_Smd'); foreach ($methods as $key => $method) { if (!preg_match('/^(set|get)/', $method)) { continue; } if (strstr($method, 'Service')) { continue; } $this->_smdMethods[] = $method; } } return $this->_smdMethods; } /** * Internal method for handling request * * @return void */ protected function _handle() { $request = $this->getRequest(); if (!$request->isMethodError() && (null === $request->getMethod())) { return $this->fault('Invalid Request', -32600); } if ($request->isMethodError()) { return $this->fault('Invalid Request', -32600); } $method = $request->getMethod(); if (!$this->_table->hasMethod($method)) { return $this->fault('Method not found', -32601); } $params = $request->getParams(); $invocable = $this->_table->getMethod($method); $serviceMap = $this->getServiceMap(); $service = $serviceMap->getService($method); $serviceParams = $service->getParams(); if (count($params) < count($serviceParams)) { $params = $this->_getDefaultParams($params, $serviceParams); } //Make sure named parameters are passed in correct order if ( is_string( key( $params ) ) ) { $callback = $invocable->getCallback(); if ('function' == $callback->getType()) { $reflection = new ReflectionFunction( $callback->getFunction() ); $refParams = $reflection->getParameters(); } else { $reflection = new ReflectionMethod( $callback->getClass(), $callback->getMethod() ); $refParams = $reflection->getParameters(); } $orderedParams = array(); foreach( $reflection->getParameters() as $refParam ) { if( isset( $params[ $refParam->getName() ] ) ) { $orderedParams[ $refParam->getName() ] = $params[ $refParam->getName() ]; } elseif( $refParam->isOptional() ) { $orderedParams[ $refParam->getName() ] = $refParam->getDefaultValue(); } else { throw new Zend_Server_Exception( 'Missing required parameter: ' . $refParam->getName() ); } } $params = $orderedParams; } try { $result = $this->_dispatch($invocable, $params); } catch (Exception $e) { return $this->fault($e->getMessage(), $e->getCode(), $e); } $this->getResponse()->setResult($result); } }