PageRenderTime 48ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/php-frontend/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
  1. <?php
  2. namespace Tonic;
  3. /**
  4. * Model a HTTP resource
  5. */
  6. class Resource
  7. {
  8. protected $app, $request;
  9. public $params;
  10. private $currentMethodName;
  11. protected $before = array(), $after = array();
  12. public function __construct(Application $app, Request $request, array $urlParams)
  13. {
  14. $this->app = $app;
  15. $this->request = $request;
  16. $this->params = $urlParams;
  17. }
  18. /**
  19. * Get a URL parameter as defined by this resource and it's URI
  20. * @param str $name Name of the parameter
  21. * @return str
  22. */
  23. public function __get($name)
  24. {
  25. return isset($this->params[$name]) ? $this->params[$name] : NULL;
  26. }
  27. /**
  28. * Get the method name of the best matching resource method.
  29. *
  30. * @param str[] $resourceMetadata
  31. * @return str
  32. */
  33. private function calculateMethodPriorities($resourceMetadata)
  34. {
  35. $methodPriorities = array();
  36. if (isset($resourceMetadata['methods'])) {
  37. foreach ($resourceMetadata['methods'] as $key => $methodMetadata) {
  38. foreach ($methodMetadata as $conditionName => $conditions) { // process each method condition
  39. if (method_exists($this, $conditionName)) {
  40. $this->currentMethodName = $key;
  41. $success = false;
  42. foreach ($conditions as $params) {
  43. if (!isset($methodPriorities[$key]['value'])) {
  44. $methodPriorities[$key]['value'] = 0;
  45. }
  46. try {
  47. if (is_array($params)) {
  48. $condition = call_user_func_array(array($this, $conditionName), $params);
  49. } else {
  50. $condition = call_user_func(array($this, $conditionName), $params);
  51. }
  52. if ($condition === true) $condition = 1;
  53. if (is_numeric($condition)) {
  54. $methodPriorities[$key]['value'] += $condition;
  55. } elseif ($condition) {
  56. $methodPriorities[$key]['value']++;
  57. $methodPriorities[$key]['response'] = $condition;
  58. }
  59. $success = true;
  60. } catch (ConditionException $e) {
  61. unset($methodPriorities[$key]);
  62. break 2;
  63. } catch (Exception $e) {
  64. $error = $e;
  65. }
  66. }
  67. if (!$success) {
  68. $methodPriorities[$key]['exception'] = $error;
  69. $methodPriorities[$key]['value'] = -1;
  70. break;
  71. }
  72. } else {
  73. throw new \Exception(sprintf(
  74. 'Condition method "%s" not found in Resource class "%s"',
  75. $conditionName,
  76. get_class($this)
  77. ));
  78. }
  79. }
  80. }
  81. }
  82. return $methodPriorities;
  83. }
  84. /**
  85. * Execute the resource, that is, find the correct resource method to call
  86. * based upon the request and then call it.
  87. *
  88. * @return Tonic\Response
  89. */
  90. public function exec()
  91. {
  92. // get the annotation metadata for this resource
  93. $resourceMetadata = $this->app->getResourceMetadata($this);
  94. $methodPriorities = $this->calculateMethodPriorities($resourceMetadata);
  95. $methodName = null;
  96. $bestMatch = -2;
  97. foreach ($methodPriorities as $name => $priority) {
  98. if ($priority['value'] > $bestMatch) {
  99. $bestMatch = $priority['value'];
  100. $methodName = $name;
  101. }
  102. }
  103. if (!$methodName) {
  104. throw new Exception;
  105. } elseif (isset($methodPriorities[$methodName]['response'])) {
  106. $response = Response::create($methodPriorities[$methodName]['response']);
  107. } elseif (isset($methodPriorities[$methodName]['exception'])) {
  108. throw $methodPriorities[$methodName]['exception'];
  109. } else {
  110. if (isset($this->before[$methodName])) {
  111. foreach ($this->before[$methodName] as $action) {
  112. call_user_func($action, $this->request, $methodName);
  113. }
  114. }
  115. $response = Response::create(call_user_func_array(array($this, $methodName), $this->params));
  116. if (isset($this->after[$methodName])) {
  117. foreach ($this->after[$methodName] as $action) {
  118. call_user_func($action, $response, $methodName);
  119. }
  120. }
  121. }
  122. return $response;
  123. }
  124. /**
  125. * Add a function to execute on the request before the resource method is called
  126. *
  127. * @param callable $action
  128. */
  129. protected function before($action)
  130. {
  131. if (is_callable($action)) {
  132. $this->before[$this->currentMethodName][] = $action;
  133. }
  134. }
  135. /**
  136. * Add a function to execute on the response after the resource method is called
  137. *
  138. * @param callable $action
  139. */
  140. protected function after($action)
  141. {
  142. if (is_callable($action)) {
  143. $this->after[$this->currentMethodName][] = $action;
  144. }
  145. }
  146. /**
  147. * HTTP method condition must match request method
  148. * @param str $method
  149. */
  150. protected function method($method)
  151. {
  152. if (strtolower($this->request->method) != strtolower($method))
  153. throw new MethodNotAllowedException('No matching method for HTTP method "'.$this->request->method.'"');
  154. }
  155. /**
  156. * Higher priority method takes precident over other matches
  157. * @param int $priority
  158. */
  159. protected function priority($priority)
  160. {
  161. return intval($priority);
  162. }
  163. /**
  164. * Accepts condition mimetype must match request content type
  165. * @param str $mimetype
  166. */
  167. protected function accepts($mimetype)
  168. {
  169. if (strtolower($this->request->contentType) != strtolower($mimetype)) {
  170. throw new UnsupportedMediaTypeException('No matching method for content type "'.$this->request->contentType.'"');
  171. }
  172. }
  173. /**
  174. * Provides condition mimetype must be in request accept array, returns a number
  175. * based on the priority of the match.
  176. * @param str $mimetype
  177. * @return int
  178. */
  179. protected function provides($mimetype)
  180. {
  181. if (count($this->request->accept) == 0) return 0;
  182. $pos = array_search($mimetype, $this->request->accept);
  183. if ($pos === FALSE) {
  184. if (in_array('*/*', $this->request->accept)) {
  185. return 0;
  186. } else {
  187. throw new NotAcceptableException('No matching method for response type "'.join(', ', $this->request->accept).'"');
  188. }
  189. } else {
  190. $this->after(function ($response) use ($mimetype) {
  191. $response->contentType = $mimetype;
  192. });
  193. return count($this->request->accept) - $pos;
  194. }
  195. }
  196. /**
  197. * Lang condition language code must be in request accept lang array, returns a number
  198. * based on the priority of the match.
  199. * @param str $language
  200. * @return int
  201. */
  202. protected function lang($language)
  203. {
  204. $pos = array_search($language, $this->request->acceptLanguage);
  205. if ($pos === FALSE)
  206. throw new NotAcceptableException('No matching method for response type "'.join(', ', $this->request->acceptLanguage).'"');
  207. return count($this->request->acceptLanguage) - $pos;
  208. }
  209. /**
  210. * Set cache control header on response
  211. * @param int length Number of seconds to cache the response for
  212. */
  213. protected function cache($length)
  214. {
  215. $this->addResponseAction(function ($response) use ($length) {
  216. if ($length == 0) {
  217. $response->cacheControl = 'no-cache';
  218. } else {
  219. $response->cacheControl = 'max-age='.$length.', must-revalidate';
  220. }
  221. });
  222. }
  223. public function __toString()
  224. {
  225. $params = array();
  226. if (is_array($this->params)) {
  227. foreach ($this->params as $name => $value) {
  228. $params[] = $name.' = "'.$value.'"';
  229. }
  230. }
  231. $params = join(', ', $params);
  232. $metadata = $this->app->getResourceMetadata($this);
  233. $class = $metadata['class'];
  234. $uri = array();
  235. foreach ($metadata['uri'] as $u) {
  236. $uri[] = $u[0];
  237. }
  238. $uri = join(', ', $uri);
  239. try {
  240. $priorities = $this->calculateMethodPriorities($metadata);
  241. } catch (Exception $e) {}
  242. $methods = '';
  243. foreach ($metadata['methods'] as $methodName => $method) {
  244. $methods .= "\n\t".'[';
  245. if (isset($priorities[$methodName])) {
  246. if (isset($priorities[$methodName]['exception'])) {
  247. $methods .= get_class($priorities[$methodName]['exception']);
  248. } else {
  249. $methods .= $priorities[$methodName]['value'];
  250. }
  251. } else {
  252. $methods .= '-';
  253. }
  254. $methods .= '] '.$methodName;
  255. foreach ($method as $itemName => $items) {
  256. foreach ($items as $item) {
  257. $methods .= ' '.$itemName;
  258. if ($item) {
  259. $methods .= '="'.join(', ', $item).'"';
  260. }
  261. }
  262. }
  263. }
  264. return <<<EOF
  265. ==============
  266. Tonic\Resource
  267. ==============
  268. Class: $class
  269. URI regex: $uri
  270. Params: $params
  271. Methods: $methods
  272. EOF;
  273. }
  274. }