version = $version; } /** * Treat the current request and output the result. This is the only * method that should be called on the webservice directly ! * @param bool $sendSession needed for testing */ public function run($sendSession = true) { Logger::start(array('version' => $this->version)); $rendererClass = Configuration::get('renderer.class', __NAMESPACE__ . '\Renderer'); $renderer = new $rendererClass; $data = array(); try { $result = $this->call($sendSession); $data['result'] = array(); $data["result"][$this->func] = $result; $data = $this->filterOutput($data); } catch (WebException $e) { $data["error"]["code"] = $e->getCode(); $data["error"]["reason"] = $e->getMessage(); $data["error"]["name"] = $e->getName(); if ($this->func !== null) { $data['extra'] = Help::exception($e, $this, $this->func); } $this->status = 400; Logger::info($e->getName(), 'error'); } catch (\Exception $e) { $data["failure"]["code"] = $e->getCode(); $data["failure"]["reason"] = $e->getMessage(); $this->status = 500; Logger::info($e->getMessage(), 'error'); } Logger::stop(array('status' => $this->status)); $renderer->render($this->status, $data); } protected function startSession() { if (!$this->sessionStarted) { session_save_path(Configuration::get('session.save_path')); session_start(); $this->sessionStarted = true; } } /** * Determines which method to call based on GET or POST parameters and * call it before returning the result. * * @param bool $sendSession used for testing * @return array * @throws UsageException */ private function call($sendSession = true) { if ($sendSession) { $this->startSession(); } $params = empty($_GET) ? $_POST : $_GET; if (empty($params)) { throw new UsageException("NoArguments", "No arguments specified.", UsageException::NO_ARGS); } if (!isset($params["func"])) { throw new UsageException("MissingMethod", "No method specified.", UsageException::MISSING_METHOD); } $params = $this->filterParams($params); $this->isAllowedMethodNameOrThrow($params["func"]); $this->func = $params["func"]; unset($params['func']); $this->paramsMatchMethodDeclarationOrThrow($params); Logger::info(array( 'func' => $this->func.'('.implode(', ', $params).')', )); return call_user_func_array(array($this, $this->func), array_values($params)); } /** * Verify that the provided params match the declaration of the requested method * @param array $params * @throws UsageException */ protected function paramsMatchMethodDeclarationOrThrow($params) { if (!is_callable(array($this, $this->func))) { throw new UsageException("BadMethod", "Method {$this->func} does not exist.", UsageException::BAD_METHOD); } $nbParams = count($params); $rm = new \ReflectionMethod($this, $this->func); $nbArgsFix = $rm->getNumberOfRequiredParameters(); $nbArgs = $rm->getNumberOfParameters(); /* Check the number of arguments. */ if ($nbParams < $nbArgsFix) { throw new UsageException("TooFewArgs", "You must provide at least $nbArgsFix arguments.", UsageException::TOO_FEW_ARGS); } if ($nbParams > $nbArgs) { throw new UsageException("TooManyArgs", "You must provide at most $nbArgs arguments.", UsageException::TOO_MANY_ARGS); } } /** * If no configuration is available assumes that all public methods are allowed * @param string the requested method name from api param func */ protected function isAllowedMethodNameOrThrow($requestedMethodName) { $allowedMethodNames = Configuration::get('webservice.api_method_names', null); if (null === $allowedMethodNames) { return; } if (!is_array($allowedMethodNames)) { throw new ConfigException('Bad config. You should pass an array of method names as strings, in "webservice" key and "api_method_names" subkey'); } if (!in_array($requestedMethodName, $allowedMethodNames)) { throw new UsageException("BadMethod", "Method {$requestedMethodName} is not whitelisted. Pick one of :" . implode(', ', $allowedMethodNames), UsageException::BAD_METHOD); } } /** * Allow subclasses to filter params by overriding this * @param $params * @param array */ protected function filterParams($params) { return $params; } /** * Allow subclasses to filter the output before it is sent * by overriding this method * @param $params * @return array */ protected function filterOutput(array $data) { return $data; } }