PageRenderTime 26ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/api/vendor/peej/tonic/src/Tonic/Resource.php

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