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