Initial commit

pull/11/head
Mike Cao 14 years ago
commit ce8dbfc435

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2011 Mike Cao <mike@mikecao.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,13 @@
# Flight
Flight is an extensible micro-framework for PHP.
## Example
include 'flight/Flight.php';
Flight::route('/', function(){
echo 'hello world!';
});
Flight::start();

@ -0,0 +1,546 @@
<?php
/**
* Flight: an extensible PHP micro-framework.
*
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license http://www.opensource.org/licenses/mit-license.php
* @version 0.1
*/
class Flight {
/**
* Stored variables.
*
* @var array
*/
protected static $vars = array();
/**
* Registered classes.
*
* @var array
*/
protected static $classes = array();
/**
* Mapped methods.
*
* @var array
*/
protected static $methods = array();
/**
* Method filters.
*
* @var array
*/
protected static $filters = array();
// Don't allow object instantiation
private function __construct() {}
private function __destruct() {}
private function __clone() {}
/**
* Handles calls to static methods.
*
* @param string $name Method name
* @param array $args Method parameters
*/
public static function __callStatic($name, $params) {
// Check if call is mapped to a method
if (isset(self::$methods[$name]) || method_exists(__CLASS__, '_'.$name)) {
$method = self::$methods[$name] ?: array(__CLASS__, '_'.$name);
// Run pre-filters
if (!empty(self::$filters['before'][$name])) {
self::filter(self::$filters['before'][$name], $params);
}
// Run requested method
$output = self::execute($method, $params);
// Run post-filters
if (!empty(self::$filters['after'][$name])) {
self::filter(self::$filters['after'][$name], $output);
}
return $output;
}
// Otherwise try to autoload class
return self::load($name, (bool)$params[0]);
}
/**
* Maps a callback to a framework method.
*
* @param string $name Method name
* @param callback $callback Callback function
*/
public static function map($name, $callback) {
if (method_exists(__CLASS__, $name)) {
throw new Exception('Cannot override an existing framework method.');
}
self::$methods[$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
*/
public static function register($name, $class, array $params = array(), $callback = null) {
if (method_exists(__CLASS__, $name)) {
throw new Exception('Cannot override an existing framework method.');
}
self::$classes[$name] = array($class, $params, $callback);
}
/**
* Loads a registered class.
*
* @param string $class Class name
* @param array $method List of methods to load
*/
public static function load($name, $shared = true) {
if (isset(self::$classes[$name])) {
list($class, $params, $callback) = self::$classes[$name];
$obj = ($shared) ?
self::getInstance($class, $params) :
self::getClass($class, $params);
if ($callback) self::execute($callback, $ref = array($obj));
return $obj;
}
return self::getInstance(ucfirst($name));
}
/**
* Adds a pre-filter to a method.
*
* @param string $name Method name
* @param callback $callback Callback function
*/
public static function before($name, $callback) {
self::$filters['before'][$name][] = $callback;
}
/**
* Adds a post-filter to a method.
*
* @param string $name Method name
* @param callback $callback Callback function
*/
public static function after($name, $callback) {
self::$filters['after'][$name][] = $callback;
}
/**
* Executes a callback function.
*
* @param callback $callback Callback function
* @param array $params Function parameters
* @return mixed Function results
*/
public static function execute($callback, &$params) {
if (is_callable($callback)) {
return is_array($callback) ?
self::invokeMethod($callback, $params) :
self::callFunction($callback, $params);
}
}
/**
* Executes a chain of method filters.
*
* @param array $filters Chain of filters
* @param array $params Method parameters
* @param mixed $data Method output
*/
public static function filter($filters, &$data) {
foreach ($filters as $callback) {
$continue = self::execute($callback, $params = array(&$data));
if ($continue === false) break;
}
}
/**
* Calls a function.
*
* @param string $func Name of function to call
* @param array $params Function parameters
*/
public static function callFunction($func, array &$params = array()) {
switch (count($params)) {
case 0:
return $func();
case 1:
return $func($params[0]);
case 2:
return $func($params[0], $params[1]);
case 3:
return $func($params[0], $params[1], $params[2]);
case 4:
return $func($params[0], $params[1], $params[2], $params[3]);
case 5:
return $func($params[0], $params[1], $params[2], $params[3], $params[4]);
default:
return call_user_func_array($func, $params);
}
}
/**
* Invokes a method.
*
* @param mixed $func Class method as either an array or string
* @param array $params Class initialization parameters
*/
public static function invokeMethod($func, array &$params = array()) {
list($class, $method) = $func;
switch (count($params)) {
case 0:
return $class::$method();
case 1:
return $class::$method($params[0]);
case 2:
return $class::$method($params[0], $params[1]);
case 3:
return $class::$method($params[0], $params[1], $params[2]);
case 4:
return $class::$method($params[0], $params[1], $params[2], $params[3]);
case 5:
return $class::$method($params[0], $params[1], $params[2], $params[3], $params[4]);
default:
return call_user_func_array($func, $params);
}
}
/**
* Gets a single instance of a class.
*
* @param string $class Class name
* @param array $params Class initialization parameters
*/
public static function getInstance($class, array $params = array()) {
static $instances = array();
if (!isset($instances[$class])) {
$instances[$class] = self::getClass($class, $params);
}
return $instances[$class];
}
/**
* Gets a class object.
*
* @param string $class Class name
* @param array $params Class initialization parameters
*/
public static function getClass($class, array $params = array()) {
switch (count($params)) {
case 0:
return new $class();
case 1:
return new $class($params[0]);
case 2:
return new $class($params[0], $params[1]);
case 3:
return new $class($params[0], $params[1], $params[2]);
case 4:
return new $class($params[0], $params[1], $params[2], $params[3]);
case 5:
return new $class($params[0], $params[1], $params[2], $params[3], $params[4]);
default:
$ref_class = new ReflectionClass($class);
return $ref_class->newInstanceArgs($params);
}
}
/**
* Gets a variable.
*
* @param string $key Key
* @return mixed
*/
public static function get($key) {
return self::$vars[$key];
}
/**
* Sets a variable.
*
* @param mixed $key Key
* @param string $value Value
*/
public static function set($key, $value = null) {
// If key is an array, save each key value pair
if (is_array($key)) {
foreach ($key as $k => $v) {
self::$vars[$k] = $v;
}
}
// If key is an object, save each property
else if (is_object($key)) {
foreach (get_object_vars($key) as $k => $v) {
self::$vars[$k] = $v;
}
}
else if (is_string($key)) {
self::$vars[$key] = $value;
}
}
/**
* Checks if a variable exists.
*
* @param string $key Key
* @return bool Variable status
*/
public static function exists($key) {
return isset(self::$vars[$key]);
}
/**
* Unsets a variable. If no key is passed in, clear all variables.
*
* @param string $key Key
*/
public static function clear($key = null) {
if (is_null($key)) {
self::$vars = array();
}
else {
unset(self::$vars[$key]);
}
}
/**
* Initializes the framework.
*/
public static function init() {
static $initialized = false;
if (!$initialized) {
// Register autoloader
spl_autoload_register(array(__CLASS__, 'autoload'));
// Handle errors internally
set_error_handler(array(__CLASS__, 'handleError'));
// Handle exceptions internally
set_exception_handler(array(__CLASS__, 'handleException'));
// Turn off notices
error_reporting (E_ALL ^ E_NOTICE);
// Fix magic quotes
if (get_magic_quotes_gpc()) {
$func = function ($value) use (&$func) {
return is_array($value) ? array_map($func, $value) : stripslashes($value);
};
$_GET = array_map($func, $_GET);
$_POST = array_map($func, $_POST);
$_COOKIE = array_map($func, $_COOKIE);
}
// Enable output buffering
ob_start();
$initialized = true;
}
}
/**
* Autoloads classes.
*
* @param string $class Class name
*/
public static function autoload($class) {
$file = str_replace('\\', '/', str_replace('_', '/', $class)).'.php';
$base = (strpos($file, '/') === false) ? __DIR__ : (self::get('flight.lib.path') ?: '.');
if (file_exists($base.'/'.$file)) {
require $base.'/'.$file;
}
else {
throw new Exception('Unable to load file: '.$base.'/'.$file);
}
}
/**
* Custom error handler.
*/
public static function handleError($errno, $errstr, $errfile, $errline) {
if (in_array($errno, array(E_USER_ERROR, E_RECOVERABLE_ERROR))) {
static::error(new ErrorException($errstr, 0, $errno, $errfile, $errline));
}
}
/**
* Custom exception handler.
*/
public static function handleException(Exception $e) {
try {
static::error($e);
}
catch (Exception $ex) {
exit(
'<h1>500 Internal Server Error</h1>'.
'<h3>'.$ex->getMessage().'</h3>'.
'<pre>'.$ex->getTraceAsString().'</pre>'
);
}
}
/**
* Routes a URL to a callback function.
*
* @param string $pattern URL pattern to match
* @param callback $callback Callback function
*/
public static function _route($pattern, $callback) {
self::router()->map($pattern, $callback);
}
/**
* Start the framework.
*/
public static function _start() {
// Route the request
$result = self::router()->route(self::request());
if ($result !== false) {
list($callback, $params) = $result;
self::execute($callback, $params);
}
else {
self::notFound();
}
// Disable caching for AJAX requests
if (self::request()->isAjax) {
self::response()->cache(false);
}
// Allow post-filters to run
self::after('start', array(__CLASS__, 'stop'));
}
/**
* Stops the framework and outputs the current response.
*/
public static function _stop() {
self::response()->
write(ob_get_clean())->
send();
}
/**
* Stops processing and returns a given response.
*
* @param int $code HTTP status code
* @param int $msg Response text
*/
public static function _halt($code = 200, $text = '') {
self::response(false)->
status($code)->
write($text)->
cache(false)->
send();
}
/**
* Sends an HTTP 500 response for any errors.
*
* @param object $ex Exception
*/
public static function _error(Exception $e) {
self::response(false)->
status(500)->
write(
'<h1>500 Internal Server Error</h1>'.
'<h3>'.$e->getMessage().'</h3>'.
'<pre>'.$e->getTraceAsString().'</pre>'
)->
send();
}
/**
* Sends an HTTP 404 response when a URL is not found.
*/
public static function _notFound() {
self::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();
}
/**
* Redirects the current request to another URL.
*
* @param string $url URL
*/
public static function _redirect($url, $code = 303) {
self::response(false)->
status($code)->
header('Location', $url)->
write($url)->
send();
}
/**
* Renders a template.
*
* @param string $file Template file
* @param array $data Template data
*/
public static function _render($file, $data = null) {
self::view()->render($file, $data);
}
/**
* Handles ETag HTTP caching.
*
* @param string $id ETag identifier
* @param string $type ETag type
*/
public static function _etag($id, $type = 'strong') {
$id = (($type === 'weak') ? 'W/' : '').$id;
self::response()->header('ETag', $id);
if ($_SERVER['HTTP_IF_NONE_MATCH'] === $id) {
self::halt(304);
}
}
/**
* Handles last modified HTTP caching.
*
* @param int $time Unix timestamp
*/
public static function _lastModified($time) {
self::response()->header('Last-Modified', date(DATE_RFC1123, $time));
if (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time) {
self::halt(304);
}
}
}
// Initialize framework on include
Flight::init();
?>

@ -0,0 +1,66 @@
<?php
/**
* Flight: an extensible PHP micro-framework.
*
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license http://www.opensource.org/licenses/mit-license.php
* @version 0.1
*/
class Request {
/**
* Constructor.
*
* @param array $config Request configuration
*/
public function __construct($config = array()) {
// Default properties
if (empty($config)) {
$config = array(
'url' => $_SERVER['REQUEST_URI'],
'base' => str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])),
'method' => $_SERVER['REQUEST_METHOD'],
'referrer' => $_SERVER['HTTP_REFERER'],
'ipAddress' => $_SERVER['REMOTE_ADDR'],
'isAjax' => ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'),
'query' => array(),
'data' => $_POST,
'cookies' => $_COOKIE,
'files' => $_FILES
);
}
self::init($config);
}
/**
* Initialize request properties.
*
* @param array $properties Array of request properties
*/
public function init($properties) {
foreach ($properties as $name => $value) {
$this->{$name} = $value;
}
if ($this->base != '/' && strpos($this->url, $this->base) === 0) {
$this->url = substr($this->url, strlen($this->base));
}
$this->query = self::parseQuery($this->url);
}
/**
* Parse query parameters from a URL.
*/
public function parseQuery($url) {
$params = array();
$args = parse_url($url);
if (isset($args['query'])) {
parse_str($args['query'], $params);
}
return $params;
}
}
?>

