. namespace hoplite\http; require_once HOPLITE_ROOT . '/http/interceptor.php'; require_once HOPLITE_ROOT . '/http/request.php'; require_once HOPLITE_ROOT . '/http/response.php'; require_once HOPLITE_ROOT . '/http/response_code.php'; /*! The FrontController is meant to be invoked from the index.php of the application. */ class FrontController { /*! @var Request */ private $request = NULL; /*! @var Response */ private $response = NULL; /*! @var UrlMap */ private $url_map = NULL; /*! @var OutputFilter */ private $output_filter = NULL; /*! @var Array */ private $interceptors = []; /*! Creates the controller with the request context information, typicallhy from the global scope ($GLOBALS), but can be injected for testing. @param array PHP globals array */ public function __construct(array $globals) { $this->response = new Response(); $this->request = new Request(); $this->request->data = array( '_GET' => &$globals['_GET'], '_POST' => &$globals['_POST'], '_COOKIE' => &$globals['_COOKIE'], '_SERVER' => &$globals['_SERVER'] ); } /*! Accessors */ public function request() { return $this->request; } public function response() { return $this->response; } /*! Sets the UrlMap. */ public function set_url_map(UrlMap $url_map) { $this->url_map = $url_map; } /*! Sest the Output Filter. */ public function set_output_filter(OutputFilter $output_filter) { $this->output_filter = $output_filter; } /*! Registers an Interceptor that will be run before executing an Action. */ public function AddInterceptor(Interceptor $interceptor) { $this->interceptors[] = $interceptor; } /*! Called in index.php to process the current HTTP request. This initializes the Request object from its data and then routes the request to generate the response. */ public function ProcessRequest() { // The query rewriter module of the webserver rewrites a request from: // http://example.com/webapp/user/view/42 // to: // http://example.com/webapp/index.php/user/view/42 // ... which then becomes accessible from PATH_INFO. $data = $this->request->data['_SERVER']; if (isset($data['PATH_INFO'])) $url = $data['PATH_INFO']; else $url = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['DOCUMENT_URI']); $this->request->data['HOPLITE_PATH_INFO'] = $url; if ($url[0] == '/') $url = substr($url, 1); // Set the final pieces of the request. $this->request->url = $url; $this->request->http_method = $this->request->data['_SERVER']['REQUEST_METHOD']; // Extract any PUT data as POST params. if ($this->request->http_method == 'PUT') parse_str(file_get_contents('php://input'), $this->request->data['_POST']); // Register self as the active instance. $GLOBALS[__CLASS__] = $this; // Dispatch the request to an Action. $this->RouteRequest($this->request); // When control returns here, all actions have been invoked and it's time // to start the output filter and exit. $this->SendResponse(); } /*! Prevents any other Actions from executing. This starts the OutputFilter and then exits. */ public function SendResponse() { $this->output_filter->FilterOutput($this->request, $this->response); $this->_Exit(); } /*! Sets the response code and stops the controller. Returns void. */ public function SendResponseCode($code) { $this->response->response_code = $code; $this->SendResponse(); } /*! Sets the response code to HTTP 302 FOUND and redirects the page to a new location. @param string The destination location of the redirect. */ public function SendResponseRedirect($location) { $this->response->headers['Location'] = $location; $this->SendResponseCode(ResponseCode::FOUND); } /*! Wrapper around PHP exit(). */ protected function _Exit() { exit; } /*! Given an Request object, this executes the action for the corresponding URL. The action is located by performing a lookup in the UrlMap. Interceptors are run before invoking the action. @param Request The Request whose URL will be routed */ public function RouteRequest(Request $request) { $url_map_value = $this->url_map->Evaluate($request); $action = NULL; if ($url_map_value) $action = $this->url_map->LookupAction($url_map_value); foreach ($this->interceptors as $interceptor) $interceptor->DoIntercept($this, $action, $request, $this->response); if ($action) $this->InvokeAction($action); } /*! Invokes the action with the Controller's request and response objects. */ public function InvokeAction(Action $action) { $action->Invoke($this->request, $this->response); } /*! Performs a reverse-lookup in the UrlMap for the pattern/fragment for the name of a given Action class. @param string Class name. */ public function LookupAction($class) { $map = $this->url_map->map(); foreach ($map as $pattern => $action) { if ($action == $class) return $pattern; } } /*! Given a relative path, return an absolute path from the root controller. @param string The relative path for which a URL will be created @param bool Include the HTTP scheme and host to create an RFC URL if true; if false an absolute path will be returned. */ public function MakeURL($new_path, $url = FALSE) { // Detect the common paths between the REQUEST_URI and the PATH_INFO. That // common piece will be the path to the root controller. $request_uri = $this->request()->data['_SERVER']['REQUEST_URI']; $path_info = $this->request()->data['HOPLITE_PATH_INFO']; if (!$path_info) $common_uri = substr($request_uri, 0, -1); else $common_uri = strstr($request_uri, $path_info, TRUE); // If just constructing an absolute path, return that now. if (!$url) return $common_uri . $new_path; // Otherwise, build the host part. $url = 'http'; if (isset($this->request()->data['_SERVER']['HTTPS']) && $this->request()->data['_SERVER']['HTTPS'] == 'on') { $url .= 's'; } $url .= '://' . $this->request()->data['_SERVER']['HTTP_HOST']; $port = $this->request()->data['_SERVER']['SERVER_PORT']; if ($port != 80 && $port != 443) $url .= ':' . $port; $url .= $common_uri; return $url . $new_path; } }