/2012/sample-tonic/src/Tonic/Resource.php
https://bitbucket.org/alessandro-aglietti/itis-leonardo-da-vinci · PHP · 304 lines · 215 code · 30 blank · 59 comment · 36 complexity · 00c1ed60e1aa6c4a7483d8a2fb20108f MD5 · raw file
- <?php
- namespace Tonic;
- /**
- * Model a HTTP resource
- */
- class Resource
- {
- protected $app, $request;
- public $params;
- private $currentMethodName;
- protected $before = array(), $after = array();
- public function __construct(Application $app, Request $request, array $urlParams)
- {
- $this->app = $app;
- $this->request = $request;
- $this->params = $urlParams;
- }
- /**
- * Get a URL parameter as defined by this resource and it's URI
- * @param str $name Name of the parameter
- * @return str
- */
- public function __get($name)
- {
- return isset($this->params[$name]) ? $this->params[$name] : NULL;
- }
- /**
- * Get the method name of the best matching resource method.
- *
- * @param str[] $resourceMetadata
- * @return str
- */
- private function calculateMethodPriorities($resourceMetadata)
- {
- $methodPriorities = array();
- if (isset($resourceMetadata['methods'])) {
- foreach ($resourceMetadata['methods'] as $key => $methodMetadata) {
- foreach ($methodMetadata as $conditionName => $conditions) { // process each method condition
- if (method_exists($this, $conditionName)) {
- $this->currentMethodName = $key;
- $success = false;
- foreach ($conditions as $params) {
- if (!isset($methodPriorities[$key]['value'])) {
- $methodPriorities[$key]['value'] = 0;
- }
- try {
- if (is_array($params)) {
- $condition = call_user_func_array(array($this, $conditionName), $params);
- } else {
- $condition = call_user_func(array($this, $conditionName), $params);
- }
- if ($condition === true) $condition = 1;
- if (is_numeric($condition)) {
- $methodPriorities[$key]['value'] += $condition;
- } elseif ($condition) {
- $methodPriorities[$key]['value']++;
- $methodPriorities[$key]['response'] = $condition;
- }
- $success = true;
- } catch (ConditionException $e) {
- unset($methodPriorities[$key]);
- break 2;
- } catch (Exception $e) {
- $error = $e;
- }
- }
- if (!$success) {
- $methodPriorities[$key]['exception'] = $error;
- $methodPriorities[$key]['value'] = -1;
- break;
- }
- } else {
- throw new \Exception(sprintf(
- 'Condition method "%s" not found in Resource class "%s"',
- $conditionName,
- get_class($this)
- ));
- }
- }
- }
- }
- return $methodPriorities;
- }
- /**
- * Execute the resource, that is, find the correct resource method to call
- * based upon the request and then call it.
- *
- * @return Tonic\Response
- */
- public function exec()
- {
- // get the annotation metadata for this resource
- $resourceMetadata = $this->app->getResourceMetadata($this);
- $methodPriorities = $this->calculateMethodPriorities($resourceMetadata);
- $methodName = null;
- $bestMatch = -2;
- foreach ($methodPriorities as $name => $priority) {
- if ($priority['value'] > $bestMatch) {
- $bestMatch = $priority['value'];
- $methodName = $name;
- }
- }
- if (!$methodName) {
- throw new Exception;
- } elseif (isset($methodPriorities[$methodName]['response'])) {
- $response = Response::create($methodPriorities[$methodName]['response']);
- } elseif (isset($methodPriorities[$methodName]['exception'])) {
- throw $methodPriorities[$methodName]['exception'];
- } else {
- if (isset($this->before[$methodName])) {
- foreach ($this->before[$methodName] as $action) {
- call_user_func($action, $this->request, $methodName);
- }
- }
- $response = Response::create(call_user_func_array(array($this, $methodName), $this->params));
- if (isset($this->after[$methodName])) {
- foreach ($this->after[$methodName] as $action) {
- call_user_func($action, $response, $methodName);
- }
- }
- }
- return $response;
- }
- /**
- * Add a function to execute on the request before the resource method is called
- *
- * @param callable $action
- */
- protected function before($action)
- {
- if (is_callable($action)) {
- $this->before[$this->currentMethodName][] = $action;
- }
- }
- /**
- * Add a function to execute on the response after the resource method is called
- *
- * @param callable $action
- */
- protected function after($action)
- {
- if (is_callable($action)) {
- $this->after[$this->currentMethodName][] = $action;
- }
- }
- /**
- * HTTP method condition must match request method
- * @param str $method
- */
- protected function method($method)
- {
- if (strtolower($this->request->method) != strtolower($method))
- throw new MethodNotAllowedException('No matching method for HTTP method "'.$this->request->method.'"');
- }
- /**
- * Higher priority method takes precident over other matches
- * @param int $priority
- */
- protected function priority($priority)
- {
- return intval($priority);
- }
- /**
- * Accepts condition mimetype must match request content type
- * @param str $mimetype
- */
- protected function accepts($mimetype)
- {
- if (strtolower($this->request->contentType) != strtolower($mimetype)) {
- throw new UnsupportedMediaTypeException('No matching method for content type "'.$this->request->contentType.'"');
- }
- }
- /**
- * Provides condition mimetype must be in request accept array, returns a number
- * based on the priority of the match.
- * @param str $mimetype
- * @return int
- */
- protected function provides($mimetype)
- {
- if (count($this->request->accept) == 0) return 0;
- $pos = array_search($mimetype, $this->request->accept);
- if ($pos === FALSE) {
- if (in_array('*/*', $this->request->accept)) {
- return 0;
- } else {
- throw new NotAcceptableException('No matching method for response type "'.join(', ', $this->request->accept).'"');
- }
- } else {
- $this->after(function ($response) use ($mimetype) {
- $response->contentType = $mimetype;
- });
- return count($this->request->accept) - $pos;
- }
- }
- /**
- * Lang condition language code must be in request accept lang array, returns a number
- * based on the priority of the match.
- * @param str $language
- * @return int
- */
- protected function lang($language)
- {
- $pos = array_search($language, $this->request->acceptLanguage);
- if ($pos === FALSE)
- throw new NotAcceptableException('No matching method for response type "'.join(', ', $this->request->acceptLanguage).'"');
- return count($this->request->acceptLanguage) - $pos;
- }
- /**
- * Set cache control header on response
- * @param int length Number of seconds to cache the response for
- */
- protected function cache($length)
- {
- $this->addResponseAction(function ($response) use ($length) {
- if ($length == 0) {
- $response->cacheControl = 'no-cache';
- } else {
- $response->cacheControl = 'max-age='.$length.', must-revalidate';
- }
- });
- }
- public function __toString()
- {
- $params = array();
- if (is_array($this->params)) {
- foreach ($this->params as $name => $value) {
- $params[] = $name.' = "'.$value.'"';
- }
- }
- $params = join(', ', $params);
- $metadata = $this->app->getResourceMetadata($this);
- $class = $metadata['class'];
- $uri = array();
- foreach ($metadata['uri'] as $u) {
- $uri[] = $u[0];
- }
- $uri = join(', ', $uri);
- try {
- $priorities = $this->calculateMethodPriorities($metadata);
- } catch (Exception $e) {}
- $methods = '';
- foreach ($metadata['methods'] as $methodName => $method) {
- $methods .= "\n\t".'[';
- if (isset($priorities[$methodName])) {
- if (isset($priorities[$methodName]['exception'])) {
- $methods .= get_class($priorities[$methodName]['exception']);
- } else {
- $methods .= $priorities[$methodName]['value'];
- }
- } else {
- $methods .= '-';
- }
- $methods .= '] '.$methodName;
- foreach ($method as $itemName => $items) {
- foreach ($items as $item) {
- $methods .= ' '.$itemName;
- if ($item) {
- $methods .= '="'.join(', ', $item).'"';
- }
- }
- }
- }
- return <<<EOF
- ==============
- Tonic\Resource
- ==============
- Class: $class
- URI regex: $uri
- Params: $params
- Methods: $methods
- EOF;
- }
- }