/** * Parses the url, and dispatches to the appropriate controller. * @param bool $skipControllerInitialization */ public function dispatch($skipControllerInitialization = false) { Profile::start('Dispatcher', 'Dispatching'); $contentTypes = array(); try { $controllerName = isset($_GET['controller']) ? trim($_GET['controller']) : $this->defaultControllerName; $controllerName = $this->controllerFromUrlSanitizer->sanitize($controllerName); $invalidControllerName = false; try { $controller = $this->controllerFactory->get($controllerName); } catch (ControllerFactoryException $e) { // Not failing just yet, so the model gets initialized. $invalidControllerName = true; $controller = $this->controllerFactory->get($this->defaultControllerName); } $model = new Model(); $controller->setModel($model); $controller->initModel(); $contentTypes = $this->getAcceptContentTypes($_SERVER['HTTP_ACCEPT']); try { if ($invalidControllerName) { ErrorCode::notFound(); } try { $errorDuringRender = null; $errorCode = null; // Try to dispatch to the actual action. $actionParameters = explode('/', isset($_GET['action']) ? $_GET['action'] : 'index'); $action = $actionParameters[0]; array_shift($actionParameters); if ($action[0] === '_') { throw new ErrorCode(ErrorCode::NOT_FOUND, 'Tried to access action with underscore.'); } $action = $this->actionFromUrlSanitizer->sanitize($action); try { // Check if the action is valid $reflectionClass = new ReflectionClass($controller); $actionMethod = $reflectionClass->getMethod($action); if ($action !== 'index' && (method_exists('Controller', $action) || !$actionMethod->isPublic() || $actionMethod->class !== get_class($controller))) { throw new DispatcherException(); } } catch (Exception $e) { throw new ErrorCode(ErrorCode::NOT_FOUND, 'Tried to access invalid action.'); } $controller->setAction($action); $parameters = array(); $stringParameters = array(); $i = 0; foreach ($actionMethod->getParameters() as $parameter) { $actionParameter = isset($actionParameters[$i]) ? $actionParameters[$i] : null; if ($actionParameter === null) { if (!$parameter->isDefaultValueAvailable()) { throw new ErrorCode(ErrorCode::BAD_REQUEST, 'Not all parameters supplied.'); } // Well: there is no more additional query, and apparently the rest of the parameters are optional, so continue. continue; } if (($parameterTypeClass = $parameter->getClass()) != false) { if (!$parameterTypeClass->isSubclassOf('RW_Type')) { throw new ErrorCode(ErrorCode::BAD_REQUEST, 'Invalid parameter type.'); } $parameterTypeClassName = $parameterTypeClass->getName(); $parameters[] = new $parameterTypeClassName($actionParameter); } else { $parameters[] = $actionParameter; } $stringParameters[] = $actionParameter; $i++; } $controller->setActionParameters($stringParameters); if (!$skipControllerInitialization) { $controller->initialize(); } // This actually calls the apropriate action. call_user_func_array(array($controller, $action), $parameters); $controller->extendModel(); try { $this->renderers->render($controller->getViewName(), $model, $this->notificationCenter, $this->theme->getTemplatesPath(), $contentTypes, $controller); } catch (Exception $e) { throw new ErrorCode(ErrorCode::INTERNAL_SERVER_ERROR, 'Error during render: ' . $e->getMessage()); } } catch (ErrorMessageException $e) { $errorDuringRender = true; $this->notificationCenter->addError($e->getMessage()); } catch (ErrorCode $e) { throw $e; } catch (Exception $e) { $additionalInfo = array(); $additionalInfo['controllerName'] = $controllerName; if (isset($action)) { $additionalInfo['action'] = $action; } $additionalInfo['exceptionThrown'] = get_class($e); $additionalInfo['error'] = $e->getMessage(); Log::warning($e->getMessage(), 'Dispatcher', $additionalInfo); throw new ErrorCode(ErrorCode::INTERNAL_SERVER_ERROR); } } catch (ErrorCode $e) { // All other exceptions have already been caught. $errorDuringRender = true; $errorCode = $e->getCode(); $e->writeHttpHeader(); if ($e->getMessage()) { Log::debug($e->getMessage(), 'Dispatcher'); } } if ($errorDuringRender) { $this->renderers->renderError($errorCode, $model, $this->notificationCenter, $this->theme->getTemplatesPath(), $contentTypes); } } catch (Exception $e) { try { Log::fatal('There has been a fatal error dispatching.', 'Dispatcher', array('error' => $e->getMessage())); $this->renderers->renderFatalError($this->notificationCenter, $this->theme->getTemplatesPath(), $contentTypes); } catch (Exception $e) { die('<h1 class="error">Fatal error...</h1>'); } } Profile::stop(); }