PageRenderTime 63ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/MiniMVC/MiniMVC/Dispatcher.php

https://github.com/tquensen/MiniMVC
PHP | 510 lines | 380 code | 65 blank | 65 comment | 118 complexity | 4ea4c4258227e782d0e03e99f0fc9f78 MD5 | raw file
  1. <?php
  2. /**
  3. * MiniMVC_Dispatcher delegates requests and route/widget calls to the right controllers and actions
  4. */
  5. class MiniMVC_Dispatcher
  6. {
  7. /**
  8. *
  9. * @var MiniMVC_Registry
  10. */
  11. protected $registry = null;
  12. public function __construct()
  13. {
  14. $this->registry = MiniMVC_Registry::getInstance();
  15. }
  16. /**
  17. *
  18. * @return string returns the parsed output of the current request
  19. */
  20. public function dispatch()
  21. {
  22. $protocol = (!isset($_SERVER['HTTPS']) || !$_SERVER['HTTPS'] || $_SERVER['HTTPS'] == 'off') ? 'http' : 'https';
  23. $host = $protocol.'://'.$_SERVER['HTTP_HOST'];
  24. $url = $host . $_SERVER['REQUEST_URI'];
  25. if (!empty($_SERVER['QUERY_STRING']) && substr($url, strlen($_SERVER['QUERY_STRING']) * -1) == $_SERVER['QUERY_STRING']) {
  26. $url = substr($url, 0, -1 + strlen($_SERVER['QUERY_STRING']) * -1);
  27. }
  28. $this->registry->settings->set('currentHost', $host);
  29. $this->registry->settings->set('currentUrl', $url);
  30. $this->registry->settings->set('currentUrlFull', $url . (!empty($_SERVER['QUERY_STRING']) ? '?'.$_SERVER['QUERY_STRING']:''));
  31. if (isset($_POST['REQUEST_METHOD'])) {
  32. $_SERVER['REQUEST_METHOD'] = strtoupper($_POST['REQUEST_METHOD']);
  33. }
  34. $method = (!empty($_SERVER['REQUEST_METHOD'])) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
  35. $currentLanguage = null;
  36. $currentApp = $this->registry->settings->get('currentApp');
  37. $route = null;
  38. if (!$currentApp || !$this->registry->settings->get('apps/'.$currentApp)) {
  39. $defaultApp = $this->registry->settings->get('config/defaultApp');
  40. if (!$redirectUrl = $this->registry->settings->get('apps/'.$defaultApp.'/baseurl')) {
  41. throw new Exception('No matching App found and no default app configured!', 404);
  42. }
  43. header('Location: ' . $redirectUrl);
  44. exit;
  45. }
  46. $appurls = $this->registry->settings->get('apps/'.$currentApp);
  47. if (isset($appurls['baseurlI18n'])) {
  48. if (is_array($appurls['baseurlI18n'])) {
  49. foreach ($appurls['baseurlI18n'] as $baseurlLang => $baseurl) {
  50. if (substr($appurls['baseurlI18n'], 0, 1) == '/') {
  51. $appurls['baseurlI18n'] = $host . $appurls['baseurlI18n'];
  52. }
  53. if (preg_match('#^' . $baseurl . '(?P<route>[^\?\#]*)$#', $url, $matches)) {
  54. $currentLanguage = $baseurlLang;
  55. $route = $matches['route'];
  56. }
  57. }
  58. } else {
  59. if (substr($appurls['baseurlI18n'], 0, 1) == '/') {
  60. $appurls['baseurlI18n'] = $host . $appurls['baseurlI18n'];
  61. }
  62. $languageFormat = $this->registry->settings->get('config/languageFormat', '[a-z]{2}_[A-Z]{2}');
  63. if (preg_match('#^' . str_replace(':lang:', '(?P<lang>'.$languageFormat.')', $appurls['baseurlI18n']) . '(?P<route>[^\?\#]*)$#', $url, $matches)) {
  64. $currentLanguage = $matches['lang'];
  65. $route = $matches['route'];
  66. }
  67. }
  68. }
  69. if ($route === null && isset($appurls['baseurl'])) {
  70. if (substr($appurls['baseurl'], 0, 1) == '/') {
  71. $appurls['baseurl'] = $host . $appurls['baseurl'];
  72. }
  73. if (preg_match('#^' . $appurls['baseurl'] . '(?P<route>[^\?\#]*)$#', $url, $matches)) {
  74. $route = $matches['route'];
  75. } else {
  76. $route = false;
  77. }
  78. }
  79. if ($currentLanguage) {
  80. if (!in_array($currentLanguage, $this->registry->settings->get('config/enabledLanguages', array())) || $currentLanguage == $this->registry->settings->get('config/defaultLanguage')) {
  81. if (!$redirectUrl = $this->registry->settings->get('apps/'.$currentApp.'/baseurl')) {
  82. throw new Exception('No baseurl for App '.$currentApp.' found!');
  83. }
  84. header('Location: ' . $redirectUrl . $route . (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : ''));
  85. exit;
  86. }
  87. } else {
  88. if (!$currentLanguage = $this->registry->settings->get('config/defaultLanguage')) {
  89. throw new Exception('No default language for App '.$currentApp.' found!');
  90. }
  91. }
  92. $this->registry->settings->set('currentLanguage', $currentLanguage);
  93. $this->registry->settings->set('requestedRoute', $route);
  94. $routes = $this->getRoutes();
  95. $routeData = null;
  96. if (!$route && $route !== false) {
  97. $defaultRoute = $this->registry->settings->get('config/defaultRoute');
  98. if ($defaultRoute && isset($routes[$defaultRoute])) {
  99. $routeName = $defaultRoute;
  100. $routeData = $routes[$defaultRoute];
  101. } else {
  102. throw new Exception('no route given and no valid default Route defined!', 404);
  103. }
  104. } else {
  105. $found = false;
  106. if ($route) {
  107. if ($routeCache = $this->registry->cache->get('routeCache/'.$method.' '.str_replace('/', '__', $route))) {
  108. $found = true;
  109. $routeName = $routeCache['route'];
  110. $params = $routeCache['params'];
  111. } else {
  112. foreach ($routes as $currentRoute => $currentRouteData) {
  113. if (isset($currentRouteData['active']) && !$currentRouteData['active']) {
  114. continue;
  115. }
  116. if (!isset($currentRouteData['route'])) {
  117. continue;
  118. }
  119. if (isset($currentRouteData['method']) && ((is_string($currentRouteData['method']) && strtoupper($currentRouteData['method']) != $method) || (is_array($currentRouteData['method']) && !in_array($method, array_map('strtoupper', $currentRouteData['method']))))) {
  120. continue;
  121. }
  122. if (preg_match($currentRouteData['routePatternGenerated'], $route, $matches)) {
  123. $params = (isset($currentRouteData['parameter'])) ? $currentRouteData['parameter'] : array();
  124. $anonymousParams = array();
  125. foreach ($matches as $paramKey => $paramValue) {
  126. if (!is_numeric($paramKey)) {
  127. if ($paramKey == 'anonymousParams') {
  128. foreach (explode('/', $paramValue) as $anonymousParam) {
  129. $anonymousParam = explode('-', $anonymousParam, 2);
  130. if (trim($anonymousParam[0]) && !isset($params[urldecode($anonymousParam[0])])) {
  131. $params[urldecode($anonymousParam[0])] = (isset($anonymousParam[1])) ? urldecode($anonymousParam[1]) : true;
  132. $anonymousParams[urldecode($anonymousParam[0])] = (isset($anonymousParam[1])) ? urldecode($anonymousParam[1]) : true;
  133. }
  134. }
  135. } elseif (trim($paramValue)) {
  136. $params[urldecode($paramKey)] = urldecode($paramValue);
  137. }
  138. }
  139. }
  140. $params = array_merge($params, $anonymousParams);
  141. $routeName = $currentRoute;
  142. //$routeData = $this->getRoute($currentRoute, $params);
  143. $found = true;
  144. $this->registry->cache->set('routeCache/'.$method.' '.str_replace('/', '__', $route), array('route' => $routeName, 'params' => $params));
  145. break;
  146. }
  147. }
  148. }
  149. }
  150. if (!$found) {
  151. throw new Exception('no valid route found!', 404);
  152. }
  153. }
  154. $this->registry->settings->set('currentRoute', $routeName);
  155. $this->registry->settings->set('currentRouteParameter', isset($params) ? $params : array());
  156. $this->registry->events->notify(new sfEvent($this, 'minimvc.init'));
  157. $content = $this->callRoute($routeName, (isset($params) ? $params : array()));
  158. return $this->registry->layout->prepare($content, $this->registry->settings->get('currentApp'));
  159. }
  160. public function getErrorPage(Exception $e)
  161. {
  162. $routes = $this->getRoutes();
  163. try {
  164. //try to handle 401, 403 and 404 exceptions
  165. switch ($e->getCode()) {
  166. case 401:
  167. $error401Route = $this->registry->settings->get('config/error401Route');
  168. if ($error401Route && isset($routes[$error401Route])) {
  169. $routeData = $routes[$error401Route];
  170. } else {
  171. throw new Exception('Not logged in and no 401 Route defined!');
  172. }
  173. break;
  174. case 403:
  175. $error403Route = $this->registry->settings->get('config/error403Route');
  176. if ($error403Route && isset($routes[$error403Route])) {
  177. $routeData = $routes[$error403Route];
  178. } else {
  179. throw new Exception('Insufficient rights and no 403 Route defined!');
  180. }
  181. break;
  182. case 404:
  183. $error404Route = $this->registry->settings->get('config/error404Route');
  184. if ($error404Route && isset($routes[$error404Route])) {
  185. $routeData = $routes[$error404Route];
  186. } else {
  187. throw new Exception('no valid route found and no valid 404 Route defined!');
  188. }
  189. break;
  190. default:
  191. //server error / rethrow exception
  192. throw $e;
  193. }
  194. $routeData['parameter']['exception'] = $e;
  195. $content = $this->call($routeData['controller'], $routeData['action'], $routeData['parameter']);
  196. return $this->registry->layout->prepare($content, $this->registry->settings->get('currentApp'));
  197. } catch (Exception $e) {
  198. //handle 50x errors
  199. $error500Route = $this->registry->settings->get('config/error500Route');
  200. if ($error500Route && isset($routes[$error500Route])) {
  201. $routeData = $routes[$error500Route];
  202. $routeData['parameter']['exception'] = $e;
  203. $content = $this->call($routeData['controller'], $routeData['action'], (isset($routeData['parameter']) ? $routeData['parameter'] : array()));
  204. return $this->registry->layout->prepare($content, $this->registry->settings->get('currentApp'));
  205. } else {
  206. throw new Exception('Exception was thrown and no 500 Route defined!');
  207. }
  208. }
  209. }
  210. /**
  211. *
  212. * @param string $route the name of an internal route
  213. * @param array $params the parameters for the route
  214. * @return array returns an array with route information
  215. */
  216. public function getRoute($route, $params = array())
  217. {
  218. if (!$routeData = $this->registry->settings->get('routes/'.$route, array())) {
  219. throw new Exception('Route "' . $route . '" does not exist!');
  220. }
  221. $routeData['parameter'] = (isset($routeData['parameter'])) ? array_merge($routeData['parameter'], (array)$params) : (array)$params;
  222. $event = new sfEvent($this, 'minimvc.dispatcher.filterRoute');
  223. $this->registry->events->filter($event, $routeData);
  224. $routeData = $event->getReturnValue();
  225. return $routeData;
  226. }
  227. /**
  228. *
  229. * @param string $route the name of an internal route
  230. * @param array $params the parameters for the route
  231. * @param bool $isMainRoute use or ignore the format/layout/... data of the route to set the layout
  232. * @return MiniMVC_View the prepared view class of the called action
  233. */
  234. public function callRoute($route, $params = array(), $isMainRoute = true)
  235. {
  236. $routeData = $this->getRoute($route, $params);
  237. if ($isMainRoute) {
  238. if (isset($routeData['format'])) {
  239. $this->registry->layout->setFormat($routeData['format']);
  240. } elseif(isset($routeData['parameter']['_format'])) {
  241. $this->registry->layout->setFormat($routeData['parameter']['_format']);
  242. }
  243. if (isset($routeData['layout'])) {
  244. $this->registry->layout->setLayout($routeData['layout']);
  245. }
  246. }
  247. if (isset($routeData['parameter']['_action'])) {
  248. $routeData['action'] = $routeData['parameter']['_action'];
  249. } elseif(!isset($routeData['action'])) {
  250. $routeData['action'] = 'index';
  251. }
  252. if (isset($routeData['parameter']['_controller'])) {
  253. if (isset($routeData['parameter']['_module'])) {
  254. $routeData['controller'] = ucfirst($routeData['parameter']['_module']) . '_' . ucfirst($routeData['parameter']['_controller']);
  255. } else {
  256. $routeData['controller'] = 'My_' . ucfirst($routeData['parameter']['_controller']);
  257. }
  258. } elseif(!isset($routeData['controller'])) {
  259. $routeData['controller'] = 'My_Default';
  260. }
  261. if (!isset($routeData['controller']) || !isset($routeData['action'])) {
  262. throw new Exception('Route "' . $route . '" is invalid (controller or action not set!', 404);
  263. }
  264. if (isset($routeData['rights']) && $routeData['rights'] && !$this->registry->guard->userHasRight($routeData['rights'])) {
  265. if (!$this->registry->guard->userHasRight('guest')) {
  266. throw new Exception('Insufficient rights', 403);
  267. } else {
  268. throw new Exception('Not logged in', 401);
  269. }
  270. }
  271. $event = new sfEvent($this, 'minimvc.dispatcher.finalizeRoute');
  272. $this->registry->events->filter($event, $routeData);
  273. $routeData = $event->getReturnValue();
  274. return $this->call($routeData['controller'], $routeData['action'], isset($routeData['parameter']) ? $routeData['parameter'] : array());
  275. }
  276. /**
  277. *
  278. * @param string $widget the name of an internal widget
  279. * @param array $params the parameters for the widget
  280. * @return array returns an array with widget information
  281. */
  282. public function getWidget($widget, $params = array())
  283. {
  284. if (!$widgetData = $this->registry->settings->get('widgets/'.$widget, array())) {
  285. throw new Exception('Widget "' . $widget . '" does not exist!', 404);
  286. }
  287. $widgetData['parameter'] = (isset($widgetData['parameter'])) ? array_merge($widgetData['parameter'], (array)$params) : (array)$params;
  288. if (isset($widgetData['assign'])) {
  289. foreach ((array) $widgetData['assign'] as $slotParam => $widgetParam) {
  290. if (isset($widgetData['parameter']['slot'][$slotParam])) {
  291. $widgetData['parameter'][$widgetParam] = $widgetData['parameter']['slot'][$slotParam];
  292. }
  293. }
  294. }
  295. return $widgetData;
  296. }
  297. /**
  298. *
  299. * @param string $widget the name of an internal widget
  300. * @param array $params the parameters for the widget
  301. * @return MiniMVC_View the prepared view class of the called widget
  302. */
  303. public function callWidget($widget, $params = array())
  304. {
  305. $widgetData = $this->getWidget($widget, $params);
  306. if (isset($widgetData['parameter']['_action'])) {
  307. $widgetData['action'] = $widgetData['parameter']['_action'];
  308. } elseif(!isset($widgetData['action'])) {
  309. $widgetData['action'] = 'index';
  310. }
  311. if (isset($widgetData['parameter']['_controller'])) {
  312. if (isset($widgetData['parameter']['_module'])) {
  313. $widgetData['controller'] = ucfirst($widgetData['parameter']['_module']) . '_' . ucfirst($widgetData['parameter']['_controller']);
  314. } else {
  315. $routeData['controller'] = 'My_' . ucfirst($widgetData['parameter']['_controller']);
  316. }
  317. } elseif(!isset($widgetData['controller'])) {
  318. $widgetData['controller'] = 'My_Default';
  319. }
  320. if (!isset($widgetData['controller']) || !isset($widgetData['action'])) {
  321. throw new Exception('Widget "' . $widget . '" is invalid (controller or action not set!', 404);
  322. }
  323. if (isset($widgetData['rights']) && $widgetData['rights'] && !$this->registry->guard->userHasRight($widgetData['rights'])) {
  324. if (!$this->registry->guard->userHasRight('guest')) {
  325. throw new Exception('Insufficient rights to call widget "' . $widget . '"!', 403);
  326. } else {
  327. throw new Exception('Insufficient rights to call widget "' . $widget . '"!', 401);
  328. }
  329. }
  330. return $this->call($widgetData['controller'], $widgetData['action'], $widgetData['parameter']);
  331. }
  332. /**
  333. *
  334. * @param string $task the name of an internal task
  335. * @param array $params the parameters for the task
  336. * @return array returns an array with task information
  337. */
  338. public function getTask($task, $params = array())
  339. {
  340. if (!$taskData = $this->registry->settings->get('tasks/'.$task, array())) {
  341. throw new Exception('Task "' . $task . '" does not exist!', 404);
  342. }
  343. if (isset($taskData['assign'])) {
  344. if (is_array($taskData['assign'])) {
  345. foreach ($taskData['assign'] as $k => $v) {
  346. if (isset($params[$k])) {
  347. $params[$v] = $params[$k];
  348. }
  349. }
  350. } elseif (is_string($taskData['assign']) && isset($params[0])) {
  351. $params[$taskData['assign']] = $params[0];
  352. }
  353. }
  354. $taskData['parameter'] = (isset($taskData['parameter'])) ? array_merge($taskData['parameter'], $params) : $params;
  355. return $taskData;
  356. }
  357. /**
  358. *
  359. * @param string $task the name of an internal task
  360. * @param array $params the parameters for the task
  361. * @return MiniMVC_View the prepared view class of the called task
  362. */
  363. public function callTask($task, $params = array())
  364. {
  365. $taskData = $this->getTask($task, $params);
  366. if (!isset($taskData['controller']) || !isset($taskData['action'])) {
  367. throw new Exception('Task "' . $task . '" is invalid (controller or action not set!', 404);
  368. }
  369. return $this->call($taskData['controller'], $taskData['action'], $taskData['parameter']);
  370. }
  371. /**
  372. *
  373. * @param string $controller the name of a controller
  374. * @param string $action the name of an action
  375. * @param array $params an array with parameters
  376. * @return MiniMVC_View the prepared view class of the called action
  377. */
  378. public function call($controller, $action, $params)
  379. {
  380. if (strpos($controller, '_') === false) {
  381. throw new Exception('Invalid controller "' . $controller . '"!', 404);
  382. }
  383. $controllerParts = explode('_', $controller);
  384. $controllerName = $controllerParts[0] . '_' . $controllerParts[1] . '_Controller';
  385. $actionName = $action . 'Action';
  386. if (!class_exists($controllerName)) {
  387. throw new Exception('Controller "' . $controller . '" does not exist!', 404);
  388. }
  389. if (!method_exists($controllerName, $actionName)) {
  390. throw new Exception('Action "' . $action . '" for Controller "' . $controller . '" does not exist!', 404);
  391. }
  392. $viewName = $this->registry->settings->get('config/classes/view', 'MiniMVC_View');
  393. $view = new $viewName($controllerParts[0], strtolower($controllerParts[1]).'/'.$action);
  394. $controllerClass = new $controllerName($view);
  395. $this->registry->events->notify(new sfEvent($controllerClass, 'minimvc.call', array('controller' => $controller, 'action' => $action, 'params' => $params)));
  396. $this->registry->events->notify(new sfEvent($controllerClass, strtolower($controllerParts[0]).'.'.strtolower($controllerParts[1]).'.'.strtolower($action).'.call', array('controller' => $controller, 'action' => $action, 'params' => $params)));
  397. $return = $controllerClass->$actionName($params);
  398. if(is_object($return) && $return instanceof $viewName) {
  399. return $return;
  400. } elseif ($return === false) {
  401. return $controllerClass->getView()->prepareEmpty();
  402. } elseif (is_string($return)) {
  403. return $controllerClass->getView()->prepareText($return);
  404. }
  405. return $controllerClass->getView();
  406. }
  407. /**
  408. *
  409. * @param string $route a name of an internal route
  410. * @param array $routeData information about the route
  411. * @return string returns a regular expression pattern to parse the called route
  412. */
  413. protected function getRoutes()
  414. {
  415. $cache = $this->registry->cache->get('routes');
  416. if ($cache) {
  417. return $cache;
  418. }
  419. $routes = $this->registry->settings->get('routes', array());
  420. foreach ($routes as $route => $routeData) {
  421. $routePattern = isset($routeData['routePattern']) ? $routeData['routePattern'] : str_replace(array('?','(',')','[',']','.'), array('\\?','(',')?','\\[','\\]','\\.'), $routeData['route']);
  422. if (isset($routeData['parameterPatterns'])) {
  423. $search = array();
  424. $replace = array();
  425. foreach ($routeData['parameterPatterns'] as $param => $regex) {
  426. $search[] = ':' . $param . ':';
  427. $replace[] = '(?P<' . $param . '>' . $regex . ')';
  428. }
  429. $routePattern = str_replace($search, $replace, $routePattern);
  430. }
  431. $routePattern = preg_replace('#:([^:]+):#i', '(?P<$1>[^\./]+)', $routePattern);
  432. if (!empty($routeData['allowAnonymous'])) {
  433. if (substr($route, -1) == '/') {
  434. $routePattern .= '(?P<anonymousParams>([^-/]+-[^/]+/)*)';
  435. } else {
  436. $routePattern .= '(?P<anonymousParams>(/[^-/]+-[^/]+)*)';
  437. }
  438. }
  439. $routePattern = '#^' . $routePattern . '$#';
  440. $routes[$route]['routePatternGenerated'] = $routePattern;
  441. }
  442. $this->registry->cache->set('routes', $routes);
  443. return $routes;
  444. }
  445. }