Do not call RootControllerDelegate through WeakInterface.
[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->Get();
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 $this->delegate->OnInitialRequest($this->request, $this->response);
117
118 // Dispatch the request to an Action.
119 $this->RouteRequest($this->request);
120
121 // When control returns here, all actions have been invoked and it's time
122 // to start the output filter and exit.
123 $this->Stop();
124 }
125
126 /*!
127 Prevents any other Actions from executing. This starts the OutputFilter and
128 then exits.
129 */
130 public function Stop()
131 {
132 $this->delegate->WillStop($this->request, $this->response);
133 $this->output_filter->FilterOutput($this->request, $this->response);
134 $this->_Exit();
135 }
136
137 /*!
138 Sets the response code and stops the controller. Returns void.
139 */
140 public function StopWithCode($code)
141 {
142 $this->response->response_code = $code;
143 $this->Stop();
144 }
145
146 /*!
147 Sets the response code to HTTP 302 FOUND and redirects the page to a new
148 location.
149 @param string The destination location of the redirect.
150 */
151 public function StopWithRedirect($location)
152 {
153 $this->response->headers['Location'] = $location;
154 $this->StopWithCode(ResponseCode::FOUND);
155 }
156
157
158 /*!
159 Wrapper around PHP exit().
160 */
161 protected function _Exit()
162 {
163 exit;
164 }
165
166 /*!
167 Invoked by Run() and can be invoked by others to evaluate and perform the
168 lookup in the UrlMap. This then calls InvokeAction().
169 @param Request The Request whose URL will be routed
170 */
171 public function RouteRequest(Request $request)
172 {
173 $this->delegate->WillRouteRequest($request, $this->response);
174
175 $url_map_value = $this->url_map->Evaluate($request);
176
177 $action = NULL;
178 if ($url_map_value)
179 $action = $this->url_map->LookupAction($url_map_value);
180
181 if (!$action) {
182 $this->response->response_code = ResponseCode::NOT_FOUND;
183 $this->Stop();
184 return;
185 }
186
187 $this->InvokeAction($action);
188 }
189
190 /*!
191 Used to run an Action and drive it through its states.
192 @param Action
193 */
194 public function InvokeAction(Action $action)
195 {
196 $this->delegate->WillInvokeAction($action, $this->request, $this->response);
197
198 $action->FilterRequest($this->request, $this->response);
199 $action->Invoke($this->request, $this->response);
200 $action->FilterResponse($this->request, $this->response);
201
202 $this->delegate->DidInvokeAction($action, $this->request, $this->response);
203 }
204
205 /*!
206 Performs a reverse-lookup in the UrlMap for the pattern/fragment for the
207 name of a given Action class.
208 @param string Class name.
209 */
210 public function LookupAction($class)
211 {
212 $map = $this->url_map->map();
213 foreach ($map as $pattern => $action) {
214 if ($action == $class)
215 return $pattern;
216 }
217 }
218
219 /*!
220 Given a relative path, return an absolute path from the root controller.
221 @param string The relative path for which a URL will be created
222 @param bool Include the HTTP scheme and host to create an RFC URL if true;
223 if false an absolute path will be returned.
224 */
225 public function MakeURL($new_path, $url = FALSE)
226 {
227 // Detect the common paths between the REQUEST_URI and the PATH_INFO. That
228 // common piece will be the path to the root controller.
229 $request_uri = $this->request()->data['_SERVER']['REQUEST_URI'];
230 $path_info = $this->request()->data['_SERVER']['PATH_INFO'];
231 if ($path_info === NULL)
232 $common_uri = substr($request_uri, 0, -1);
233 else
234 $common_uri = strstr($request_uri, $path_info, TRUE);
235
236 // If just constructing an absolute path, return that now.
237 if (!$url)
238 return $common_uri . $new_path;
239
240 // Otherwise, build the host part.
241 $url = 'http';
242 if (isset($this->request()->data['_SERVER']['HTTPS']) &&
243 $this->request()->data['_SERVER']['HTTPS'] == 'on') {
244 $url .= 's';
245 }
246 $url .= '://' . $this->request()->data['_SERVER']['HTTP_HOST'];
247
248 $port = $this->request()->data['_SERVER']['SERVER_PORT'];
249 if ($port != 80 && $port != 443)
250 $url .= ':' . $port;
251
252 $url .= $common_uri;
253 return $url . $new_path;
254 }
255 }
256
257 /*!
258 Delegate for the root controller. All methods are optional.
259 */
260 interface RootControllerDelegate
261 {
262 public function OnInitialRequest(Request $request, Response $response);
263
264 public function WillRouteRequest(Request $request, Response $response);
265
266 public function WillInvokeAction(Action $action, Request $request, Response $response);
267
268 public function DidInvokeAction(Action $action, Request $request, Response $response);
269
270 public function WillStop(Request $request, Response $response);
271 }