@ -0,0 +1,165 @@
<?php
/**
* Flight: an extensible PHP micro-framework.
*
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license http://www.opensource.org/licenses/mit-license.php
* @version 0.1
*/
class Response {
protected $headers = array();
protected $status = 200;
protected $body;
public static $codes = array(
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported'
);
/**
* Sets the HTTP status of the response.
*
* @param int $code HTTP status code.
*/
public function status($code) {
if (array_key_exists($code, self::$codes)) {
if (strpos(php_sapi_name(), 'cgi') !== false) {
header('Status: '.$code.' '.self::$codes[$code], true);
}
else {
header(($_SERVER['SERVER_PROTOCOL'] ?: 'HTTP/1.1').' '.self::$codes[$code], true, $code);
}
}
else {
throw new Exception('Invalid status code.');
}
return $this;
}
/**
* Adds a header to the response.
*
* @param string|array $key Header name or array of names and values
* @param string $value Header value
*/
public function header($name, $value = null) {
if (is_array($name)) {
foreach ($name as $k => $v) {
$this->headers[$k] = $v;
}
}
else {
$this->headers[$name] = $value;
}
return $this;
}
/**
* Writes content to the response body.
*
* @param string $str Response content
*/
public function write($str) {
$this->body .= $str;
return $this;
}
/**
* Clears the response.
*/
public function clear() {
$this->headers = array();
$this->status = 200;
$this->body = '';
return $this;
}
/**
* Sets caching headers for the response.
*
* @param int|string $expires Expiration time
*/
public function cache($expires) {
if ($expires === false) {
$this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
$this->headers['Cache-Control'] = array(
'no-store, no-cache, must-revalidate',
'post-check=0, pre-check=0',
'max-age=0'
);
$this->headers['Pragma'] = 'no-cache';
}
else {
$expires = is_int($expires) ? $expires : strtotime($expires);
$this->headers['Expires'] = gmdate('D, d M Y H:i:s', $expires) . ' GMT';
$this->headers['Cache-Control'] = 'max-age='.($expires - time());
}
return $this;
}
/**
* Sends the response and exits the program.
*/
public function send() {
ob_end_clean();
if (!headers_sent()) {
foreach ($this->headers as $field => $value) {
if (is_array($value)) {
foreach ($value as $v) {
header($field.': '.$v);
}
}
else {
header($field.': '.$value);
}
}
}
exit($this->body);
}
}
?>

