2 // @role Controller abstract class
3 // @title Controller Abstract Class
5 // @date 2005-11-26 5:26PM
6 // @desc Handles performing the actions required by the requests.
8 include_once('stdexception.php');
11 abstract class Controller
extends Canvas
{ // should implement the IController interface
13 public $controller; // this controller
14 public $action; // the action // defaults to 'index'
15 public $id; // the action ID
21 // protected variables
22 protected $request; // the request
23 protected $session; // the session
25 // action modification properties
26 protected $skip_authentication = array();
27 protected $authenticate = array();
30 public $error; // the error object
32 // constructor // consider making this 'final'
33 public function __construct(Request
$request) {
34 $this->request
= $request;
35 $this->session
= new Session();
36 $this->config
= $this->load_config();
37 $this->response
= new Response();
38 foreach($this->request
->route
->mapping() as $routing_symbol) {
39 $this->$routing_symbol = $this->request
->route
->$routing_symbol;
41 $this->environment
= Config2
::$environment;
45 /* @note If the user defined 'dispatch' as a controller action, it could
46 be disasterous. Instead, make it final so that it is not able
48 @consider Consider changing this from 'dispatch' to '_dispatch' or even
49 '__dispatch', to simulate the 'magic methods' for classes,
50 like '__get' and '__call'.
51 Consider handling action response (for view rendering) in a
53 @summary Dispatcher handles the majority of the overhead logic, such as
54 making sure the appropriate instance variables are set correctly,
55 executing the default handles/events/callbacks, and actually
56 calling the requested action's function (transparently).
57 Currently, the response of the $action is put in the $response
58 instance variable, within the array index of ['response'].
60 final public function dispatch($action, $id = null, $request = null) {
61 if(empty($action)) $action = "index";
62 $this->action
= $action;
65 // execute authentication & session handling event
66 $this->authenticate_request();
67 $this->handle_sessions();
69 $this->before_action(); // callback
72 // dispatch action, calling the exception handler if an exception
73 // which is thrown is not caught
74 // $this->$action($id); // deprecated because it only supports one single param
75 call_user_func(array($this, $action), $this->request
->params());
76 } catch(Exception
$e) {
77 $this->handle_exception($e);
80 if($this->after_action_performed
!= true) {
81 $this->after_action(); // callback (if not already performed)
82 $this->after_action_performed
= true;
85 // render (if not already rendered; also, consider layouts in views)
86 if(!$this->rendered
) $this->render();
88 return $this->response
;
92 protected function authenticate_request() {
93 try { // check if the action is to be authenticated... skip it if it isn't
94 if(is_array($this->skip_authentication
) && !empty($this->skip_authentication
) && !in_array($this->action
, $this->skip_authentication
)) {
95 return $this->authenticate();
96 } elseif(is_array($this->authenticate
) && !empty($this->authenticate
) && in_array($this->action
, $this->authenticate
)) {
97 return $this->authenticate();
99 // dep // $this->redirect_to(array("controller"=>"admin")); // handled within 'authenticate()'
101 } catch(Exception
$e) {
102 $this->handle_exception($e);
105 protected function authenticate() {
106 // this function is to be overwritten only if authentication is deemed necessary
110 protected function handle_exception($e) {
111 $exception_type = get_class($e); // will be the type of exception that was thrown
112 $handler = !empty($this->exception_handlers
[$exception_type]) ?
$this->exception_handlers
[$exception_type] : $this->exception_handlers
['*'];
113 if(!empty($handler)) {
116 Debug
::generic_exception_handler($e);
119 // terminate execution on exception after handling exception
123 protected function handle_sessions() {
124 // this function is to be overwritten only if specific session control is deemed necessary (rare!)
125 // this will require updating sessions and everything, particularly for flashes
128 public function __call($name, $params) {
129 // @consider Consider handling dynamic requests.
131 // catch-all function (throw an error)
132 throw new Exception("The <em>{$this->request->action}</em> action does not exist");
136 protected function render($params = null) {
137 // if the after_action() hasn't been performed yet (by the action explicitly calling the render() method), call it now
138 if($this->after_action_performed
!= true) {
139 $this->after_action();
140 $this->after_action_performed
= true;
143 $this->rendered
= true; // so that multiple render calls aren't made (such as when the user calls render() and the dispatcher knows not to call render() too)
145 $this->before_render(); // callback
147 // so that if no $params were specified, it would still be an array and an access violation
148 // wouldn't occur below when $params['foo'] was accessed
149 if($params == null) $params = array();
151 // render results to screen
152 // passes important data to templates
153 // maybe just creates the View object and does these things? yeah
154 // or, even better, create Response and send it, from the main dispatcher, to a view object?
156 // actually, respond with the template file
157 // also should provide the a 'url' and the basics of the header/caller information as properties of the 'response' object/[array]
158 $this->response
->layout
= (!empty($params['layout'])) ?
"{$params[layout]}.php" : (file_exists("views/{$this->controller}/layout.php") ?
"layout.php" : '../layout.php');
159 if($params['layout'] == "null") $this->response
->layout
= null;
160 if(empty($this->response
->template
)) $this->response
->template
= (!empty($params['template'])) ?
"{$params[template]}.php" : "{$this->action}.php"; // for smarty, took out 'views/{$this->controller}/' before because smarty keeps the template dir internally
161 $this->response
->url
= "{$this->controller}/{$this->action}/{$this->id}";
162 $this->response
->controller
= $this->controller
;
163 $this->response
->action
= $this->action
;
164 $this->response
->id
= $this->id
;
165 $this->response
->request
= $this->request
;
166 $this->response
->flash
= Session
::flash();
168 View
::render($this->response
, $params);
170 $this->after_render(); // callback
172 protected function render_partial($partial, $no_layout = true) {
173 $this->render(array("template"=>$partial, "layout"=>($no_layout ?
"null" : null)));
175 protected function render_template($template, $no_layout = false) {
176 $this->render(array("template"=>$template, "layout"=>($no_layout ?
"null" : null)));
178 protected function render_layout($layout) {
179 $this->render(array("layout"=>$layout));
181 protected function redirect_to($request, $flash = null) {
182 $this->before_redirect(); // callback
184 // $this->response->flash = Session::flash();
185 $_SESSION['flash'] = $this->flash
;
187 // if there's continuance to be done in the future, put it in the session data
188 if(!empty($request['continue_to'])) {
189 $this->session
->continue_to
= $request['continue_to'];
190 $this->session
->continue_to($request['continue_to']);
193 // if the direct url hasn't been set, do normally... otherwise if it has been
194 if(empty($request['url'])) {
195 // redirect to the appropriate protocol if specified
196 if(!empty($request['protocol'])) $protocol = $request['protocol']; else $protocol = $this->request
->protocol
;
197 unset($request['protocol']);
200 $location = "{$protocol}://{$this->request->host}/{$this->request->directory}";
203 $route = Router2
::url_for($request);
205 $request = sprintf($url, $location, $route);
207 // set defaults if not set
208 // if(empty($controller)) $controller = $this->controller;
209 // shouldn't happen... should forward to default action // if(empty($action)) $action = $this->action; // probably won't happen... doesn't make sense, does it?
213 // $request = "{$controller}/{$action}/{$id}";
214 // if(empty($id)) $request = "{$controller}/{$action}";
215 // if(empty($action)) $request = "{$controller}/";
217 $request = $request['url'];
220 // handle session closing to prevent data loss
224 header("Location: {$request}");
226 // not sure if this will ever happen, but you never know
227 $this->after_redirect(); // callback
230 $execution_time = round(microtime(true) - $GLOBALS['dispatch_time_begin'], 5);
231 $execution_memory = round(memory_get_usage()/1024, 2);
232 Debug
::log("Redirected {$_SERVER['PATH_INFO']} to {$path} ({$execution_time}sec/{$execution_memory}kb)", 'internal', 'notice', 'Controller');
234 // kill the rest of execution for this request
235 die("Action calls for redirection. Click <a href='{$request}'>here</a> if you are not automatically forwarded.");
239 protected function ajax_response($response) {
240 // log remote request
241 $execution_time = round(microtime(true) - $GLOBALS['dispatch_time_begin'], 5);
242 $execution_memory = round(memory_get_usage()/1024, 2);
243 Debug
::log("Remote request {$_SERVER['PATH_INFO']} ({$execution_time}sec/{$execution_memory}kb)", 'internal', 'notice', 'Controller');
245 if(is_array($response)) {
246 // full on xml response
247 $this->response
->response_id
= $response['id'];
248 $this->response
->units
= $response['units'];
250 'template'=>'../templates/ajax_response',
254 // plain text response
260 // @desc These allow for developers to simply assign values to $this->foo
261 // and have them automatically sent to the Response object and,
262 // ultimately, the View object.
263 protected function __get($key) {
264 return $this->properties
[$key];
265 return $this->response
->$key;
267 protected function __set($key, $value) {
268 return $this->properties
[$key] = $value;
269 return $this->response
->$key = $value;
272 // flash function (which replaces just using $this->flash = array())
273 protected function flash($message, $class, $params = array()) {
274 // graft the message and the class into optionally-specified the $params array (whereby more data can be passed on)
275 $params['message'] = $message;
276 $params['class'] = $class;
277 $this->flash
= $params;
281 protected function before_action() {}
282 protected function after_action() {}
283 protected function before_render() {} // @consider Consider moving this to the View class, or at least calling before_render() of the View as well... maybe
284 protected function after_render() {}
285 protected function before_redirect() {}
286 protected function after_redirect() {} // this is executed right before the header() call and the die()
289 public function __destruct() {
290 // possibly put some serialization code in here?
292 $_SESSION['flash'] = $this->flash
;
294 session_write_close();