Forráskód Böngészése

* Update namespacing everywhere

Nathaniel van Diepen 7 éve
szülő
commit
96a61b1774

+ 33 - 0
App/arguments.class.php

@@ -0,0 +1,33 @@
+<?php
+	namespace Juju\App {
+		class Arguments implements \JsonSerializable, \ArrayAccess{
+			public $args;
+			public function __construct($args){
+				$this->args = $args;
+			}
+			public function jsonSerialize(){
+				return $this->args;
+			}
+			public function __toString(){
+				return $this->path;
+			}
+			public function __get($name){
+				if(isset($this->args[$name])){
+					return $this->args[$name];
+				}
+			}
+			public function offsetGet($key){
+				return $this->args[$key];
+			}
+			public function offsetExists($key){
+				return isset($this->args[$key]);
+			}
+			public function offsetSet($key,$val){
+				$this->args[$key] = $val;
+			}
+			public function offsetUnset($key){
+				unset($this->args[$key]);
+			}
+		}
+	}
+?>

+ 44 - 0
App/dypath.class.php

@@ -0,0 +1,44 @@
+<?php
+	namespace Juju\App {
+		require_once('arguments.class.php');
+
+		class DyPath implements \JsonSerializable{
+			public $path;
+			public function __construct(string $path){
+				$this->path = $path;
+			}
+			public function jsonSerialize(){
+				return [
+					'path'=>$this->path,
+					'regex'=>$this->regex,
+					'arguments'=>$this->arguments
+				];
+			}
+			public function __toString(){
+				return $this->path;
+			}
+			public function __get($name){
+				switch($name){
+					case 'arguments':
+						preg_match_all('/\{([^#\/][^}\n]+?)\}/',$this->path,$m,PREG_SET_ORDER);
+						return $m;
+					break;
+					case 'regex':
+						return '/^'.preg_replace('/\\\{[^#\/][^}\n]+?\\\}/','([^\/]*)',preg_quote($this->path,'/')).'$/';
+					break;
+				}
+			}
+			public function matches(string $url){
+				return preg_match($this->regex, $url);
+			}
+			public function args(string $url){
+				$ret = [];
+				preg_match_all($this->regex,$url,$m,PREG_SET_ORDER);
+				foreach($this->arguments as $k => $arg){
+					$ret[$arg[1]] = $m[0][$k+1];
+				}
+				return new Arguments($ret);
+			}
+		}
+	}
+?>

+ 2 - 2
App/exception.class.php

@@ -1,8 +1,8 @@
 <?php
