/flight/Engine.php
PHP | 507 lines | 245 code | 68 blank | 194 comment | 26 complexity | 401d970aabb21b6734b4de093a7d6f3d MD5 | raw file
- <?php
- /**
- * Flight: An extensible micro-framework.
- *
- * @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
- * @license MIT, http://flightphp.com/license
- */
- namespace flight;
- use flight\core\Loader;
- use flight\core\Dispatcher;
- /**
- * The Engine class contains the core functionality of the framework.
- * It is responsible for loading an HTTP request, running the assigned services,
- * and generating an HTTP response.
- */
- class Engine {
- /**
- * Stored variables.
- *
- * @var array
- */
- protected $vars;
- /**
- * Class loader.
- *
- * @var object
- */
- protected $loader;
- /**
- * Event dispatcher.
- *
- * @var object
- */
- protected $dispatcher;
- /**
- * Constructor.
- */
- public function __construct() {
- $this->vars = array();
- $this->loader = new Loader();
- $this->dispatcher = new Dispatcher();
- $this->init();
- }
- /**
- * Handles calls to class methods.
- *
- * @param string $name Method name
- * @param array $params Method parameters
- * @return mixed Callback results
- */
- public function __call($name, $params) {
- $callback = $this->dispatcher->get($name);
- if (is_callable($callback)) {
- return $this->dispatcher->run($name, $params);
- }
- $shared = (!empty($params)) ? (bool)$params[0] : true;
- return $this->loader->load($name, $shared);
- }
- /*** Core Methods ***/
- /**
- * Initializes the framework.
- */
- public function init() {
- static $initialized = false;
- $self = $this;
- if ($initialized) {
- $this->vars = array();
- $this->loader->reset();
- $this->dispatcher->reset();
- }
- // Register default components
- $this->loader->register('request', '\flight\net\Request');
- $this->loader->register('response', '\flight\net\Response');
- $this->loader->register('router', '\flight\net\Router');
- $this->loader->register('view', '\flight\template\View', array(), function($view) use ($self) {
- $view->path = $self->get('flight.views.path');
- });
- // Register framework methods
- $methods = array(
- 'start','stop','route','halt','error','notFound',
- 'render','redirect','etag','lastModified','json','jsonp'
- );
- foreach ($methods as $name) {
- $this->dispatcher->set($name, array($this, '_'.$name));
- }
- // Default configuration settings
- $this->set('flight.base_url', null);
- $this->set('flight.handle_errors', true);
- $this->set('flight.log_errors', false);
- $this->set('flight.views.path', './views');
- $initialized = true;
- }
- /**
- * Enables/disables custom error handling.
- *
- * @param bool $enabled True or false
- */
- public function handleErrors($enabled)
- {
- if ($enabled) {
- set_error_handler(array($this, 'handleError'));
- set_exception_handler(array($this, 'handleException'));
- }
- else {
- restore_error_handler();
- restore_exception_handler();
- }
- }
- /**
- * Custom error handler. Converts errors into exceptions.
- *
- * @param int $errno Error number
- * @param int $errstr Error string
- * @param int $errfile Error file name
- * @param int $errline Error file line number
- * @throws \ErrorException
- */
- public function handleError($errno, $errstr, $errfile, $errline) {
- if ($errno & error_reporting()) {
- throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
- }
- }
- /**
- * Custom exception handler. Logs exceptions.
- *
- * @param \Exception $e Thrown exception
- */
- public function handleException(\Exception $e) {
- if ($this->get('flight.log_errors')) {
- error_log($e->getMessage());
- }
- $this->error($e);
- }
- /**
- * Maps a callback to a framework method.
- *
- * @param string $name Method name
- * @param callback $callback Callback function
- * @throws \Exception If trying to map over a framework method
- */
- public function map($name, $callback) {
- if (method_exists($this, $name)) {
- throw new \Exception('Cannot override an existing framework method.');
- }
- $this->dispatcher->set($name, $callback);
- }
- /**
- * Registers a class to a framework method.
- *
- * @param string $name Method name
- * @param string $class Class name
- * @param array $params Class initialization parameters
- * @param callback $callback Function to call after object instantiation
- * @throws \Exception If trying to map over a framework method
- */
- public function register($name, $class, array $params = array(), $callback = null) {
- if (method_exists($this, $name)) {
- throw new \Exception('Cannot override an existing framework method.');
- }
- $this->loader->register($name, $class, $params, $callback);
- }
- /**
- * Adds a pre-filter to a method.
- *
- * @param string $name Method name
- * @param callback $callback Callback function
- */
- public function before($name, $callback) {
- $this->dispatcher->hook($name, 'before', $callback);
- }
- /**
- * Adds a post-filter to a method.
- *
- * @param string $name Method name
- * @param callback $callback Callback function
- */
- public function after($name, $callback) {
- $this->dispatcher->hook($name, 'after', $callback);
- }
- /**
- * Gets a variable.
- *
- * @param string $key Key
- * @return mixed
- */
- public function get($key) {
- return isset($this->vars[$key]) ? $this->vars[$key] : null;
- }
- /**
- * Sets a variable.
- *
- * @param mixed $key Key
- * @param string $value Value
- */
- public function set($key, $value = null) {
- if (is_array($key) || is_object($key)) {
- foreach ($key as $k => $v) {
- $this->vars[$k] = $v;
- }
- }
- else {
- $this->vars[$key] = $value;
- }
- }
- /**
- * Checks if a variable has been set.
- *
- * @param string $key Key
- * @return bool Variable status
- */
- public function has($key) {
- return isset($this->vars[$key]);
- }
- /**
- * Unsets a variable. If no key is passed in, clear all variables.
- *
- * @param string $key Key
- */
- public function clear($key = null) {
- if (is_null($key)) {
- $this->vars = array();
- }
- else {
- unset($this->vars[$key]);
- }
- }
- /**
- * Adds a path for class autoloading.
- *
- * @param string $dir Directory path
- */
- public function path($dir) {
- $this->loader->addDirectory($dir);
- }
- /*** Extensible Methods ***/
- /**
- * Starts the framework.
- */
- public function _start() {
- $dispatched = false;
- $self = $this;
- $request = $this->request();
- $response = $this->response();
- $router = $this->router();
- // Flush any existing output
- if (ob_get_length() > 0) {
- $response->write(ob_get_clean());
- }
- // Enable output buffering
- ob_start();
- // Enable error handling
- $this->handleErrors($this->get('flight.handle_errors'));
- // Disable caching for AJAX requests
- if ($request->ajax) {
- $response->cache(false);
- }
- // Allow post-filters to run
- $this->after('start', function() use ($self) {
- $self->stop();
- });
- // Route the request
- while ($route = $router->route($request)) {
- $params = array_values($route->params);
- $continue = $this->dispatcher->execute(
- $route->callback,
- $params
- );
- $dispatched = true;
- if (!$continue) break;
- $router->next();
- }
- if (!$dispatched) {
- $this->notFound();
- }
- }
- /**
- * Stops the framework and outputs the current response.
- *
- * @param int $code HTTP status code
- */
- public function _stop($code = 200) {
- $this->response()
- ->status($code)
- ->write(ob_get_clean())
- ->send();
- }
- /**
- * Stops processing and returns a given response.
- *
- * @param int $code HTTP status code
- * @param string $message Response message
- */
- public function _halt($code = 200, $message = '') {
- $this->response(false)
- ->status($code)
- ->write($message)
- ->send();
- }
- /**
- * Sends an HTTP 500 response for any errors.
- *
- * @param \Exception Thrown exception
- */
- public function _error(\Exception $e) {
- $msg = sprintf('<h1>500 Internal Server Error</h1>'.
- '<h3>%s (%s)</h3>'.
- '<pre>%s</pre>',
- $e->getMessage(),
- $e->getCode(),
- $e->getTraceAsString()
- );
- try {
- $this->response(false)
- ->status(500)
- ->write($msg)
- ->send();
- }
- catch (\Exception $ex) {
- exit($msg);
- }
- }
- /**
- * Sends an HTTP 404 response when a URL is not found.
- */
- public function _notFound() {
- $this->response(false)
- ->status(404)
- ->write(
- '<h1>404 Not Found</h1>'.
- '<h3>The page you have requested could not be found.</h3>'.
- str_repeat(' ', 512)
- )
- ->send();
- }
- /**
- * Routes a URL to a callback function.
- *
- * @param string $pattern URL pattern to match
- * @param callback $callback Callback function
- * @param boolean $pass_route Pass the matching route object to the callback
- */
- public function _route($pattern, $callback, $pass_route = false) {
- $this->router()->map($pattern, $callback, $pass_route);
- }
- /**
- * Redirects the current request to another URL.
- *
- * @param string $url URL
- * @param int $code HTTP status code
- */
- public function _redirect($url, $code = 303) {
- $base = $this->get('flight.base_url');
- if ($base === null) {
- $base = $this->request()->base;
- }
- // Append base url to redirect url
- if ($base != '/' && strpos($url, '://') === false) {
- $url = preg_replace('#/+#', '/', $base.'/'.$url);
- }
- $this->response(false)
- ->status($code)
- ->header('Location', $url)
- ->write($url)
- ->send();
- }
- /**
- * Renders a template.
- *
- * @param string $file Template file
- * @param array $data Template data
- * @param string $key View variable name
- */
- public function _render($file, $data = null, $key = null) {
- if ($key !== null) {
- $this->view()->set($key, $this->view()->fetch($file, $data));
- }
- else {
- $this->view()->render($file, $data);
- }
- }
- /**
- * Sends a JSON response.
- *
- * @param mixed $data JSON data
- * @param int $code HTTP status code
- * @param bool $encode Whether to perform JSON encoding
- */
- public function _json($data, $code = 200, $encode = true) {
- $json = ($encode) ? json_encode($data) : $data;
- $this->response(false)
- ->status($code)
- ->header('Content-Type', 'application/json')
- ->write($json)
- ->send();
- }
-
- /**
- * Sends a JSONP response.
- *
- * @param mixed $data JSON data
- * @param string $param Query parameter that specifies the callback name.
- * @param int $code HTTP status code
- * @param bool $encode Whether to perform JSON encoding
- */
- public function _jsonp($data, $param = 'jsonp', $code = 200, $encode = true) {
- $json = ($encode) ? json_encode($data) : $data;
- $callback = $this->request()->query[$param];
- $this->response(false)
- ->status($code)
- ->header('Content-Type', 'application/javascript')
- ->write($callback.'('.$json.');')
- ->send();
- }
- /**
- * Handles ETag HTTP caching.
- *
- * @param string $id ETag identifier
- * @param string $type ETag type
- */
- public function _etag($id, $type = 'strong') {
- $id = (($type === 'weak') ? 'W/' : '').$id;
- $this->response()->header('ETag', $id);
- if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
- $_SERVER['HTTP_IF_NONE_MATCH'] === $id) {
- $this->halt(304);
- }
- }
- /**
- * Handles last modified HTTP caching.
- *
- * @param int $time Unix timestamp
- */
- public function _lastModified($time) {
- $this->response()->header('Last-Modified', date(DATE_RFC1123, $time));
- if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
- strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time) {
- $this->halt(304);
- }
- }
- }