@ -0,0 +1,103 @@
<?php
/**
* Flight: an extensible PHP micro-framework.
*
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license http://www.opensource.org/licenses/mit-license.php
* @version 0.1
*/
class Router {
protected $routes = array();
/**
* Maps a URL pattern to a callback function.
*
* @param string $pattern URL pattern to match
* @param callback $callback Callback function
*/
public function map($pattern, $callback) {
list($method, $url) = explode(' ', trim($pattern), 2);
if (!is_null($url)) {
foreach (explode('|', $method) as $value) {
$this->routes[$value][$url] = $callback;
}
}
else {
$this->routes['*'][$pattern] = $callback;
}
}
/**
* Tries to match a requst to a route. Also parses named parameters in the url.
*
* @param string $pattern URL pattern
* @param object $request Request object
*/
public function match($pattern, $url, array &$params = array()) {
$ids = array();
// Build the regex for matching
$regex = '/^'.implode('\/', array_map(
function($str) use (&$ids){
if ($str == '*') {
$str = '(.*)';
}
else if (@$str{0} == '@') {
if (preg_match('/@(\w+)(\:([^\/]*))?/', $str, $matches)) {
$ids[$matches[1]] = true;
return '(?P<'.$matches[1].'>'.(isset($matches[3]) ? $matches[3] : '[^(\/|\?)]*').')';
}
}
return $str;
},
explode('/', $pattern)
)).'\/?(?:\?.*)?$/i';
// Attempt to match route and named parameters
if (preg_match($regex, $url, $matches)) {
if (!empty($ids)) {
$params = array_intersect_key($matches, $ids);
}
return true;
}
return false;
}
/**
* Routes the current request.
*
* @param object $request Request object
*/
public function route(&$request) {
$params = array();
$routes = $this->routes[$request->method] + ($this->routes['*'] ?: array());
foreach ($routes as $pattern => $callback) {
if ($request->url === $pattern || self::match($pattern, $request->url, $params)) {
$request->matched = $pattern;
return array($callback, array($params));
}
}
return false;
}
/**
* Gets mapped routes.
*
* @return array Array of routes
*/
public function getRoutes() {
return $this->routes;
}
/**
* Resets the router.
*/
public function clear() {
$this->routes = array();
}
}
?>

@ -0,0 +1,67 @@
<?php
/**
* Flight: an extensible PHP micro-framework.
*
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
* @license http://www.opensource.org/licenses/mit-license.php
* @version 0.1
*/
class View {
protected $templatePath;
public function __construct($templatePath = null) {
$this->templatePath = $templatePath ?: './views';
}
/**
* Renders a template.
*
* @param string $file Template file
* @param array $data Template data
*/
public function render($file, $data = null) {
// Bind template data to view
if (!is_null($data)) {
if (is_array($data)) {
foreach ($data as $key => $value) {
$this->{$key} = $value;
}
}
else if (is_object($data)) {
foreach (get_object_vars($data) as $key => $value) {
$this->{$key} = $value;
}
}
}
// Display template
include $this->templatePath.'/'.((substr($file,-4) == '.php') ? $file : $file.'.php');
}
/**
* Gets the output of a template.
*
* @param string $file Template file
* @param array $data Template data
*/
public function fetch($file, $data = null) {
ob_start();
$this->render($file, $data);
$output = ob_get_contents();
ob_end_clean();
return $output;
}
/**
* Displays escaped output.
*
* @param string $str String to escape
*/
public function e($str) {
echo htmlentities($str);
}
}
?>
Loading…
Cancel
Save