Add RootController::StopWithCode()
[hoplite.git] / http / root_controller.php
1 <?php
2 // Hoplite
3 // Copyright (c) 2011 Blue Static
4 //
5 // This program is free software: you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or any later version.
8 //
9 // This program is distributed in the hope that it will be useful, but WITHOUT
10 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 // more details.
13 //
14 // You should have received a copy of the GNU General Public License along with
15 // this program. If not, see <http://www.gnu.org/licenses/>.
16
17 namespace hoplite\http;
18
19 require_once HOPLITE_ROOT . '/base/weak_interface.php';
20 require_once HOPLITE_ROOT . '/http/request.php';
21 require_once HOPLITE_ROOT . '/http/response.php';
22 require_once HOPLITE_ROOT . '/http/response_code.php';
23
24 /*!
25 The RootController is meant to be invoked from the index.php of the
26 application.
27 */
28 class RootController
29 {
30 /*! @var Request */
31 private $request = NULL;
32
33 /*! @var Response */
34 private $response = NULL;
35
36 /*! @var UrlMap */
37 private $url_map = NULL;
38
39 /*! @var OutputFilter */
40 private $output_filter = NULL;
41
42 /*! @var WeakInterface<RootControllerDelegate> */
43 private $delegate = NULL;
44
45 /*!
46 Creates the controller with the request context information, typicallhy
47 from the global scope ($GLOBALS), but can be injected for testing.
48 @param UrlMap The routing map
49 @param OutputFilter The object responsible for decorating output.
50 @param array& PHP globals array
51 */
52 public function __construct(array $globals)
53 {
54 $this->response = new Response();
55 $this->request = new Request();
56 $this->request->data = array(
57 '_GET' => &$globals['_GET'],
58 '_POST' => &$globals['_POST'],
59 '_COOKIE' => &$globals['_COOKIE'],
60 '_SERVER' => &$globals['_SERVER']
61 );
62 $this->delegate = new \hoplite\base\WeakInterface('hoplite\http\RootControllerDelegate');
63 }
64
65 /*! Accessors */
66 public function request() { return $this->request; }
67 public function response() { return $this->response; }
68
69 /*! Sets the UrlMap. */
70 public function set_url_map(UrlMap $url_map) { $this->url_map = $url_map; }
71
72 /*! Sest the Output Filter. */
73 public function set_output_filter(OutputFilter $output_filter)
74 {
75 $this->output_filter = $output_filter;
76 }
77
78 /*! Sets the delegate. */
79 public function set_delegate($delegate)
80 {
81 $this->delegate->Bind($delegate);
82 }
83
84 /*!
85 Createst the Request and Response that are used throughout the duration of
86 the execution.
87 */
88 public function Run()
89 {
90 // The query rewriter module of the webserver rewrites a request from:
91 // http://example.com/webapp/user/view/42
92 // to:
93 // http://example.com/webapp/index.php/user/view/42
94 // ... which then becomes accessible from PATH_INFO.
95 $data = $this->request->data['_SERVER'];
96 if (isset($data['PATH_INFO']))
97 $url = $data['PATH_INFO'];
98 else
99 $url = '/';
100 if ($url[0] == '/')
101 $url = substr($url, 1);
102
103 // Set the final pieces of the request.
104 $this->request->url = $url;
105 $this->request->http_method = $this->request->data['_SERVER']['REQUEST_METHOD'];
106
107 // Extract any PUT data as POST params.
108 if ($this->request->http_method == 'PUT')
109 parse_str(file_get_contents('php://input'), $this->request->data['_POST']);
110
111 // Register self as the active instance.
112 $GLOBALS[__CLASS__] = $this;
113
114 $this->delegate->OnInitialRequest($this->request, $this->response);
115
116 // Dispatch the request to an Action.
117 $this->RouteRequest($this->request);
118
119 // When control returns here, all actions have been invoked and it's time
120 // to start the output filter and exit.
121 $this->Stop();
122 }
123
124 /*!
125 Prevents any other Actions from executing. This starts the OutputFilter and
126 then exits.
127 */
128 public function Stop()
129 {
130 $this->delegate->WillStop($this->request, $this->response);
131 $this->output_filter->FilterOutput($this->request, $this->response);
132 $this->_Exit();
133 }
134
135 /*!
136 Sets the response code and stops the controller. Returns void.
137 */
138 public function StopWithCode($code)
139 {
140 $this->response->response_code = $code;
141 $this->Stop();
142 }
143
144 /*!
145 Wrapper around PHP exit().
146 */
147 protected function _Exit()
148 {
149 exit;
150 }
151
152 /*!
153 Invoked by Run() and can be invoked by others to evaluate and perform the
154 lookup in the UrlMap. This then calls InvokeAction().
155 @param Request The Request whose URL will be routed
156 */
157 public function RouteRequest(Request $request)
158 {
159 $this->delegate->WillRouteRequest($request, $this->response);
160
161 $url_map_value = $this->url_map->Evaluate($request);
162
163 $action = NULL;
164 if ($url_map_value)
165 $action = $this->url_map->LookupAction($url_map_value);
166
167 if (!$action) {
168 $this->response->response_code = ResponseCode::NOT_FOUND;
169 $this->Stop();
170 return;
171 }
172
173 $this->InvokeAction($action);
174 }
175
176 /*!
177 Used to run an Action and drive it through its states.
178 @param Action
179 */
180 public function InvokeAction(Action $action)
181 {
182 $this->delegate->WillInvokeAction($action, $this->request, $this->response);
183
184 $action->FilterRequest($this->request, $this->response);
185 $action->Invoke($this->request, $this->response);
186 $action->FilterResponse($this->request, $this->response);
187
188 $this->delegate->DidInvokeAction($action, $this->request, $this->response);
189 }
190
191 /*!
192 Performs a reverse-lookup in the UrlMap for the pattern/fragment for the
193 name of a given Action class.
194 @param string Class name.
195 */
196 public function LookupAction($class)
197 {
198 $map = $this->url_map->map();
199 foreach ($map as $pattern => $action) {
200 if ($action == $class)
201 return $pattern;
202 }
203 }
204
205 /*!
206 Given a relative path, return an absolute path from the root controller.
207 @param string The relative path for which a URL will be created
208 @param bool Include the HTTP scheme and host to create an RFC URL if true;
209 if false an absolute path will be returned.
210 */
211 public function MakeURL($new_path, $url = FALSE)
212 {
213 // Detect the common paths between the REQUEST_URI and the PATH_INFO. That
214 // common piece will be the path to the root controller.
215 $request_uri = $this->request()->data['_SERVER']['REQUEST_URI'];
216 $path_info = $this->request()->data['_SERVER']['PATH_INFO'];
217 if ($path_info === NULL)
218 $common_uri = substr($request_uri, 0, -1);
219 else
220 $common_uri = strstr($request_uri, $path_info, TRUE);
221
222 // If just constructing an absolute path, return that now.
223 if (!$url)
224 return $common_uri . $new_path;
225
226 // Otherwise, build the host part.
227 $url = 'http';
228 if (isset($this->request()->data['_SERVER']['HTTPS']) &&
229 $this->request()->data['_SERVER']['HTTPS'] == 'on') {
230 $url .= 's';
231 }
232 $url .= '://' . $this->request()->data['_SERVER']['HTTP_HOST'];
233
234 $port = $this->request()->data['_SERVER']['SERVER_PORT'];
235 if ($port != 80 && $port != 443)
236 $url .= ':' . $port;
237
238 $url .= $common_uri;
239 return $url . $new_path;
240 }
241 }
242
243 /*!
244 Delegate for the root controller. The controller uses WeakInterface to call
245 these methods, so they're all optional.
246 */
247 interface RootControllerDelegate
248 {
249 public function OnInitialRequest(Request $request, Response $response);
250
251 public function WillRouteRequest(Request $request, Response $response);
252
253 public function WillInvokeAction(Action $action, Request $request, Response $response);
254
255 public function DidInvokeAction(Action $action, Request $request, Response $response);
256
257 public function WillStop(Request $request, Response $response);
258 }