-	namespace App {
+	namespace Juju\App {
 		class Exception extends \Exception {
 			private $included;
-			public function __construct($message = null, $code = 0, Exception $previous = null, string $file = null, int $line = null, array $trace = null, array $included = []){
+			public function __construct($message = null, $code = 0, \Exception $previous = null, string $file = null, int $line = null, array $trace = null, array $included = []){
 				parent::__construct($message, $code, $previous);
 				if(!is_null($file)){
 					$this->file = $file;

+ 52 - 0
App/path.class.php

@@ -0,0 +1,52 @@
+<?php
+	namespace Juju\App {
+		require_once('dypath.class.php');
+		$dir = dirname(__DIR__).'/Http/';
+		require_once(realpath("{$dir}/request.class.php"));
+		require_once(realpath("{$dir}/response.class.php"));
+		require_once('arguments.class.php');
+		use \Juju\Http\{Request, Response};
+
+		class Path implements \JsonSerializable{
+			private $handles = [];
+			public $path;
+			public function __construct(string $path){
+				$this->path = new DyPath($path);
+			}
+			public function __invoke(Request $req, Response $res, Arguments $args){
+				$err = null;
+				foreach($this->handles as $k => $fn){
+					try{
+						$fn($req, $res, $args, $err);
+					}catch(\Exception $e){
+						$err = $e;
+					}
+				}
+			}
+			public function __clone(){
+				// No cloning for now
+			}
+			public function __destruct(){
+				// Nothing to do here
+			}
+			public function jsonSerialize(){
+				return [
+					'dypath'=>$this->path
+				];
+			}
+			public function __toString(){
+				return "[Path {$this->path}]";
+			}
+			public function handle(callable $fn){
+				array_push($this->handles,$fn);
+				return $this;
+			}
+			public function matches(string $path){
+				return $this->path->matches($path);
+			}
+			public function args(string $path){
+				return $this->path->args($path);
+			}
+		}
+	}
+?>

+ 137 - 0
App/router.class.php

@@ -0,0 +1,137 @@
+<?php
+	namespace Juju\App {
+		require_once('path.class.php');
+		$dir = dirname(__DIR__);
+		require_once(realpath("{$dir}/events.trait.php"));
+		$dir = "{$dir}/Http/";
+		require_once(realpath("{$dir}/request.class.php"));
+		require_once(realpath("{$dir}/response.class.php"));
+		use \Juju\Events;
+		use \Juju\Http\{Request, Response};
+
+		class Router {
+			use Events;
+			private $_paths = [];
+			private $_routers = [];
+			private $_base = '/';
+			private $responses = [];
+			private $_handled = false;
+			public function __construct(string $base = null, array $paths = null){
+				if($paths != null){
+					$this->paths($paths);
+				}
+				if($base != null){
+					$this->base($base);
+				}
+			}
+			public function __get($name){
+				switch($name){
+					case 'base':
+						return $this->_base;
+					break;
+					case 'handled':
+						return $this->_handled;
+					break;
+				}
+			}
+			public function __clone(){
+				// No cloning
+			}
+			public function __destruct(){
+				$this->_paths = [];
+			}
+			public function __toString(){
+				return "[Router]";
+			}
+			public function base(string $base){
+				$this->_base = $base;
+			}
+			public function url(string $url){
+				return preg_replace('/(\/+)/','/',$url);
+			}
+			public function prefix(string $prefix, callable $fn){
+				$found = false;
+				foreach($this->_routers as $k => $router){
+					if($router->base == $prefix){
+						$found = true;
+						$fn($router);
+						break;
+					}
+				}
+				if(!$found){
+					$router= new Router($prefix);
+					$this->_routers[] = $router;
+					$fn($router);
+				}
+			}
+			// fn = function(response,args){}
+			public function path(string $path, callable $fn){
+				$obj = false;
+				foreach($this->_paths as $k => $p){
+					if($p->path == $path){
+						$obj = $p;
+					}
+				}
+				if(!$obj){
+					$obj = new Path($path);
+					array_push($this->_paths,$obj);
+				}
+				return $obj->handle($fn);
+			}
+			public function paths(array $paths){
+				foreach($paths as $path => $fn){
+					$this->path($path,$fn);
+				}
+			}
+			public function clear(){
+				$this->_paths = [];
+			}
+			public function handle(string $path, Request $req = null, Response $res = null, callable $fn = null, callable $onerror = null){
+				if(strpos($path, $this->base) !== false){
+					$path = substr($path,strpos($path,$this->base)+strlen($this->base));
+					if($path[0] != '/'){
+						$path = '/'.$path;
+					}
+					if(is_null($req)){
+						$req = new Request();
+					}
+					if(is_null($res)){
+						$res = new Response();
+					}
+					if(!in_array($res,$this->responses)){
+						array_push($this->responses,$res);
+					}
+					$this->fire('handle', $req, $res);
+					$handled = false;
+					foreach($this->_routers as $prefix => $router){
+						$router->handle($path, $req, $res);
+						$handled = $handled ||$router->handled;
+					}
+					ob_start();
+					foreach($this->_paths as $k => $p){
+						if($p->matches($path)){
+							$handled = true;
+							try{
+								$p($req, $res, $p->args($path));
+							}catch(\Exception $e){
+								if(!is_null($onerror)){
+									$onerror($req, $res,$e);
+								}else{
+									throw $e;
+								}
+							}
+						}
+					}
+					$this->_handled = $handled;
+					if(!$handled && !is_null($fn)){
+						$fn($req, $res);
+					}
+					$res->output .= ob_get_contents();
+					ob_end_clean();
+					$this->fire('afterhandle', $req, $res);
+				}
+				return $res;
+			}
+		}
+	}
+?>

+ 48 - 0
Http/request.class.php

@@ -0,0 +1,48 @@
+<?php
+	namespace Juju\Http {
+		require_once('uri.class.php');
+
+		class Request {
+			private $url;
+			private $headers;
+			private $body;
+			public function __construct($url, array $headers = [], string $body = ''){
+				if(is_string($url) || is_array($url)){
+					$this->url = new Uri($url);
+				}elseif($url instanceof Uri){
+					$this->url = $url;
+				}else{
+					throw new \Exception("Invalid url {$url}");
+				}
+				if(is_array($headers)){
+					$this->headers = $headers;
+				}else{
+					$this->headers = [];
+				}
+				$this->body = $body;
+			}
+			public function __get($name){
+				switch($name){
+					case 'headers':
+						return $this->headers;
+					break;
+					case 'url':
+						return $this->url;
+					break;
+					case 'body':
+						return $this->body;
+					break;
+				}
+			}
+			public function header($name){
+				return $this->headers[$name] ?? null;
+			}
+			public function json(){
+				return json_decode($this->body, true);
+			}
+			public function text(){
+				return $this->body;
+			}
+		}
+	}
+?>

+ 129 - 0
Http/response.class.php

@@ -0,0 +1,129 @@
+<?php
+	namespace Juju\Http {
+		require_once(realpath(dirname(__DIR__).'/events.trait.php'));
+		use \Juju\Events;
+
+		class Response {
+			use Events;
+			public $output = '';
+			public $body = '';
+			private $code = 200;
+			public $headers = [];
+			protected $open = true;
+			public function __construct(){}
+			public function __toString(){
+				return $this->body;
+			}
+			public function clear(){
+				if($this->open){
+					$this->fire('clear');
+					$this->body = '';
+				}
+				return $this;
+			}
+			public function clear_headers(){
+				if($this->open){
+					$this->fire('clear_headers');
+					$this->headers = [];
+				}
+				return $this;
+			}
+			public function clear_header(string $name){
+				foreach($this->headers as $key => $header){
+					if($header[0] == $name){
+						$this->fire('clear_header', $name);
+						array_splice($this->headers, $key, 1);
+					}
+				}
+				return $this;
+			}
+			public function write(string $chunk){
+				$this->fire('write', $chunk);
+				if($this->open){
+					$this->body .= $chunk;
+				}
+				return $this;
+			}
+			public function json($json){
+				if(is_array($json)){
+					array_walk_recursive($json, function(&$item, $key){
+						if(!mb_detect_encoding($item, 'utf-8', true)){
+							$item = utf8_encode($item);
+						}
+					});
+				}
+				$this->fire('json', $json);
+				$this->write(json_encode($json));
+				if(json_last_error() != JSON_ERROR_NONE){
+					throw new \Exception(json_last_error_msg());
+				}
+				return $this;
+			}
+			public function header(string $name, string $value){
+				if($this->open){
+					$this->fire('header', $name, $value);
+					array_push(
+						$this->headers,
+						[
+							$name,
+							$value
+						]
+					);
+				}
+				return $this;
+			}
+			public function redirect(string $url){
+				$this->fire('redirect', $url);
+				$this->header('Location',Router::url($url));
+				return $this;
+			}
+			public function end(string $chunk=''){
+				if($this->open){
+					$this->write($chunk);
+					$this->fire('end');
+					$this->open = false;
+				}
+				return $this;
+			}
+			public function img(Image $img, string $type = null){
+				if(!$type){
+					$type = $img->type;
+				}
+				$this->fire('image', $img, $type);
+				$this->clear_header('Content-Type')
+					->header('Content-Type', 'image/'.$type);
+				if(!is_a($img, 'Image')){
+					$img = new Image(100, 20);
+					$img->text('Invalid Image',0,0,'black',12);
+				}
+				ob_start();
+				$img();
+				$this->write(ob_get_contents());
+				ob_end_clean();
+				return $this;
+			}
+			public function code(int $code=null){
+				if(is_null($code)){
+					return $this->code;
+				}
+				$this->fire('code', $code);
+				$this->code = $code;
+				return $this;
+			}
+			public function shutdown(){
+				$this->fire('beforeshutdown');
+				if($this->open){
+					$this->end();
+				}
+				$this->fire('shutdown');
+				http_response_code($this->code);
+				foreach($this->headers as $k => $header){
+					header("{$header[0]}: $header[1]");
+				}
+				echo $this->body;
+				flush();
+				$this->fire('aftershutdown');
+			}
+		}
+	}
+?>

+ 58 - 0
Http/uri.class.php

@@ -0,0 +1,58 @@
+<?php
+	namespace Juju\Http {
+		class Uri{
+			private $url;
+			public function __construct($url){
+				if(is_array($url)){
+					$this->url = $url;
+				}else if(is_string($url)){
+					$this->url = parse_url($url);
+				}else{
+					throw new \Exception("Invalid Url");
+				}
+			}
+			public function __get($name){
+				if(isset($this->url[$name])){
+					return $this->url[$name];
+				}else{
+					switch($name){
+						case 'variables':
+							parse_str((string)$this, $output);
+							return $output;
+						break;
+					}
+				}
+			}
+			public function __set($name, $value){
+				if(isset($this->url[$name])){
+					$this->url[$name] = $value;
+				}
+			}
+			public function __toString(){
+				$port = $this->port;
+				if($port){
+					if($this->scheme == 'http'){
+						$port = $port == 80 ? "" : ":{$port}";
+					}elseif($this->scheme = 'https'){
+						$port = $port == 443 ? "" : ":{$port}";
+					}
+					// @todo - add other default port types
+				}
+				$auth = $this->user;
+				if($auth){
+					$auth = $this->pass ? "{$auth}:{$this->pass}@" : "{$auth}@";
+				}
+				$query = $this->query;
+				if($query){
+					$query = "?{$query}";
+				}
+				$fragmanet = $this->fragmanet;
+				if($fragmanet){
+					$fragmanet = "#{$fragmanet}";
+				}
+				// @todo - handle when scheme requires other formats
+				return "{$this->scheme}://{$auth}{$this->host}{$port}{$this->path}{$query}{$fragmanet}";
+			}
+		}
+	}
+?>

+ 100 - 0
ORM/relationship.class.php

@@ -0,0 +1,100 @@
+<?php
+	namespace Juju\ORM {
+		require_once(realpath(dirname(__DIR__).'/earray.class.php'));
+		use \Juju\EArray;
+		use \Juju\ORM;
+
+		class Relationship extends EArray {
+			private $model;
+			private $name;
+			private $alias;
+			private $removed = [];
+			private $added = [];
+			public static function from(ORM $model, string $name, array $alias, array $data){
+				$earray = parent::from($data);
+				$earray->model = $model;
+				$earray->name = $name;
+				$earray->alias = $alias;
+				return $earray
+					->on('set', function(&$offset, &$value) use($earray){
+						$earray->added = array_merge($earray->added, [$value]);
+						$earray->removed = array_diff($earray->removed, [$value]);
+					})->on('unset', function($offset, $value) use($earray){
+						$earray->removed = array_merge($earray->removed, [$value]);
+						$earray->added = array_diff($earray->added, [$value]);
+					});
+			}
+			public function add(ORM $model){
+				$this[] = $model;
+				return $this;
+			}
+			public function remove(ORM $model){
+				$index = $this->index($model);
+				if($index !== false){
+					unset($this->data[$index]);
+				}
+				return $this;
+			}
+			public function has(ORM $model){
+				return $this->index($model) !== false;
+			}
+			public function index(ORM $model){
+				return array_search($model, $this->data);
+			}
+			public function dirty(){
+				return count($this->removed) + count($this->added) > 0;
+			}
+			public function reset(){
+				foreach($this->added as $model){
+					$index = array_search($model, $this->data);
+					if($index !== false){
+						unset($this->data[$index]);
+					}
+				}
+				$this->added = [];
+				foreach($this->removed as $model){
+					$this->data = array_merge($this->data, [$model]);
+				}
+				$this->removed = [];
+			}
+			public function save(){
+				if($this->dirty()){
+					$model = $this->model;
+					$alias = $this->$alias;
+					$name = $this->name;
+					$class = "Models\\{$alias['model']}";
+					if(isset($model->has_many[$name])){
+						throw new \Exception("Invalid relationship {$name}");
+					}
+					if(isset($alias['through'])){
+						$rem_sql = "delete from {$alias['through']} where {$alias['foreign_key']} = ? and ".$class::table_name().$class::foreign_key_suffix()." = ?";
+						$rem_args = [$model->id];
+						$add_sql = "insert into {$alias['through']} ({$alias['foreign_key']}, ".$class::table_name().$class::foreign_key_suffix().") values (?, ?)";
+					}else{
+						$rem_sql = "update {$alias['model']} set {$alias['foreign_key']} = null where ".$class::primary_key()." = ?";
+						$rem_args = [];
+						$add_sql .= "update {$alias['model']} set {$alias['foreign_key']} = ? where ".$class::primary_key()." = ?";
+					}
+					$class = get_class($models);
+					foreach($this->removed as $item){
+						$class::query(
+							$rem_sql,
+							'ii',
+							...array_merge($rem_args, [$item->id])
+						)->execute();
+					}
+					$this->removed = [];
+					foreach($this->added as $item){
+						$class::query(
+							$add_sql,
+							'ii',
+							$model->id,
+							$item->id
+						)->execute();
+					}
+					$this->added = [];
+				}
+			}
+		}
+	}
+?>

+ 2 - 2
SQL/query.class.php

@@ -1,5 +1,5 @@
 <?php
-	namespace SQL {
+	namespace Juju\SQL {
 		/**
 		* Query class. Returned by \SQL::query()
 		*
@@ -19,7 +19,7 @@
 					throw new \RuntimeException($this->query->error);
 				}
 				if(!is_null($types)){
-					if(!$this->query->bind_param(...\SQL::make_referenced($args))){
+					if(!$this->query->bind_param(...\Juju\SQL::make_referenced($args))){
 						throw new \RuntimeException("Unable to bind parameter {$this->query->error}");
 					}
 				}

+ 161 - 160
app.class.php

@@ -1,185 +1,186 @@
 <?php
-	require_once('base.abstract.class.php');
-	require_once('router.class.php');
-	require_once('response.class.php');
-	require_once('request.class.php');
-	require_once('uri.class.php');
-	require_once('events.trait.php');
-	require_once('App/exception.class.php');
-	class App extends Base {
-		use Events;
-		private static $apps = [];
-		private $domains;
-		private $routers;
-		private $router;
-		public $_onerror;
-		public $request;
-		public $response;
-		public function __construct(string $name, Callable $fn = null, array $events = null){
-			$this->name = $name;
-			$this->router = new Router();
-			$this->response = new Response();
-			$this->routers = [];
-			$this->domains = [];
-			$this->_onerror = function($req, $res, $error){
-				$o = json_encode([
-					'message'=>$error->getMessage(),
-					'code'=>$error->getCode(),
-					'file'=>$error->getFile(),
-					'line'=>$error->getLine(),
-					'traceback'=>$error->getTrace()
-				]);
-				$res->code(404);
-				$res->header('Content-Type', 'application/json');
-				$res->end($o);
-				$res->shutdown();
-			};
-			if(!is_null($events)){
-				foreach($events as $name => $handler){
-					$this->on($name, $handler);
+	namespace Juju {
+		require_once('base.abstract.class.php');
+		require_once('App/router.class.php');
+		require_once('App/exception.class.php');
+		use \Juju\Http\{Response, Request};
+		use \Juju\App\Router;
+
+		class App extends Base {
+			use Events;
+			private static $apps = [];
+			private $domains;
+			private $routers;
+			private $router;
+			public $_onerror;
+			public $request;
+			public $response;
+			public function __construct(string $name, callable $fn = null, array $events = null){
+				$this->name = $name;
+				$this->router = new Router();
+				$this->response = new Response();
+				$this->routers = [];
+				$this->domains = [];
+				$this->_onerror = function($req, $res, $error){
+					$o = json_encode([
+						'message'=>$error->getMessage(),
+						'code'=>$error->getCode(),
+						'file'=>$error->getFile(),
+						'line'=>$error->getLine(),
+						'traceback'=>$error->getTrace()
+					]);
+					$res->code(404);
+					$res->header('Content-Type', 'application/json');
+					$res->end($o);
+					$res->shutdown();
+				};
+				if(!is_null($events)){
+					foreach($events as $name => $handler){
+						$this->on($name, $handler);
+					}
 				}
-			}
-			static::$apps[] = $this;
-			if(is_callable($fn)){
-				$fn($this);
-			}
-		}
-		public function __destruct(){
-			$this->routers = [];
-			$index = array_search($this, static::$apps);
-			if($index !== false){
-				array_splice(static::$apps, $index, 1);
-			}
-			if(is_callable('parent::__destruct')){
-				parent::__destruct();
-			}
-		}
-		public static function import_all(string $dirpath){
-			foreach(scandir($dirpath) as $file){
-				$path = "{$dirpath}{$file}";
-				if(is_file($path) && pathinfo($file, PATHINFO_EXTENSION) == "php"){
-					require_once($path);
+				static::$apps[] = $this;
+				if(is_callable($fn)){
+					$fn($this);
 				}
 			}
-		}
-		public static function shutdown(){
-			$verb = $_SERVER['REQUEST_METHOD'];
-			$url = App::get_url();
-			$data = file_get_contents( 'php://input','r');
-			foreach(static::$apps as $k => $app){
-				if($app instanceof App){
-					$app->handle($verb, $url, $data)->shutdown();
+			public function __destruct(){
+				$this->routers = [];
+				$index = array_search($this, static::$apps);
+				if($index !== false){
+					array_splice(static::$apps, $index, 1);
+				}
+				if(is_callable('parent::__destruct')){
+					parent::__destruct();
 				}
 			}
-			if(is_callable('parent::__destruct')){
-				parent::__destruct();
-			}
-		}
-		public static function shutdown_error($error){
-			foreach(static::$apps as $k => $app){
-				if(is_null($app->request)){
-					$app->$request = new Request(static::get_url(), getallheaders(), file_get_contents( 'php://input','r'));
+			public static function import_all(string $dirpath){
+				foreach(scandir($dirpath) as $file){
+					$path = "{$dirpath}{$file}";
+					if(is_file($path) && pathinfo($file, PATHINFO_EXTENSION) == "php"){
+						require_once($path);
+					}
 				}
-				$app->onerror($app->request, $app->response, $error);
-				$app->response->shutdown();
 			}
-		}
-		public static function get_url(){
-			if(isset($_SERVER['REDIRECT_URL'])){
-				$url = 'REDIRECT_URL';
-			}else{
-				$url = 'REQUEST_URI';
+			public static function shutdown(){
+				$verb = $_SERVER['REQUEST_METHOD'];
+				$url = App::get_url();
+				$data = file_get_contents( 'php://input','r');
+				foreach(static::$apps as $k => $app){
+					if($app instanceof App){
+						$app->handle($verb, $url, $data)->shutdown();
+					}
+				}
+				if(is_callable('parent::__destruct')){
+					parent::__destruct();
+				}
 			}
-			if(!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])){
-				$root = $_SERVER['HTTP_X_FORWARDED_PROTO'].'://';
-			}else{
-				$root = !empty($_SERVER['HTTPS']) ? "https://" : "http://";
+			public static function shutdown_error($error){
+				foreach(static::$apps as $k => $app){
+					if(is_null($app->request)){
+						$app->$request = new Request(static::get_url(), getallheaders(), file_get_contents( 'php://input','r'));
+					}
+					$app->onerror($app->request, $app->response, $error);
+					$app->response->shutdown();
+				}
 			}
-			// @todo - determine if http or http and also check cloudflare
-			return parse_url($root.$_SERVER['HTTP_HOST'].$_SERVER[$url]);
-		}
-		public function handle(string $verb, array $url, string $data, array $headers = null){
-			if(is_null($headers)){
-				$headers = getallheaders();
+			public static function get_url(){
+				if(isset($_SERVER['REDIRECT_URL'])){
+					$url = 'REDIRECT_URL';
+				}else{
+					$url = 'REQUEST_URI';
+				}
+				if(!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])){
+					$root = $_SERVER['HTTP_X_FORWARDED_PROTO'].'://';
+				}else{
+					$root = !empty($_SERVER['HTTPS']) ? "https://" : "http://";
+				}
+				// @todo - determine if http or http and also check cloudflare
+				return parse_url($root.$_SERVER['HTTP_HOST'].$_SERVER[$url]);
 			}
-			$res = $this->response;
-			$this->request = $req = new Request($url, $headers, $data);
-			if($this->fire('handle', $req, $res)){
-				$self = $this;
-				$onerror = function($res, $error) use($self){
-					$self->onerror($req, $res, $error);
-				};
-				$handled = false;
-				// Domain routers
-				foreach($this->domains as $host => $router){
-					if($url['host'] == $host){
-						while(is_string($router)){
-							$router = $this->domains[$router];
+			public function handle(string $verb, array $url, string $data, array $headers = null){
+				if(is_null($headers)){
+					$headers = getallheaders();
+				}
+				$res = $this->response;
+				$this->request = $req = new Request($url, $headers, $data);
+				if($this->fire('handle', $req, $res)){
+					$self = $this;
+					$onerror = function($res, $error) use($self){
+						$self->onerror($req, $res, $error);
+					};
+					$handled = false;
+					// Domain routers
+					foreach($this->domains as $host => $router){
+						if($url['host'] == $host){
+							while(is_string($router)){
+								$router = $this->domains[$router];
+							}
+							$router->handle($url["path"], $req, $res, null, $onerror);
+							$handled = $handled || $router->handled;
 						}
+					}
+					// Prefixed path routers
+					foreach($this->routers as $prefix => $router){
 						$router->handle($url["path"], $req, $res, null, $onerror);
 						$handled = $handled || $router->handled;
 					}
+					// Base router for non-prefixed paths
+					$this->router->handle($url["path"], $req, $res, function($req, $res) use($handled, $self){
+						if(!$handled){
+							$self->onerror($req, $res,new Error("Not Found", 404));
+						}
+					}, $onerror);
+					$this->fire('afterhandle', $req, $res);
 				}
-				// Prefixed path routers
-				foreach($this->routers as $prefix => $router){
-					$router->handle($url["path"], $req, $res, null, $onerror);
-					$handled = $handled || $router->handled;
-				}
-				// Base router for non-prefixed paths
-				$this->router->handle($url["path"], $req, $res, function($req, $res) use($handled, $self){
-					if(!$handled){
-						$self->onerror($req, $res,new Error("Not Found", 404));
-					}
-				}, $onerror);
-				$this->fire('afterhandle', $req, $res);
+				return $res;
 			}
-			return $res;
-		}
-		public function error(Callable $fn){
-			$this->_onerror = $fn;
-			return $this;
-		}
-		public function route(string $path, Callable $fn){
-			$this->router->path($path, $fn);
-			return $this;
-		}
-		public function prefix(string $prefix, Callable $fn){
-			if(!$this->routers[$prefix]){
-				$this->routers[$prefix] = new Router($prefix);
+			public function error(callable $fn){
+				$this->_onerror = $fn;
+				return $this;
 			}
-			$fn($this->routers[$prefix]);
-			return $this;
-		}
-		public function domain(string $host, Callable $fn){
-			if(!isset($this->domains[$host])){
-				$this->domains[$host] = new Router();
+			public function route(string $path, callable $fn){
+				$this->router->path($path, $fn);
+				return $this;
 			}
-			$fn($this->domains[$host]);
-			return $this;
-		}
-		public function map_domain(string $host, $handle){
-			if(!is_array($handle)){
-				$handle = [$handle];
+			public function prefix(string $prefix, callable $fn){
+				if(!$this->routers[$prefix]){
+					$this->routers[$prefix] = new App\Router($prefix);
+				}
+				$fn($this->routers[$prefix]);
+				return $this;
+			}
+			public function domain(string $host, callable $fn){
+				if(!isset($this->domains[$host])){
+					$this->domains[$host] = new App\Router();
+				}
+				$fn($this->domains[$host]);
+				return $this;
 			}
-			foreach($handle as $host2){
-				$this->domains[$host2] = $host;
+			public function map_domain(string $host, $handle){
+				if(!is_array($handle)){
+					$handle = [$handle];
+				}
+				foreach($handle as $host2){
+					$this->domains[$host2] = $host;
+				}
+				return $this;
 			}
-			return $this;
-		}
-		public function onerror(Request $req, Response $res, $error){
-			$this->fire('error', $error);
-			$fn = $this->_onerror;
-			if(is_callable($fn)){
-				$fn($req, $res, $error);
+			public function onerror(Request $req, Response $res, $error){
+				$this->fire('error', $error);
+				$fn = $this->_onerror;
+				if(is_callable($fn)){
+					$fn($req, $res, $error);
+				}
 			}
 		}
+		error_reporting(E_ALL);
+		ini_set('display_errors', 'On');
+		register_shutdown_function(function(){
+			App::shutdown();
+		});
+		set_error_handler(function($errno, $errstr, $errfile, $errline){
+			App::shutdown_error(new App\Exception($errstr, $errno, null, $errfile, $errline, debug_backtrace(), get_included_files()));
+		},E_ALL);
 	}
-	error_reporting(E_ALL);
-	ini_set('display_errors', 'On');
-	register_shutdown_function(function(){
-		App::shutdown();
-	});
-	set_error_handler(function($errno, $errstr, $errfile, $errline){
-		App::shutdown_error(new App\Exception($errstr, $errno, null, $errfile, $errline, debug_backtrace(), get_included_files()));
-	},E_ALL);
 ?>

+ 0 - 31
arguments.class.php

@@ -1,31 +0,0 @@
-<?php
-	class Arguments implements JsonSerializable, ArrayAccess{
-		public $args;
-		public function __construct($args){
-			$this->args = $args;
-		}
-		public function jsonSerialize(){
-			return $this->args;
-		}
-		public function __toString(){
-			return $this->path;
-		}
-		public function __get($name){
-			if(isset($this->args[$name])){
-				return $this->args[$name];
-			}
-		}
-		public function offsetGet($key){
-			return $this->args[$key];
-		}
-		public function offsetExists($key){
-			return isset($this->args[$key]);
-		}
-		public function offsetSet($key,$val){
-			$this->args[$key] = $val;
-		}
-		public function offsetUnset($key){
-			unset($this->args[$key]);
-		}
-	}
-?>

+ 39 - 37
base.abstract.class.php

@@ -1,43 +1,45 @@
 <?php
-	abstract class Base {
-		private $data;
-		public function __construct(){
-			$this->data = [];
-			// Constructor
-		}
-		public function __clone(){
-			// Clone handler
-		}
-		public function __destruct(){
-			// Destroy handler
-			$this->data = [];
-		}
-		public function __toString(){
-			$name = get_class($this);
-			if(!$name){
-				$name = "Object";
+	namespace Juju {
+		abstract class Base {
+			private $data;
+			public function __construct(){
+				$this->data = [];
+				// Constructor
 			}
-			return "[".$name."]";
-		}
-		public function __invoke($res,$args){
-			// Invoke handler
-		}
-		public function jsonSerialize(){
-			return $this;
-		}
-		public function __get($name){
-			return $this->data??null;
-		}
-		public function __set($name,$value){
-			if(isset($this->data[$name])){
-				$this->data[$name] = $value;
+			public function __clone(){
+				// Clone handler
+			}
+			public function __destruct(){
+				// Destroy handler
+				$this->data = [];
+			}
+			public function __toString(){
+				$name = get_class($this);
+				if(!$name){
+					$name = "Object";
+				}
+				return "[".$name."]";
+			}
+			public function __invoke($res,$args){
+				// Invoke handler
+			}
+			public function jsonSerialize(){
+				return $this;
+			}
+			public function __get($name){
+				return $this->data??null;
+			}
+			public function __set($name,$value){
+				if(isset($this->data[$name])){
+					$this->data[$name] = $value;
+				}
+			}
+			public function __isset($name){
+				return isset($this->data[$name]);
+			}
+			public function __unset($name){
+				unset($this->data[$name]);
 			}
-		}
-		public function __isset($name){
-			return isset($this->data[$name]);
-		}
-		public function __unset($name){
-			unset($this->data[$name]);
 		}
 	}
 ?>

+ 0 - 41
dypath.class.php

@@ -1,41 +0,0 @@
-<?php
-	require_once('arguments.class.php');
-	class DyPath implements JsonSerializable{
-		public $path;
-		public function __construct(string $path){
-			$this->path = $path;
-		}
-		public function jsonSerialize(){
-			return [
-				'path'=>$this->path,
-				'regex'=>$this->regex,
-				'arguments'=>$this->arguments
-			];
-		}
-		public function __toString(){
-			return $this->path;
-		}
-		public function __get($name){
-			switch($name){
-				case 'arguments':
-					preg_match_all('/\{([^#\/][^}\n]+?)\}/',$this->path,$m,PREG_SET_ORDER);
-					return $m;
-				break;
-				case 'regex':
-					return '/^'.preg_replace('/\\\{[^#\/][^}\n]+?\\\}/','([^\/]*)',preg_quote($this->path,'/')).'$/';
-				break;
-			}
-		}
-		public function matches(string $url){
-			return preg_match($this->regex, $url);
-		}
-		public function args(string $url){
-			$ret = [];
-			preg_match_all($this->regex,$url,$m,PREG_SET_ORDER);
-			foreach($this->arguments as $k => $arg){
-				$ret[$arg[1]] = $m[0][$k+1];
-			}
-			return new Arguments($ret);
-		}
-	}
-?>

+ 33 - 29
earray.class.php

@@ -1,34 +1,38 @@
 <?php
-	class EArray implements ArrayAccess {
-		use Events;
-		private $data = [];
-		public static function from(array $data){
-			return new static($data);
-		}
-		protected function __construct(array $data){
-			$this->data = array_values($data);
-		}
-		public function offsetExists($offset){
-			$this->fire('exists', $offset);
-			return isset($this->data[$offset]);
-		}
-		public function offsetGet($offset){
-			$this->fire('get', $offset);
-			return $this->data[$offset];
-		}
-		public function offsetSet($offset, $value){
-			$this->fire('set', $offset, $value);
-			$this->data[$offset] = $value;
-		}
-		public function offsetUnset($offset){
-			$this->fire('unset', $offset, $this[$offset]);
-			unset($this->data[$offset]);
-		}
-		public function each(callable $fn){
-			foreach($this->data as $offset => $model){
-				$fn($model, $offset);
+	namespace Juju {
+		require_once('events.trait.php');
+
+		class EArray implements \ArrayAccess {
+			use Events;
+			private $data = [];
+			public static function from(array $data){
+				return new static($data);
+			}
+			protected function __construct(array $data){
+				$this->data = array_values($data);
+			}
+			public function offsetExists($offset){
+				$this->fire('exists', $offset);
+				return isset($this->data[$offset]);
+			}
+			public function offsetGet($offset){
+				$this->fire('get', $offset);
+				return $this->data[$offset];
+			}
+			public function offsetSet($offset, $value){
+				$this->fire('set', $offset, $value);
+				$this->data[$offset] = $value;
+			}
+			public function offsetUnset($offset){
+				$this->fire('unset', $offset, $this[$offset]);
+				unset($this->data[$offset]);
+			}
+			public function each(callable $fn){
+				foreach($this->data as $offset => $model){
+					$fn($model, $offset);
+				}
+				return $this;
 			}
-			return $this;
 		}
 	}
 ?>

+ 24 - 22
events.trait.php

@@ -1,33 +1,35 @@
 <?php
-	trait Events {
-		private $events = [];
-		public function on(string $name, Callable $fn){
-			if(!isset($this->events[$name])){
-				$this->events[$name] = [];
+	namespace Juju {
+		trait Events {
+			private $events = [];
+			public function on(string $name, callable $fn){
+				if(!isset($this->events[$name])){
+					$this->events[$name] = [];
+				}
+				$this->events[$name][] = $fn;
+				return $this;
 			}
-			$this->events[$name][] = $fn;
-			return $this;
-		}
-		public function off(string $name, Callable $fn = null){
-			if(isset($this->events[$name])){
-				$a = $this->events[$name];
-				foreach($a as $k => $f){
-					if(is_null($fn) || $f == $fn){
-						array_splice($a, $k, 1);
+			public function off(string $name, callable $fn = null){
+				if(isset($this->events[$name])){
+					$a = $this->events[$name];
+					foreach($a as $k => $f){
+						if(is_null($fn) || $f == $fn){
+							array_splice($a, $k, 1);
+						}
 					}
 				}
+				return $this;
 			}
-			return $this;
-		}
-		public function fire(string $name, ...$args){
-			if(isset($this->events[$name])){
-				foreach($this->events[$name] as $fn){
-					if($fn(...$args) === false){
-						return false;
+			public function fire(string $name, ...$args){
+				if(isset($this->events[$name])){
+					foreach($this->events[$name] as $fn){
+						if($fn(...$args) === false){
+							return false;
+						}
 					}
 				}
+				return true;
 			}
-			return true;
 		}
 	}
 ?>

+ 7 - 0
migration.abstract.class.php

@@ -0,0 +1,7 @@
+<?php
+	namespace Juju {
+		abstract class Migration {
+
+		}
+	}
+?>

+ 382 - 378
orm.abstract.class.php

@@ -1,416 +1,420 @@
 <?php
-	require_once('sql.class.php');
-	require_once('relationship.class.php');
-	abstract class ORM implements ArrayAccess, JsonSerializable {
-		// Model definition
-		protected  static $table_name = null;
-		protected static $primary_key = 'id';
-		protected static $foreign_key_suffix = '_id';
-		protected static $has_one = [];
-		protected static $has_many = [];
-		protected static $belongs_to = [];
-		// Data tracking
-		private $_data = [];
-		private $_changed = [];
-		private $_related = [];
-		private static $aliases = [];
-		private static $instances = [];
-		private static $sql;
-		// Magic functions
-		private function __construct($idOrData){
-			if(!isset(self::$aliases[$this->name])){
-				$aliases = [
-					'belongs_to' => [],
-					'has_one' => [],
-					'has_many' => []
-				];
-				if(is_array(static::$belongs_to)){
-					foreach(static::$belongs_to as $alias => $details){
-						$aliases['belongs_to'][$alias] = array_merge(
-							[
-								'model'=>$alias,
-								'foreign_key'=>$this->name.static::foreign_key_suffix()
-							],
-							$details
-						);
+	namespace Juju {
+		require_once('sql.class.php');
+		require_once('ORM/relationship.class.php');
+		use \Juju\ORM\Relationship as Relationship;
+
+		abstract class ORM implements \ArrayAccess, \JsonSerializable {
+			// Model definition
+			protected  static $table_name = null;
+			protected static $primary_key = 'id';
+			protected static $foreign_key_suffix = '_id';
+			protected static $has_one = [];
+			protected static $has_many = [];
+			protected static $belongs_to = [];
+			// Data tracking
+			private $_data = [];
+			private $_changed = [];
+			private $_related = [];
+			private static $aliases = [];
+			private static $instances = [];
+			private static $sql;
+			// Magic functions
+			private function __construct($idOrData){
+				if(!isset(self::$aliases[$this->name])){
+					$aliases = [
+						'belongs_to' => [],
+						'has_one' => [],
+						'has_many' => []
+					];
+					if(is_array(static::$belongs_to)){
+						foreach(static::$belongs_to as $alias => $details){
+							$aliases['belongs_to'][$alias] = array_merge(
+								[
+									'model'=>$alias,
+									'foreign_key'=>$this->name.static::foreign_key_suffix()
+								],
+								$details
+							);
+						}
 					}
-				}
-				if(is_array(static::$has_one)){
-					foreach(static::$has_one as $alias => $details){
-						$aliases['has_one'][$alias] = array_merge(
-							[
-								'model'=>$alias,
-								'foreign_key'=>$alias.static::foreign_key_suffix()
-							],
-							$details
-						);
+					if(is_array(static::$has_one)){
+						foreach(static::$has_one as $alias => $details){
+							$aliases['has_one'][$alias] = array_merge(
+								[
+									'model'=>$alias,
+									'foreign_key'=>$alias.static::foreign_key_suffix()
+								],
+								$details
+							);
+						}
+					}
+					if(is_array(static::$has_many)){
+						foreach(static::$has_many as $alias => $details){
+							$aliases['has_many'][$alias] = array_merge(
+								[
+									'model'=>$alias,
+									'foreign_key'=>$this->name.static::foreign_key_suffix(),
+									'through'=> null
+								],
+								$details
+							);
+						}
 					}
+					self::$aliases[$this->name] = $aliases;
+					// Clear relationship definitions to save memory
+					static::$belongs_to = static::$has_one = static::$has_many = null;
 				}
-				if(is_array(static::$has_many)){
-					foreach(static::$has_many as $alias => $details){
-						$aliases['has_many'][$alias] = array_merge(
-							[
-								'model'=>$alias,
-								'foreign_key'=>$this->name.static::foreign_key_suffix(),
-								'through'=> null
-							],
-							$details
-						);
+				if(is_array($idOrData)){
+					if(isset($idOrData[static::primary_key()]) && self::cached($idOrData[static::primary_key()])){
+						throw new \Exception('Instance already cached');
+					}
+					foreach($idOrData as $key => $val){
+						$this->_data[$key] = $val;
 					}
+				}else{
+					if(self::cached($idOrData)){
+						throw new \Exception('Instance already cached');
+					}
+					$this->_data[static::primary_key()] = (int)$idOrData;
 				}
-				self::$aliases[$this->name] = $aliases;
-				// Clear relationship definitions to save memory
-				static::$belongs_to = static::$has_one = static::$has_many = null;
+				self::$instances[] = $this;
 			}
-			if(is_array($idOrData)){
-				if(isset($idOrData[static::primary_key()]) && self::cached($idOrData[static::primary_key()])){
-					throw new Exception('Instance already cached');
+			private function __destruct(){
+				$this->__sleep();
+				$this->_changed = [];
+				$this->_data = [];
+				$this->_related = [];
+				$key = array_search($this, self::$instances);
+				if($key !== false){
+					array_splice(self::$instances, $key, 1);
 				}
-				foreach($idOrData as $key => $val){
-					$this->_data[$key] = $val;
+			}
+			public function __get(string $name){
+				switch($name){
+					case 'name':
+						return static::table_name();
+					break;
+					case 'primary_key':
+						return static::primary_key();
+					break;
+					case 'foreign_key_suffix':
+						return static::foreign_key_suffix();
+					break;
+					case 'has_one':case 'has_many':case 'belongs_to':
+						return self::$aliases[$this->name][$name];
+					break;
+					case 'id':
+						return $this->get(static::primary_key());
+					break;
+					default:
+						throw new \Exception('Unknown property '.$name);
 				}
-			}else{
-				if(self::cached($idOrData)){
-					throw new Exception('Instance already cached');
+			}
+			public function __set(string $name, $val){
+				switch($name){
+					case 'id':case 'name':
+						throw new \Exception('Property '.$name.' is read only');
+					break;
+					default:
+						throw new \Exception('Unknown property '.$name);
 				}
-				$this->_data[static::primary_key()] = (int)$idOrData;
 			}
-			self::$instances[] = $this;
-		}
-		private function __destruct(){
-			$this->__sleep();
-			$this->_changed = [];
-			$this->_data = [];
-			$this->_related = [];
-			$key = array_search($this, self::$instances);
-			if($key !== false){
-				array_splice(self::$instances, $key, 1);
+			public function __sleep(){
+				$this->save();
 			}
-		}
-		public function __get(string $name){
-			switch($name){
-				case 'name':
-					return static::table_name();
-				break;
-				case 'primary_key':
-					return static::primary_key();
-				break;
-				case 'foreign_key_suffix':
-					return static::foreign_key_suffix();
-				break;
-				case 'has_one':case 'has_many':case 'belongs_to':
-					return self::$aliases[$this->name][$name];
-				break;
-				case 'id':
-					return $this->get(static::primary_key());
-				break;
-				default:
-					throw new Exception('Unknown property '.$name);
+			public function __wakeup(){
+				$this->reload(true);
 			}
-		}
-		public function __set(string $name, $val){
-			switch($name){
-				case 'id':case 'name':
-					throw new Exception('Property '.$name.' is read only');
-				break;
-				default:
-					throw new Exception('Unknown property '.$name);
+			public function __toString(){
+				return $this->name.'('.$this->id.')';
 			}
-		}
-		public function __sleep(){
-			$this->save();
-		}
-		public function __wakeup(){
-			$this->reload(true);
-		}
-		public function __toString(){
-			return $this->name.'('.$this->id.')';
-		}
-		public function __invoke(){
-			return $this->_data;
-		}
-		public function __clone(){
-			unset($this->$_data[static::primary_key()]);
-		}
-		// JsonSerializable
-		public function jsonSerialize(){
-			return $this->_data;
-		}
-		// ArrayAccess
-		public function offsetSet($key, $val){
-			$this->set($key, $val);
-		}
-		public function offsetExists($key){
-			return $this->has($key);
-		}
-		public function offsetUnset($key){
-			return $this->unset($key);
-		}
-		public function offsetGet($key){
-			return $this->get($key);
-		}
-		// Main API
-		public static function table_name(){
-			if(is_null(static::$table_name)){
-				$name = get_called_class();
-				return substr($name, strrpos($name, '\\') + 1);
+			public function __invoke(){
+				return $this->_data;
 			}
-			return static::$table_name;
-		}
-		public static function primary_key(){
-			return static::$primary_key;
-		}
-		public static function foreign_key_suffix(){
-			return static::$foreign_key_suffix;
-		}
-		public static function bind(SQL $sql){
-			self::$sql = $sql;
-			// @todo handle updating live instances
-		}
-		public static function query(...$args){
-			return self::$sql->query(...$args);
-		}
-		public static function instance(int $id){
-			$instance = self::cached_instance($id);
-			if(!is_null($instance)){
-				return $instance;
-			}elseif(self::exists($id)){
-				return new static($id);
-			}
-			return null;
-		}
-		public static function exists(int $id){
-			return (int)self::query(
-				"select count(1) as count ".
-				"from ".static::table_name().' '.
-				"where ".static::primary_key()." = ?",
-				'i',
-				$id
-			)->assoc_result["count"] > 0;
-		}
-		public static function cached_instance(int $id){
-			$name = static::table_name();
-			if(isset(self::$instances[$name])){
-				$instances = array_filter(self::$instances[$name], function(&$instance){
-					return $instance->id === $id;
-				});
-				return isset($instances[0]) ? $instances[0] : null;
+			public function __clone(){
+				unset($this->$_data[static::primary_key()]);
 			}
-			return null;
-		}
-		public static function cached(int $id){
-			return !is_null(self::cached_instance($id));
-		}
-		public static function delete(int $id){
-			$query = self::query(
-				"delete from ".static::table_name().' '.
-				"where ".static::primary_key()." = ?",
-				'i',
-				$id
-			);
-			return $query->execute() && $query->affected_rows > 0;
-		}
-		public static function each_cached(callable $fn){
-			$name = static::table_name();
-			if(self::$instances[$name]){
-				array_walk(self::$instances[$name], $fn);
+			// JsonSerializable
+			public function jsonSerialize(){
+				return $this->_data;
 			}
-		}
-		public static function each_where(callable $fn, array $filter = null, int $start = null, int $amount = null){
-			$limit = ' ';
-			if(!is_null($start) && !is_null($amount)){
-				$limit .= "limit {$start}, {$amount}";
-			}
-			$where = ' ';
-			$types = null;
-			$bindings = null;
-			if(!is_null($filter)){
-				$types = '';
-				$bindings = array();
-				foreach($filter as $key => $val){
-					if(is_string($val)){
-						$types .= 's';
-					}elseif(is_double($val)){
-						$types .= 'd';
-					}elseif(is_int($val)){
-						$types .= 'i';
-					}else{
-						throw new Exception("Unknown data type");
-					}
-					$where .= 'and {$key} = ? ';
-					$bindings[] = $val;
-				}
-				$where = self::str_replace_first(' and ', ' ', $where);
-			}
-			self::query(
-				"select ".static::primary_key().' id '.
-				"from ".static::table_name().
-				$where.
-				$limit,
-				$types,
-				$bindings
-			)->each_assoc(function($row) use($fn){
-				$fn(self::instance((int)$row['id']));
-			});
-		}
-		public static function each(callable $fn, int $start = null, int $amount = null){
-			self::each_where($fn, null, $start, $amount);
-		}
-		public static function fetch(array $filter = null, int $start = null, int $amount = null){
-			$results = [];
-			self::each_where(function($item) use($results){
-				$results[] = $item;
-			}, $filter, $start, $amount);
-			return $results;
-		}
-		public static function str_replace_first($search, $replace, $source) {
-			$explode = explode($search, $source);
-			$shift = array_shift($explode);
-			$implode = implode($search, $explode);
-			return $shift.$replace.$implode;
-		}
-		// Instance Api
-		public function values($values){
-			foreach($values as $key => $val){
+			// ArrayAccess
+			public function offsetSet($key, $val){
 				$this->set($key, $val);
 			}
-			return $this;
-		}
-		public function load(bool $force = false){
-			if(!$force && $this->dirty()){
-				throw new Exception('Cannot load, there are pending changes');
-			}else{
-				if(!is_null($this->id)){
-					$data = self::query(
-						"select * " .
-						"from {$this->name} ".
-						"where ".static::primary_key()." = ?",
-						'i',
-						$this->id
-					)->assoc_result;
-					if($data === false){
-						throw new Exception("{$this->name} with ".static::primary_key()." of {$this->id} does not exist");
+			public function offsetExists($key){
+				return $this->has($key);
+			}
+			public function offsetUnset($key){
+				return $this->unset($key);
+			}
+			public function offsetGet($key){
+				return $this->get($key);
+			}
+			// Main API
+			public static function table_name(){
+				if(is_null(static::$table_name)){
+					$name = get_called_class();
+					return substr($name, strrpos($name, '\\') + 1);
+				}
+				return static::$table_name;
+			}
+			public static function primary_key(){
+				return static::$primary_key;
+			}
+			public static function foreign_key_suffix(){
+				return static::$foreign_key_suffix;
+			}
+			public static function bind(SQL $sql){
+				self::$sql = $sql;
+				// @todo handle updating live instances
+			}
+			public static function query(...$args){
+				return self::$sql->query(...$args);
+			}
+			public static function instance(int $id){
+				$instance = self::cached_instance($id);
+				if(!is_null($instance)){
+					return $instance;
+				}elseif(self::exists($id)){
+					return new static($id);
+				}
+				return null;
+			}
+			public static function exists(int $id){
+				return (int)self::query(
+					"select count(1) as count ".
+					"from ".static::table_name().' '.
+					"where ".static::primary_key()." = ?",
+					'i',
+					$id
+				)->assoc_result["count"] > 0;
+			}
+			public static function cached_instance(int $id){
+				$name = static::table_name();
+				if(isset(self::$instances[$name])){
+					$instances = array_filter(self::$instances[$name], function(&$instance){
+						return $instance->id === $id;
+					});
+					return isset($instances[0]) ? $instances[0] : null;
+				}
+				return null;
+			}
+			public static function cached(int $id){
+				return !is_null(self::cached_instance($id));
+			}
+			public static function delete(int $id){
+				$query = self::query(
+					"delete from ".static::table_name().' '.
+					"where ".static::primary_key()." = ?",
+					'i',
+					$id
+				);
+				return $query->execute() && $query->affected_rows > 0;
+			}
+			public static function each_cached(callable $fn){
+				$name = static::table_name();
+				if(self::$instances[$name]){
+					array_walk(self::$instances[$name], $fn);
+				}
+			}
+			public static function each_where(callable $fn, array $filter = null, int $start = null, int $amount = null){
+				$limit = ' ';
+				if(!is_null($start) && !is_null($amount)){
+					$limit .= "limit {$start}, {$amount}";
+				}
+				$where = ' ';
+				$types = null;
+				$bindings = null;
+				if(!is_null($filter)){
+					$types = '';
+					$bindings = array();
+					foreach($filter as $key => $val){
+						if(is_string($val)){
+							$types .= 's';
+						}elseif(is_double($val)){
+							$types .= 'd';
+						}elseif(is_int($val)){
+							$types .= 'i';
+						}else{
+							throw new \Exception("Unknown data type");
+						}
+						$where .= 'and {$key} = ? ';
+						$bindings[] = $val;
 					}
-					$this->_data = $data;
+					$where = self::str_replace_first(' and ', ' ', $where);
 				}
+				self::query(
+					"select ".static::primary_key().' id '.
+					"from ".static::table_name().
+					$where.
+					$limit,
+					$types,
+					$bindings
+				)->each_assoc(function($row) use($fn){
+					$fn(self::instance((int)$row['id']));
+				});
 			}
-			return $this;
-		}
-		public function save(){
-			if($this->dirty()){
-				$data = [];
-				$set = "set ";
-				$types = '';
-				foreach($this->_changed as $key){
-					if(isset($this->_data[$key]) || is_null($this->_data[$key])){
-						$data[$key] = $this->_data[$key];
-					}else{
-						$set .= "{$key} = null";
+			public static function each(callable $fn, int $start = null, int $amount = null){
+				self::each_where($fn, null, $start, $amount);
+			}
+			public static function fetch(array $filter = null, int $start = null, int $amount = null){
+				$results = [];
+				self::each_where(function($item) use($results){
+					$results[] = $item;
+				}, $filter, $start, $amount);
+				return $results;
+			}
+			public static function str_replace_first($search, $replace, $source) {
+				$explode = explode($search, $source);
+				$shift = array_shift($explode);
+				$implode = implode($search, $explode);
+				return $shift.$replace.$implode;
+			}
+			// Instance Api
+			public function values($values){
+				foreach($values as $key => $val){
+					$this->set($key, $val);
+				}
+				return $this;
+			}
+			public function load(bool $force = false){
+				if(!$force && $this->dirty()){
+					throw new \Exception('Cannot load, there are pending changes');
+				}else{
+					if(!is_null($this->id)){
+						$data = self::query(
+							"select * " .
+							"from {$this->name} ".
+							"where ".static::primary_key()." = ?",
+							'i',
+							$this->id
+						)->assoc_result;
+						if($data === false){
+							throw new \Exception("{$this->name} with ".static::primary_key()." of {$this->id} does not exist");
+						}
+						$this->_data = $data;
 					}
 				}
-				foreach($data as $key => $val){
-					if(is_string($val)){
-						$types .= 's';
-					}elseif(is_double($val)){
-						$types .= 'd';
-					}elseif(is_int($val)){
-						$types .= 'i';
+				return $this;
+			}
+			public function save(){
+				if($this->dirty()){
+					$data = [];
+					$set = "set ";
+					$types = '';
+					foreach($this->_changed as $key){
+						if(isset($this->_data[$key]) || is_null($this->_data[$key])){
+							$data[$key] = $this->_data[$key];
+						}else{
+							$set .= "{$key} = null";
+						}
+					}
+					foreach($data as $key => $val){
+						if(is_string($val)){
+							$types .= 's';
+						}elseif(is_double($val)){
+							$types .= 'd';
+						}elseif(is_int($val)){
+							$types .= 'i';
+						}else{
+							throw new \Exception('Unknown data type');
+						}
+						$set .= "{$key} = ? ";
+					}
+					if(!is_null($this->id) && !in_array(static::primary_key(), $this->_changed)){
+						$data = array_merge(array_values($data), [$this->id]);
+						self::query(
+							"update {$this->name} {$set} where ".static::primary_key()." = ?",
+							$types.'i',
+							$data
+						)->execute();
 					}else{
-						throw new Exception('Unknown data type');
+						self::query(
+							"insert {$this->name} {$set}"
+						)->execute();
+						$this->_data[static::primary_key()] = self::$sql->insert_id;
+					}
+					foreach($this->_related as $related){
+						$related->save();
 					}
-					$set .= "{$key} = ? ";
 				}
-				if(!is_null($this->id) && !in_array(static::primary_key(), $this->_changed)){
-					$data = array_merge(array_values($data), [$this->id]);
-					self::query(
-						"update {$this->name} {$set} where ".static::primary_key()." = ?",
-						$types.'i',
-						$data
-					)->execute();
-				}else{
-					self::query(
-						"insert {$this->name} {$set}"
-					)->execute();
-					$this->_data[static::primary_key()] = self::$sql->insert_id;
+				// Always fetch again from the database in case saving
+				// forces something to change at the database level
+				return $this->reload(true);
+			}
+			public function reload(bool $force = false){
+				if($force){
+					$this->_changed = [];
 				}
-				foreach($this->_related as $related){
-					$related->save();
+				return $this->load($force);
+			}
+			public function clear(){
+				return $this->reload(true);
+			}
+			public function get($key){
+				return $this->_data[$key];
+			}
+			public function set($key, $val){
+				if($key === static::primary_key() && !is_null($this->id)){
+					throw new \Exception('You are not allowed to change the primary key');
 				}
+				$this->_data[$key] = $val;
+				$this->_changed = array_merge($this->_changed, [$key]);
+				return $this;
 			}
-			// Always fetch again from the database in case saving
-			// forces something to change at the database level
-			return $this->reload(true);
-		}
-		public function reload(bool $force = false){
-			if($force){
-				$this->_changed = [];
+			public function unset($key){
+				unset($this->_data[$key]);
+				return $this;
 			}
-			return $this->load($force);
-		}
-		public function clear(){
-			return $this->reload(true);
-		}
-		public function get($key){
-			return $this->_data[$key];
-		}
-		public function set($key, $val){
-			if($key === static::primary_key() && !is_null($this->id)){
-				throw new Exception('You are not allowed to change the primary key');
+			public function has($key){
+				return isset($this->_data[$key]);
 			}
-			$this->_data[$key] = $val;
-			$this->_changed = array_merge($this->_changed, [$key]);
-			return $this;
-		}
-		public function unset($key){
-			unset($this->_data[$key]);
-			return $this;
-		}
-		public function has($key){
-			return isset($this->_data[$key]);
-		}
-		public function dirty(string $key = null){
-			if(is_null($key)){
-				return count($this->_changed) > 0;
-			}else{
-				return in_array($key, $this->_changed);
+			public function dirty(string $key = null){
+				if(is_null($key)){
+					return count($this->_changed) > 0;
+				}else{
+					return in_array($key, $this->_changed);
+				}
 			}
-		}
-		public function related(string $name){
-			if(!isset($this->_related[$name])){
-				$aliases = self::$aliases[$this->name];
-				if(isset($aliases['belongs_to'][$name])){
-					$alias = $aliases['has_many'][$name];
-					$class = "Models\\{$alias['model']}";
-					$this->_related[$name] = $class::fetch([$alias['foreign_key'] => $this->id])[0];
-				}elseif(isset($aliases['has_one'][$name])){
-					$alias = $aliases['has_many'][$name];
-					$class = "Models\\{$alias['model']}";
-					$this->_related[$name] = $class::instance($this[$alias['foreign_key']]);
-				}elseif(isset($aliases['has_many'][$name])){
-					$alias = $aliases['has_many'][$name];
-					$class = "Models\\{$alias['model']}";
-					$sql = "select ";
-					if($alias['through']){
-						$sql .= $class::table_name().$class::foreign_key_suffix()." id from {$alias['through']} ";
-					}else{
-						$sql .= $class::primary_key()." id from {$alias['model']} ";
+			public function related(string $name){
+				if(!isset($this->_related[$name])){
+					$aliases = self::$aliases[$this->name];
+					if(isset($aliases['belongs_to'][$name])){
+						$alias = $aliases['has_many'][$name];
+						$class = "Models\\{$alias['model']}";
+						$this->_related[$name] = $class::fetch([$alias['foreign_key'] => $this->id])[0];
+					}elseif(isset($aliases['has_one'][$name])){
+						$alias = $aliases['has_many'][$name];
+						$class = "Models\\{$alias['model']}";
+						$this->_related[$name] = $class::instance($this[$alias['foreign_key']]);
+					}elseif(isset($aliases['has_many'][$name])){
+						$alias = $aliases['has_many'][$name];
+						$class = "Models\\{$alias['model']}";
+						$sql = "select ";
+						if($alias['through']){
+							$sql .= $class::table_name().$class::foreign_key_suffix()." id from {$alias['through']} ";
+						}else{
+							$sql .= $class::primary_key()." id from {$alias['model']} ";
+						}
+						$sql .= "where ".$alias['foreign_key']." = ?";
+						$related = [];
+						self::query(
+							$sql,
+							'i',
+							$this->id
+						)->each_assoc(function($row) use(&$related, $class){
+							$related[] = new $class($row['id']);
+						});
+						$this->_related[$name] = Relationship::from($this, $name, $alias, $related);
 					}
-					$sql .= "where ".$alias['foreign_key']." = ?";
-					$related = [];
-					self::query(
-						$sql,
-						'i',
-						$this->id
-					)->each_assoc(function($row) use(&$related, $class){
-						$related[] = new $class($row['id']);
-					});
-					$this->_related[$name] = Relationship::from($this, $name, $alias, $related);
 				}
-			}
-			if(isset($this->_related[$name])){
-				return $this->_related[$name];
-			}else{
-				throw new Exception("Relationship {$name} does not exist");
+				if(isset($this->_related[$name])){
+					return $this->_related[$name];
+				}else{
+					throw new \Exception("Relationship {$name} does not exist");
+				}
 			}
 		}
 	}

+ 0 - 47
path.class.php

@@ -1,47 +0,0 @@
-<?php
-	require_once('dypath.class.php');
-	require_once('request.class.php');
-	require_once('response.class.php');
-	require_once('arguments.class.php');
-	class Path implements JsonSerializable{
-		private $handles = [];
-		public $path;
-		public function __construct(string $path){
-			$this->path = new DyPath($path);
-		}
-		public function __invoke(Request $req, Response $res, Arguments $args){
-			$err = null;
-			foreach($this->handles as $k => $fn){
-				try{
-					$fn($req, $res, $args, $err);
-				}catch(Exception $e){
-					$err = $e;
-				}
-			}
-		}
-		public function __clone(){
-			// No cloning for now
-		}
-		public function __destruct(){
-			// Nothing to do here
-		}
-		public function jsonSerialize(){
-			return [
-				'dypath'=>$this->path
-			];
-		}
-		public function __toString(){
-			return "[Path {$this->path}]";
-		}
-		public function handle(Callable $fn){
-			array_push($this->handles,$fn);
-			return $this;
-		}
-		public function matches(string $path){
-			return $this->path->matches($path);
-		}
-		public function args(string $path){
-			return $this->path->args($path);
-		}
-	}
-?>

+ 0 - 95
relationship.class.php

@@ -1,95 +0,0 @@
-<?php
-	require_once('earray.class.php');
-	class Relationship extends EArray {
-		private $model;
-		private $name;
-		private $alias;
-		private $removed = [];
-		private $added = [];
-		public static function from(ORM $model, string $name, array $alias, array $data){
-			$earray = parent::from($data);
-			$earray->model = $model;
-			$earray->name = $name;
-			$earray->alias = $alias;
-			return $earray
-				->on('set', function(&$offset, &$value) use($earray){
-					$earray->added = array_merge($earray->added, [$value]);
-					$earray->removed = array_diff($earray->removed, [$value]);
-				})->on('unset', function($offset, $value) use($earray){
-					$earray->removed = array_merge($earray->removed, [$value]);
-					$earray->added = array_diff($earray->added, [$value]);
-				});
-		}
-		public function add(ORM $model){
-			$this[] = $model;
-			return $this;
-		}
-		public function remove(ORM $model){
-			$index = $this->index($model);
-			if($index !== false){
-				unset($this->data[$index]);
-			}
-			return $this;
-		}
-		public function has(ORM $model){
-			return $this->index($model) !== false;
-		}
-		public function index(ORM $model){
-			return array_search($model, $this->data);
-		}
-		public function dirty(){
-			return count($this->removed) + count($this->added) > 0;
-		}
-		public function reset(){
-			foreach($this->added as $model){
-				$index = array_search($model, $this->data);
-				if($index !== false){
-					unset($this->data[$index]);
-				}
-			}
-			$this->added = [];
-			foreach($this->removed as $model){
-				$this->data = array_merge($this->data, [$model]);
-			}
-			$this->removed = [];
-		}
-		public function save(){
-			if($this->dirty()){
-				$model = $this->model;
-				$alias = $this->$alias;
-				$name = $this->name;
-				$class = "Models\\{$alias['model']}";
-				if(isset($model->has_many[$name])){
-					throw new Exception("Invalid relationship {$name}");
-				}
-				if(isset($alias['through'])){
-					$rem_sql = "delete from {$alias['through']} where {$alias['foreign_key']} = ? and ".$class::table_name().$class::foreign_key_suffix()." = ?";
-					$rem_args = [$model->id];
-					$add_sql = "insert into {$alias['through']} ({$alias['foreign_key']}, ".$class::table_name().$class::foreign_key_suffix().") values (?, ?)";
-				}else{
-					$rem_sql = "update {$alias['model']} set {$alias['foreign_key']} = null where ".$class::primary_key()." = ?";
-					$rem_args = [];
-					$add_sql .= "update {$alias['model']} set {$alias['foreign_key']} = ? where ".$class::primary_key()." = ?";
-				}
-				$class = get_class($models);
-				foreach($this->removed as $item){
-					$class::query(
-						$rem_sql,
-						'ii',
-						...array_merge($rem_args, [$item->id])
-					)->execute();
-				}
-				$this->removed = [];
-				foreach($this->added as $item){
-					$class::query(
-						$add_sql,
-						'ii',
-						$model->id,
-						$item->id
-					)->execute();
-				}
-				$this->added = [];
-			}
-		}
-	}
-?>

+ 0 - 44
request.class.php

@@ -1,44 +0,0 @@
-<?php
-	class Request {
-		private $url;
-		private $headers;
-		private $body;
-		public function __construct($url, array $headers = [], string $body = ''){
-			if(is_string($url) || is_array($url)){
-				$this->url = new Uri($url);
-			}elseif($url instanceof Uri){
-				$this->url = $url;
-			}else{
-				throw new Exception("Invalid url {$url}");
-			}
-			if(is_array($headers)){
-				$this->headers = $headers;
-			}else{
-				$this->headers = [];
-			}
-			$this->body = $body;
-		}
-		public function __get($name){
-			switch($name){
-				case 'headers':
-					return $this->headers;
-				break;
-				case 'url':
-					return $this->url;
-				break;
-				case 'body':
-					return $this->body;
-				break;
-			}
-		}
-		public function header($name){
-			return $this->headers[$name] ?? null;
-		}
-		public function json(){
-			return json_decode($this->body, true);
-		}
-		public function text(){
-			return $this->body;
-		}
-	}
-?>

+ 0 - 125
response.class.php

@@ -1,125 +0,0 @@
-<?php
-	require_once('events.trait.php');
-	class Response {
-		use Events;
-		public $output = '';
-		public $body = '';
-		private $code = 200;
-		public $headers = [];
-		protected $open = true;
-		public function __construct(){}
-		public function __toString(){
-			return $this->body;
-		}
-		public function clear(){
-			if($this->open){
-				$this->fire('clear');
-				$this->body = '';
-			}
-			return $this;
-		}
-		public function clear_headers(){
-			if($this->open){
-				$this->fire('clear_headers');
-				$this->headers = [];
-			}
-			return $this;
-		}
-		public function clear_header(string $name){
-			foreach($this->headers as $key => $header){
-				if($header[0] == $name){
-					$this->fire('clear_header', $name);
-					array_splice($this->headers, $key, 1);
-				}
-			}
-			return $this;
-		}
-		public function write(string $chunk){
-			$this->fire('write', $chunk);
-			if($this->open){
-				$this->body .= $chunk;
-			}
-			return $this;
-		}
-		public function json($json){
-			if(is_array($json)){
-				array_walk_recursive($json, function(&$item, $key){
-					if(!mb_detect_encoding($item, 'utf-8', true)){
-						$item = utf8_encode($item);
-					}
-				});
-			}
-			$this->fire('json', $json);
-			$this->write(json_encode($json));
-			if(json_last_error() != JSON_ERROR_NONE){
-				throw new Exception(json_last_error_msg());
-			}
-			return $this;
-		}
-		public function header(string $name, string $value){
-			if($this->open){
-				$this->fire('header', $name, $value);
-				array_push(
-					$this->headers,
-					[
-						$name,
-						$value
-					]
-				);
-			}
-			return $this;
-		}
-		public function redirect(string $url){
-			$this->fire('redirect', $url);
-			$this->header('Location',Router::url($url));
-			return $this;
-		}
-		public function end(string $chunk=''){
-			if($this->open){
-				$this->write($chunk);
-				$this->fire('end');
-				$this->open = false;
-			}
-			return $this;
-		}
-		public function img(Image $img, string $type = null){
-			if(!$type){
-				$type = $img->type;
-			}
-			$this->fire('image', $img, $type);
-			$this->clear_header('Content-Type')
-				->header('Content-Type', 'image/'.$type);
-			if(!is_a($img, 'Image')){
-				$img = new Image(100, 20);
-				$img->text('Invalid Image',0,0,'black',12);
-			}
-			ob_start();
-			$img();
-			$this->write(ob_get_contents());
-			ob_end_clean();
-			return $this;
-		}
-		public function code(int $code=null){
-			if(is_null($code)){
-				return $this->code;
-			}
-			$this->fire('code', $code);
-			$this->code = $code;
-			return $this;
-		}
-		public function shutdown(){
-			$this->fire('beforeshutdown');
-			if($this->open){
-				$this->end();
-			}
-			$this->fire('shutdown');
-			http_response_code($this->code);
-			foreach($this->headers as $k => $header){
-				header("{$header[0]}: $header[1]");
-			}
-			echo $this->body;
-			flush();
-			$this->fire('aftershutdown');
-		}
-	}
-?>

+ 0 - 130
router.class.php

@@ -1,130 +0,0 @@
-<?php
-	require_once('path.class.php');
-	require_once('response.class.php');
-	require_once('request.class.php');
-	require_once('events.trait.php');
-	class Router {
-		use Events;
-		private $_paths = [];
-		private $_routers = [];
-		private $_base = '/';
-		private $responses = [];
-		private $_handled = false;
-		public function __construct(string $base = null, array $paths = null){
-			if($paths != null){
-				$this->paths($paths);
-			}
-			if($base != null){
-				$this->base($base);
-			}
-		}
-		public function __get($name){
-			switch($name){
-				case 'base':
-					return $this->_base;
-				break;
-				case 'handled':
-					return $this->_handled;
-				break;
-			}
-		}
-		public function __clone(){
-			// No cloning
-		}
-		public function __destruct(){
-			$this->_paths = [];
-		}
-		public function __toString(){
-			return "[Router]";
-		}
-		public function base(string $base){
-			$this->_base = $base;
-		}
-		public function url(string $url){
-			return preg_replace('/(\/+)/','/',$url);
-		}
-		public function prefix(string $prefix, Callable $fn){
-			$found = false;
-			foreach($this->_routers as $k => $router){
-				if($router->base == $prefix){
-					$found = true;
-					$fn($router);
-					break;
-				}
-			}
-			if(!$found){
-				$router= new Router($prefix);
-				$this->_routers[] = $router;
-				$fn($router);
-			}
-		}
-		// fn = function(response,args){}
-		public function path(string $path, Callable $fn){
-			$obj = false;
-			foreach($this->_paths as $k => $p){
-				if($p->path == $path){
-					$obj = $p;
-				}
-			}
-			if(!$obj){
-				$obj = new Path($path);
-				array_push($this->_paths,$obj);
-			}
-			return $obj->handle($fn);
-		}
-		public function paths(array $paths){
-			foreach($paths as $path => $fn){
-				$this->path($path,$fn);
-			}
-		}
-		public function clear(){
-			$this->_paths = [];
-		}
-		public function handle(string $path, Request $req = null,Response $res = null, Callable $fn = null, Callable $onerror = null){
-			if(strpos($path, $this->base) !== false){
-				$path = substr($path,strpos($path,$this->base)+strlen($this->base));
-				if($path[0] != '/'){
-					$path = '/'.$path;
-				}
-				if(is_null($req)){
-					$req = new Request();
-				}
-				if(is_null($res)){
-					$res = new Response();
-				}
-				if(!in_array($res,$this->responses)){
-					array_push($this->responses,$res);
-				}
-				$this->fire('handle', $req, $res);
-				$handled = false;
-				foreach($this->_routers as $prefix => $router){
-					$router->handle($path, $req, $res);
-					$handled = $handled ||$router->handled;
-				}
-				ob_start();
-				foreach($this->_paths as $k => $p){
-					if($p->matches($path)){
-						$handled = true;
-						try{
-							$p($req, $res, $p->args($path));
-						}catch(Exception $e){
-							if(!is_null($onerror)){
-								$onerror($req, $res,$e);
-							}else{
-								throw $e;
-							}
-						}
-					}
-				}
-				$this->_handled = $handled;
-				if(!$handled && !is_null($fn)){
-					$fn($req, $res);
-				}
-				$res->output .= ob_get_contents();
-				ob_end_clean();
-				$this->fire('afterhandle', $req, $res);
-			}
-			return $res;
-		}
-	}
-?>

+ 80 - 76
sql.class.php

@@ -1,86 +1,90 @@
 <?php
-	require_once('SQL/query.class.php');
-	/**
-	* SQL class. Used for handling SQL connections
-	*
-	* @module sql
-	* @class SQL
-	* @constructor
-	*/
-	class SQL {
+	namespace Juju {
+		require_once('SQL/query.class.php');
+		use \Juju\SQL\Query;
+
 		/**
-		* This is the mysqli connection beneath everything
-		* 
-		* @property sql
-		* @type {mysqli}
-		* @private
-		* @required
+		* SQL class. Used for handling SQL connections
+		*
+		* @module sql
+		* @class SQL
+		* @constructor
 		*/
-		private $guid;
-		private $sql;
-		public $queries = [];
-		private static $connections = [];
-		public function __construct($server,$user,$pass,$db){
-			$this->guid = uniqid();
-			$this->sql = new mysqli('p:'.$server,$user,$pass,$db) or die('Unable to connect to mysql');
-			self::$connections[] = $sql;
-		}
-		public function __destruct(){
-			$this->sql->rollback();
-			$this->sql->close();
-			foreach($this->queries as $query){
-				unset($query);
+		class SQL {
+			/**
+			* This is the mysqli connection beneath everything
+			* 
+			* @property sql
+			* @type {mysqli}
+			* @private
+			* @required
+			*/
+			private $guid;
+			private $sql;
+			public $queries = [];
+			private static $connections = [];
+			public function __construct($server,$user,$pass,$db){
+				$this->guid = uniqid();
+				$this->sql = new \mysqli('p:'.$server,$user,$pass,$db) or die('Unable to connect to mysql');
+				self::$connections[] = $sql;
 			}
-			self::$connections = array_diff(self::$connections, [$this]);
-		}
-		public function __invoke(){
-			return $this->sql;
-		}
-		public function __get($name){
-			switch($name){
-				case 'error':
-					return $this->sql->error;
-				break;
-				case 'insert_id':
-					return $this->sql->insert_id;
-				break;
+			public function __destruct(){
+				$this->sql->rollback();
+				$this->sql->close();
+				foreach($this->queries as $query){
+					unset($query);
+				}
+				self::$connections = array_diff(self::$connections, [$this]);
 			}
-		}
-		public function __toString(){
-			return $this->guid;
-		}
-		/**
-		* Returns a Query object based on inputs
-		*
-		* @method query
-		* @param {String} sql The sql expression to run
-		* @param {String=null} [types] A string containing all the types of arguments being passed
-		* @param {Mixed} [bindings]* The bindings to use in the sql statement
-		* @return {Query} Returns the query object
-		*/
-		public function query(...$args){
-			return new SQL\Query(...array_merge([$this], $args));
-		}
-		public function escape($s){
-			return $this->sql->escape_string($s);
-		}
-		public function charset($charset){
-			return $this->sql->set_charset($charset);
-		}
-		public static function make_referenced(&$arr){
-			$refs = [];
-			foreach($arr as $key => $value){
-				$refs[$key] = &$arr[$key];
+			public function __invoke(){
+				return $this->sql;
 			}
-			return $refs;
-		}
-		public static function shutdown(){
-			foreach(self::$connections as $sql){
-				unset($sql);
+			public function __get($name){
+				switch($name){
+					case 'error':
+						return $this->sql->error;
+					break;
+					case 'insert_id':
+						return $this->sql->insert_id;
+					break;
+				}
+			}
+			public function __toString(){
+				return $this->guid;
+			}
+			/**
+			* Returns a Query object based on inputs
+			*
+			* @method query
+			* @param {String} sql The sql expression to run
+			* @param {String=null} [types] A string containing all the types of arguments being passed
+			* @param {Mixed} [bindings]* The bindings to use in the sql statement
+			* @return {Query} Returns the query object
+			*/
+			public function query(...$args){
+				return new SQL\Query(...array_merge([$this], $args));
+			}
+			public function escape($s){
+				return $this->sql->escape_string($s);
+			}
+			public function charset($charset){
+				return $this->sql->set_charset($charset);
+			}
+			public static function make_referenced(&$arr){
+				$refs = [];
+				foreach($arr as $key => $value){
+					$refs[$key] = &$arr[$key];
+				}
+				return $refs;
+			}
+			public static function shutdown(){
+				foreach(self::$connections as $sql){
+					unset($sql);
+				}
 			}
 		}
+		register_shutdown_function(function(){
+			SQL::shutdown();
+		});
 	}
-	register_shutdown_function(function(){
-		SQL::shutdown();
-	});
 ?>

+ 0 - 56
uri.class.php

@@ -1,56 +0,0 @@
-<?php
-	class Uri{
-		private $url;
-		public function __construct($url){
-			if(is_array($url)){
-				$this->url = $url;
-			}else if(is_string($url)){
-				$this->url = parse_url($url);
-			}else{
-				throw new Exception("Invalid Url");
-			}
-		}
-		public function __get($name){
-			if(isset($this->url[$name])){
-				return $this->url[$name];
-			}else{
-				switch($name){
-					case 'variables':
-						parse_str((string)$this, $output);
-						return $output;
-					break;
-				}
-			}
-		}
-		public function __set($name, $value){
-			if(isset($this->url[$name])){
-				$this->url[$name] = $value;
-			}
-		}
-		public function __toString(){
-			$port = $this->port;
-			if($port){
-				if($this->scheme == 'http'){
-					$port = $port == 80 ? "" : ":{$port}";
-				}elseif($this->scheme = 'https'){
-					$port = $port == 443 ? "" : ":{$port}";
-				}
-				// @todo - add other default port types
-			}
-			$auth = $this->user;
-			if($auth){
-				$auth = $this->pass ? "{$auth}:{$this->pass}@" : "{$auth}@";
-			}
-			$query = $this->query;
-			if($query){
-				$query = "?{$query}";
-			}
-			$fragmanet = $this->fragmanet;
-			if($fragmanet){
-				$fragmanet = "#{$fragmanet}";
-			}
-			// @todo - handle when scheme requires other formats
-			return "{$this->scheme}://{$auth}{$this->host}{$port}{$this->path}{$query}{$fragmanet}";
-		}
-	}
-?>