3
* Flight: An extensible micro-framework.
5
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
6
* @license MIT, http://flightphp.com/license
11
use flight\core\Loader;
12
use flight\core\Dispatcher;
15
* The Engine class contains the core functionality of the framework.
16
* It is responsible for loading an HTTP request, running the assigned services,
17
* and generating an HTTP response.
39
protected $dispatcher;
44
public function __construct() {
45
$this->vars = array();
47
$this->loader = new Loader();
48
$this->dispatcher = new Dispatcher();
54
* Handles calls to class methods.
56
* @param string $name Method name
57
* @param array $params Method parameters
58
* @return mixed Callback results
60
public function __call($name, $params) {
61
$callback = $this->dispatcher->get($name);
63
if (is_callable($callback)) {
64
return $this->dispatcher->run($name, $params);
67
$shared = (!empty($params)) ? (bool)$params[0] : true;
69
return $this->loader->load($name, $shared);
72
/*** Core Methods ***/
75
* Initializes the framework.
77
public function init() {
78
static $initialized = false;
82
$this->vars = array();
83
$this->loader->reset();
84
$this->dispatcher->reset();
87
// Register default components
88
$this->loader->register('request', '\flight\net\Request');
89
$this->loader->register('response', '\flight\net\Response');
90
$this->loader->register('router', '\flight\net\Router');
91
$this->loader->register('view', '\flight\template\View', array(), function($view) use ($self) {
92
$view->path = $self->get('flight.views.path');
95
// Register framework methods
97
'start','stop','route','halt','error','notFound',
98
'render','redirect','etag','lastModified','json','jsonp'
100
foreach ($methods as $name) {
101
$this->dispatcher->set($name, array($this, '_'.$name));
104
// Default configuration settings
105
$this->set('flight.base_url', null);
106
$this->set('flight.handle_errors', true);
107
$this->set('flight.log_errors', false);
108
$this->set('flight.views.path', './views');
114
* Enables/disables custom error handling.
116
* @param bool $enabled True or false
118
public function handleErrors($enabled)
121
set_error_handler(array($this, 'handleError'));
122
set_exception_handler(array($this, 'handleException'));
125
restore_error_handler();
126
restore_exception_handler();
131
* Custom error handler. Converts errors into exceptions.
133
* @param int $errno Error number
134
* @param int $errstr Error string
135
* @param int $errfile Error file name
136
* @param int $errline Error file line number
137
* @throws \ErrorException
139
public function handleError($errno, $errstr, $errfile, $errline) {
140
if ($errno & error_reporting()) {
141
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
146
* Custom exception handler. Logs exceptions.
148
* @param \Exception $e Thrown exception
150
public function handleException(\Exception $e) {
151
if ($this->get('flight.log_errors')) {
152
error_log($e->getMessage());
159
* Maps a callback to a framework method.
161
* @param string $name Method name
162
* @param callback $callback Callback function
163
* @throws \Exception If trying to map over a framework method
165
public function map($name, $callback) {
166
if (method_exists($this, $name)) {
167
throw new \Exception('Cannot override an existing framework method.');
170
$this->dispatcher->set($name, $callback);
174
* Registers a class to a framework method.
176
* @param string $name Method name
177
* @param string $class Class name
178
* @param array $params Class initialization parameters
179
* @param callback $callback Function to call after object instantiation
180
* @throws \Exception If trying to map over a framework method
182
public function register($name, $class, array $params = array(), $callback = null) {
183
if (method_exists($this, $name)) {
184
throw new \Exception('Cannot override an existing framework method.');
187
$this->loader->register($name, $class, $params, $callback);
191
* Adds a pre-filter to a method.
193
* @param string $name Method name
194
* @param callback $callback Callback function
196
public function before($name, $callback) {
197
$this->dispatcher->hook($name, 'before', $callback);
201
* Adds a post-filter to a method.
203
* @param string $name Method name
204
* @param callback $callback Callback function
206
public function after($name, $callback) {
207
$this->dispatcher->hook($name, 'after', $callback);
213
* @param string $key Key
216
public function get($key = null) {
217
if ($key === null) return $this->vars;
219
return isset($this->vars[$key]) ? $this->vars[$key] : null;
225
* @param mixed $key Key
226
* @param string $value Value
228
public function set($key, $value = null) {
229
if (is_array($key) || is_object($key)) {
230
foreach ($key as $k => $v) {
231
$this->vars[$k] = $v;
235
$this->vars[$key] = $value;
240
* Checks if a variable has been set.
242
* @param string $key Key
243
* @return bool Variable status
245
public function has($key) {
246
return isset($this->vars[$key]);
250
* Unsets a variable. If no key is passed in, clear all variables.
252
* @param string $key Key
254
public function clear($key = null) {
256
$this->vars = array();
259
unset($this->vars[$key]);
264
* Adds a path for class autoloading.
266
* @param string $dir Directory path
268
public function path($dir) {
269
$this->loader->addDirectory($dir);
272
/*** Extensible Methods ***/
275
* Starts the framework.
277
public function _start() {
280
$request = $this->request();
281
$response = $this->response();
282
$router = $this->router();
284
// Flush any existing output
285
if (ob_get_length() > 0) {
286
$response->write(ob_get_clean());
289
// Enable output buffering
292
// Enable error handling
293
$this->handleErrors($this->get('flight.handle_errors'));
295
// Disable caching for AJAX requests
296
if ($request->ajax) {
297
$response->cache(false);
300
// Allow post-filters to run
301
$this->after('start', function() use ($self) {
306
while ($route = $router->route($request)) {
307
$params = array_values($route->params);
309
$continue = $this->dispatcher->execute(
316
if (!$continue) break;
329
* Stops the framework and outputs the current response.
331
* @param int $code HTTP status code
333
public function _stop($code = 200) {
336
->write(ob_get_clean())
341
* Stops processing and returns a given response.
343
* @param int $code HTTP status code
344
* @param string $message Response message
346
public function _halt($code = 200, $message = '') {
347
$this->response(false)
354
* Sends an HTTP 500 response for any errors.
356
* @param \Exception Thrown exception
358
public function _error(\Exception $e) {
359
$msg = sprintf('<h1>500 Internal Server Error</h1>'.
364
$e->getTraceAsString()
368
$this->response(false)
373
catch (\Exception $ex) {
379
* Sends an HTTP 404 response when a URL is not found.
381
public function _notFound() {
382
$this->response(false)
385
'<h1>404 Not Found</h1>'.
386
'<h3>The page you have requested could not be found.</h3>'.
393
* Routes a URL to a callback function.
395
* @param string $pattern URL pattern to match
396
* @param callback $callback Callback function
397
* @param boolean $pass_route Pass the matching route object to the callback
399
public function _route($pattern, $callback, $pass_route = false) {
400
$this->router()->map($pattern, $callback, $pass_route);
404
* Redirects the current request to another URL.
406
* @param string $url URL
407
* @param int $code HTTP status code
409
public function _redirect($url, $code = 303) {
410
$base = $this->get('flight.base_url');
412
if ($base === null) {
413
$base = $this->request()->base;
416
// Append base url to redirect url
417
if ($base != '/' && strpos($url, '://') === false) {
418
$url = preg_replace('#/+#', '/', $base.'/'.$url);
421
$this->response(false)
423
->header('Location', $url)
429
* Renders a template.
431
* @param string $file Template file
432
* @param array $data Template data
433
* @param string $key View variable name
435
public function _render($file, $data = null, $key = null) {
437
$this->view()->set($key, $this->view()->fetch($file, $data));
440
$this->view()->render($file, $data);
445
* Sends a JSON response.
447
* @param mixed $data JSON data
448
* @param int $code HTTP status code
449
* @param bool $encode Whether to perform JSON encoding
451
public function _json($data, $code = 200, $encode = true) {
452
$json = ($encode) ? json_encode($data) : $data;
454
$this->response(false)
456
->header('Content-Type', 'application/json')
462
* Sends a JSONP response.
464
* @param mixed $data JSON data
465
* @param string $param Query parameter that specifies the callback name.
466
* @param int $code HTTP status code
467
* @param bool $encode Whether to perform JSON encoding
469
public function _jsonp($data, $param = 'jsonp', $code = 200, $encode = true) {
470
$json = ($encode) ? json_encode($data) : $data;
472
$callback = $this->request()->query[$param];
474
$this->response(false)
476
->header('Content-Type', 'application/javascript')
477
->write($callback.'('.$json.');')
482
* Handles ETag HTTP caching.
484
* @param string $id ETag identifier
485
* @param string $type ETag type
487
public function _etag($id, $type = 'strong') {
488
$id = (($type === 'weak') ? 'W/' : '').$id;
490
$this->response()->header('ETag', $id);
492
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
493
$_SERVER['HTTP_IF_NONE_MATCH'] === $id) {
499
* Handles last modified HTTP caching.
501
* @param int $time Unix timestamp
503
public function _lastModified($time) {
504
$this->response()->header('Last-Modified', date(DATE_RFC1123, $time));
506
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
507
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time) {