Qoob Portfolio
The open qoob is a php mvc framework created to speed up the process of creating dynamic sites. This is the code running this website!
The open qoob is a php mvc framework created to speed up the process of creating dynamic sites. This is the code running this website!
<?php
/**
* open qoob framework
*
* @author xero harrison <x@xero.nu>
* @copyright creative commons attribution-shareAlike 3.0 unported
* @license http://creativecommons.org/licenses/by-sa/3.0/
* @version 2.01.00
*/
class qoob {
/**
* famework info
*/
const
PACKAGE='open qoob',
VERSION='2.01.00';
/**
* error constants
*/
const
E_Handler='Missing Callback',
E_Pattern='Invalid routing pattern: %s',
E_Fatal='Fatal error: %s',
E_Open='Unable to open: %s',
E_Loading='Failed loading: %s',
E_Request='Invalid reqest type: %s',
E_Routes='No routes specified',
E_Method='Invalid method: %s',
E_Gateway='Not implemented: %s';
/**
* request types
*/
const
REQUESTS='SYNC|AJAX';
/**
* http verbs
*/
const
VERBS='GET|HEAD|POST|PUT|DELETE';
/**
* http status codes
* @see http://www.rfc-editor.org/rfc/rfc2616.txt
*/
const
// informational
HTTP_100='Continue',
HTTP_101='Switching Protocols',
// successful
HTTP_200='OK',
HTTP_201='Created',
HTTP_202='Accepted',
HTTP_203='Non-Authorative Information',
HTTP_204='No Content',
HTTP_205='Reset Content',
HTTP_206='Partial Content',
// redirection
HTTP_300='Multiple Choices',
HTTP_301='Moved Permanently',
HTTP_302='Found',
HTTP_303='See Other',
HTTP_304='Not Modified',
HTTP_305='Use Proxy',
HTTP_307='Temporary Redirect',
// client error
HTTP_400='Bad Request',
HTTP_401='Unauthorized',
HTTP_402='Payment Required',
HTTP_403='Forbidden',
HTTP_404='Not Found',
HTTP_405='Method Not Allowed',
HTTP_406='Not Acceptable',
HTTP_407='Proxy Authentication Required',
HTTP_408='Request Timeout',
HTTP_409='Conflict',
HTTP_410='Gone',
HTTP_411='Length Required',
HTTP_412='Precondition Failed',
HTTP_413='Request Entity Too Large',
HTTP_414='Request-URI Too Long',
HTTP_415='Unsupported Media Type',
HTTP_416='Requested Range Not Satisfiable',
HTTP_417='Exception Failed',
// server error
HTTP_500='Internal Server Error',
HTTP_501='Not Implemented',
HTTP_502='Bad Gateway',
HTTP_503='Service Unavailable',
HTTP_504='Gateway Timeout',
HTTP_505='HTTP Version Not Supported';
/**
* status
* echo http header and return status message
* @param int $code status code
* @return string status message
*/
function status($code) {
// account for system thrown exceptions
if(!defined('self::HTTP_'.$code)) {
$code = 500;
}
// no commandline headers
if (PHP_SAPI!='cli') {
header('HTTP/1.1 '.$code);
header('X-Powered-By: '.self::PACKAGE.'/'.self::VERSION);
}
library::set('STATUS.code', $code);
return @constant('self::HTTP_'.$code);
}
/**
* config
* parse a php ini into the library. autoload classes if defined.
* @param string $file file name
*/
function config($file) {
if(!file_exists($file)) {
throw new Exception(sprintf(self::E_Loading, $file), 500);
}
$ini = parse_ini_file($file, true);
if(count($ini)>0) {
foreach ($ini as $key => $val) {
if(is_array($val)) {
if(strtolower($key)=='autoload') {
foreach ($val as $k => $v) {
$this->load($v);
}
}
foreach ($val as $k => $v) {
library::set('CONFIG.'.strtoupper($key).'.'.$k, $v);
}
} else {
library::set('CONFIG.'.$key, $val);
}
}
}
}
/**
* load
* load namespace aware classes into the framework
* @param string $class class name
*/
function load($class) {
// nullbyte poisoning check
$class = str_replace(chr(0), '', $class);
if(class_exists($class)) {
// remove namespace from class name
$name = explode('\', $class);
$name = $name[count($name)-1];
if(!library::exists($name)) {
// create class and set a reference to it
library::set('CLASS.'.$name, new $class);
$this->$name = library::get('CLASS.'.$name);
}
} else {
throw new Exception(sprintf(self::E_Loading, $class), 500);
}
}
/**
* route
* add a route pattern
* @param string $pattern route
* @param mixed $handler closure function or class->method reference
*/
function route($pattern, $handler) {
if(empty($handler)) {
throw new Exception(self::E_Handler, 500);
}
$parts = explode(' ', trim($pattern));
foreach ($this->split($parts[0]) as $verb) {
if (!preg_match('/'.self::VERBS.'/', strtoupper($verb))) {
throw new Exception(sprintf(self::E_Gateway, $verb), 500);
}
if(!isset($parts[1])) {
throw new Exception(sprintf(self::E_Pattern, $pattern), 500);
}
$type = isset($parts[2])?str_replace(array('[',']'), '', strtoupper($parts[2])):'SYNC';
if (!preg_match('/'.self::REQUESTS.'/', $type)) {
throw new Exception(sprintf(self::E_Request, $type), 500);
}
library::set(
'ROUTES',
array(
'verb' => strtoupper($verb),
'type' => $type,
'handler' => $handler,
'pattern' => rtrim($parts[1], '/')
)
);
}
}
/**
* parse routes
* mine the current request against the routes in the library
*/
function parseRoutes() {
if(isset($this->benchmark)) {
$this->benchmark->mark('parseStart');
}
library::set('QOOB.url', 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
library::set('QOOB.domain', 'http://'.dirname($_SERVER["HTTP_HOST"].$_SERVER["SCRIPT_NAME"]));
library::set('REQUEST.verb', $_SERVER['REQUEST_METHOD']);
library::set('REQUEST.uri', rtrim(str_replace('?'.$_SERVER['QUERY_STRING'], '', str_replace(library::get('QOOB.domain'), '', library::get('QOOB.url'))), '/'));
library::set('REQUEST.ajax', (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])&&strtolower($_SERVER['HTTP_X_REQUESTED_WITH'])=='xmlhttprequest')?'AJAX':'SYNC');
$found = false;
foreach(library::get('ROUTES') as $route) {
// regular expression to identify uri requests formatted like '/users/:uid/posts/:pid'
$pattern = "@^".preg_replace('/\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['pattern']))."$@D";
$args = array();
// check if the current request matches the expression
if(library::get('REQUEST.verb') == $route['verb'] && preg_match($pattern, library::get('REQUEST.uri'), $matches)) {
if($route['type'] == library::get('REQUEST.ajax')) {
// remove the first match
array_shift($matches);
$found = true;
// correlate route regex with uri parameters
preg_match_all('/\:[a-zA-Z0-9\_\-]+/', preg_quote($route['pattern']), $names);
for($i=0;$i<count($names[0]);$i++) {
$args[str_replace('\:', '', $names[0][$i])] = isset($matches[$i])?$matches[$i]:'';
}
// get and merge request and uri arguments
$requests = $this->parseRequest(library::get('REQUEST.verb'));
$args = array_merge_recursive($requests, $args);
break;
}
}
}
if(isset($this->benchmark)) {
$this->benchmark->mark('parseEnd');
}
if(!$found) {
throw new Exception(self::HTTP_404, 404);
} else {
$this->call($route, $args);
}
}
/**
* call
* execute a route handler
* @param array $route route information
* @param array $args url arguments
*/
function call($route, $args) {
if(isset($this->benchmark)) {
$this->benchmark->mark('callStart');
}
// closure style
if(is_callable($route['handler'])) {
call_user_func_array($route['handler'], array($args));
}
// class creation
if(is_string($route['handler']) && preg_match('/(.+)\h*(->|::)\h*(.+)/s', $route['handler'], $parts)) {
if (!class_exists($parts[1]) || !method_exists($parts[1], $parts[3])) {
throw new Exception(self::HTTP_404, 404);
}
call_user_func_array(array(new $parts[1], $parts[3]), array($args));
}
if(isset($this->benchmark)) {
$this->benchmark->mark('callEnd');
}
}
/**
* parse request
* gets request arguments from the correct protocol for the given http verb
* @param string $verb the http verb
*/
function parseRequest($verb) {
$args = array();
switch ($verb) {
case 'GET':
case 'HEAD':
$args = $_GET;
break;
case 'POST':
$args = $_POST;
break;
case 'PUT':
case 'DELETE':
parse_str(file_get_contents('php://input'), $args);
break;
}
return $args;
}
/**
* run
* framework execution
*/
function run() {
$this->parseRoutes();
}
/**
* split
* seperate a comma, semi-colon, or pipe delimited string
* @param string $str
* @param array $strs
*/
function split($str) {
return array_map('trim',
preg_split('/[,;|]/',$str,0,PREG_SPLIT_NO_EMPTY));
}
/**
* fatal error handler
* gracefully respond to fatal errors.
*/
function fatal_handler() {
if(@is_array($err = @error_get_last())) {
$code = isset($err['type']) ? $err['type'] : 0;
if($code>0) {
$this->error_handler(
$code,
isset($err['message']) ? $err['message'] : '',
isset($err['file']) ? $err['file'] : '',
isset($err['line']) ? $err['line'] : ''
);
}
}
}
/**
* exception handler
* gracefully respond to exceptions.
* @param object $exc the php exception object
*/
function exception_handler($exc) {
$this->error_handler(
$exc->getCode(),
$exc->getMessage(),
$exc->getFile(),
$exc->getLine(),
$exc->getTrace()
);
}
/**
* error handler
* gracefully respond to errors
* @param int $num error code
* @param string $str error message
* @param string $file the file throwing the error
* @param int $line line number in the file throwing the error
* @param array $ctx error context
*/
function error_handler($num, $str, $file, $line, $ctx=array()) {
// remove php error output
if(ob_get_length()>0){
@ob_end_clean();
}
$code = $this->status($num);
if(isset($this->logz)) {
$this->logz->changeFile('error.log');
$this->logz->write('error: '.$num.' - '.$str.' [file] '.$file.' [line] '.$line.' [context] '.trim(preg_replace('/\s+/', ' ', print_r($ctx, true))));
}
$this->load('app\error');
$this->error->render($code, $num, $str, $file, $line, $ctx);
die();
}
/**
* open qoob
* get the singleton reference to the open qoob framework
* @return class qoob
*/
static function open() {
if (!library::exists('CLASS.'.$class=__CLASS__)) {
library::set('CLASS.'.$class, new $class);
}
return library::get('CLASS.'.$class);
}
/**
* clone
* disabled
*/
private function __clone() {}
/**
* constructor
* bootstraps the framework/core utils. initializes autoloading classes. set default variables.
*/
private function __construct() {
//support non-namespaced classes
set_include_path(
implode(
PATH_SEPARATOR,
array(
get_include_path(),
dirname(__FILE__).DIRECTORY_SEPARATOR.'app'
)
)
);
spl_autoload_extensions(".php,.inc");
spl_autoload_register(function($class) {
$class = (string) str_replace('\', DIRECTORY_SEPARATOR, $class);
if(stream_resolve_include_path($class.'.php') !== false) {
require $class.'.php';
}
});
library::set('STATUS.code', 200);
library::set('UI.dir', realpath('ui'));
library::set('TMP.dir', realpath('tmp'));
library::set('CONFIG.debug', false);
}
/**
* destructor
*/
public function __destruct() {}
}
//_________________________________________________________________________
// object library
/**
* library
* singleton object library
*
* @author xero harrison <x@xero.nu>
* @copyright creative commons attribution-shareAlike 3.0 unported
* @license http://creativecommons.org/licenses/by-sa/3.0/
* @version 2.22.01
*/
final class library {
private static $catalog;
static function expose() {
return print_r(self::$catalog, true);
}
static function exists($key) {
return isset(self::$catalog[$key]);
}
static function get($key) {
return isset(self::$catalog[$key]) ? self::$catalog[$key] : false;
}
static function set($key, $value) {
is_array($value) ? self::$catalog[$key][]=$value : self::$catalog[$key]=$value;
}
static function clear($key) {
unset(self::$catalog[$key]);
}
function __construct() {}
function __clone() {}
}
//_________________________________________________________________________
// open the qoob
/**
* setup global error handling
* @return class qoob instance
*/
$qoob = qoob::open();
set_error_handler(array(&$qoob, 'error_handler'));
set_exception_handler(array(&$qoob, 'exception_handler'));
register_shutdown_function(array(&$qoob, 'fatal_handler'));
return $qoob;
?>