Browse Source

* Use different style for formatting
* Make use statements smaller
* Add controller.abstract.class.php and settings.class.php
* When doing an application bind, if there is no argument then bind to all the available controllers

Nathaniel van Diepen 7 years ago
parent
commit
f1b18e933c

+ 28 - 28
App/arguments.class.php

@@ -1,33 +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]);
+	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]);
+		}
 	}
 ?>

+ 27 - 0
App/controller.abstract.class.php

@@ -0,0 +1,27 @@
+<?php
+	namespace Juju\App;
+	require_once('router.class.php');
+	use \Juju\Base;
+
+	abstract class Controller {
+		private $name;
+		public abstract static function handle(Router $router);
+		public static function controllers(){
+			return array_filter(get_declared_classes(), function($class){
+				return substr($class, 0, 11) == "Controller\\";
+			});
+		}
+		public static function handle_all(Router $router){
+			foreach(self::controllers() as $controller){
+				$controller::handle($router);
+			}
+		}
+		public static function name(){
+			if(is_null(static::$name)){
+				$name = get_called_class();
+				return substr($name, strrpos($name, '\\') + 1);
+			}
+			return static::$name;
+		}
+	}
+?>

+ 36 - 37
App/dypath.class.php

@@ -1,44 +1,43 @@
 <?php
-	namespace Juju\App {
-		require_once('arguments.class.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);
+	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 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);
+		}
+		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);
 		}
 	}
 ?>

+ 17 - 17
App/exception.class.php

@@ -1,23 +1,23 @@
 <?php
-	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 = []){
-				parent::__construct($message, $code, $previous);
-				if(!is_null($file)){
-					$this->file = $file;
-				}
-				if(!is_null($line)){
-					$this->line = $line;
-				}
-				if(!is_null($trace)){
-					$this->trace = $trace;
-				}
-				$this->included = $included;
+	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 = []){
+			parent::__construct($message, $code, $previous);
+			if(!is_null($file)){
+				$this->file = $file;
 			}
-			public function getIncluded(){
-				return $this->included;
+			if(!is_null($line)){
+				$this->line = $line;
 			}
+			if(!is_null($trace)){
+				$this->trace = $trace;
+			}
+			$this->included = $included;
+		}
+		public function getIncluded(){
+			return $this->included;
 		}
 	}
 ?>

+ 44 - 45
App/path.class.php

@@ -1,52 +1,51 @@
 <?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};
+	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;
-					}
+	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);
-			}
+		}
+		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);
 		}
 	}
 ?>

+ 112 - 114
App/router.class.php

@@ -1,137 +1,135 @@
 <?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};
+	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\{Http\Request, Http\Response, Events};
 
-		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);
-				}
+	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);
 			}
-			public function __get($name){
-				switch($name){
-					case 'base':
-						return $this->_base;
-					break;
-					case 'handled':
-						return $this->_handled;
+			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;
 				}
 			}
-			public function __clone(){
-				// No cloning
-			}
-			public function __destruct(){
-				$this->_paths = [];
+			if(!$found){
+				$router= new Router($prefix);
+				$this->_routers[] = $router;
+				$fn($router);
 			}
-			public function __toString(){
-				return "[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;
+				}
 			}
-			public function base(string $base){
-				$this->_base = $base;
+			if(!$obj){
+				$obj = new Path($path);
+				array_push($this->_paths,$obj);
 			}
-			public function url(string $url){
-				return preg_replace('/(\/+)/','/',$url);
+			return $obj->handle($fn);
+		}
+		public function paths(array $paths){
+			foreach($paths as $path => $fn){
+				$this->path($path,$fn);
 			}
-			public function prefix(string $prefix, callable $fn){
-				$found = false;
-				foreach($this->_routers as $k => $router){
-					if($router->base == $prefix){
-						$found = true;
-						$fn($router);
-						break;
-					}
+		}
+		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(!$found){
-					$router= new Router($prefix);
-					$this->_routers[] = $router;
-					$fn($router);
+				if(is_null($req)){
+					$req = new Request();
 				}
-			}
-			// 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(is_null($res)){
+					$res = new Response();
 				}
-				if(!$obj){
-					$obj = new Path($path);
-					array_push($this->_paths,$obj);
+				if(!in_array($res,$this->responses)){
+					array_push($this->responses,$res);
 				}
-				return $obj->handle($fn);
-			}
-			public function paths(array $paths){
-				foreach($paths as $path => $fn){
-					$this->path($path,$fn);
+				$this->fire('handle', $req, $res);
+				$handled = false;
+				foreach($this->_routers as $prefix => $router){
+					$router->handle($path, $req, $res);
+					$handled = $handled ||$router->handled;
 				}
-			}
-			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;
-								}
+				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;
+				$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;
 		}
 	}
 ?>

+ 32 - 33
Data/earray.class.php

@@ -1,39 +1,38 @@
 <?php
-	namespace Juju\Data {
-		require_once(realpath(dirname(__DIR__).'/events.trait.php'));
-		use Juju\Events;
+	namespace Juju\Data;
+	require_once(realpath(dirname(__DIR__).'/events.trait.php'));
+	use Juju\Events;
 
-		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;
+	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;
 		}
 	}
 ?>

+ 22 - 19
Data/securestring.class.php

@@ -1,30 +1,33 @@
 <?php
-	namespace Juju\Data {
-		class SecureString implements \JsonSerializable {
-			private $data;
-			private $password;
-			private $iv;
-			private $method;
-			private static $methods = null;
-			private function __construct(string $data){
+	namespace Juju\Data;
+
+	class SecureString implements \JsonSerializable {
+		private $data;
+		private $password;
+		private $iv;
+		private $method;
+		private static $methods = null;
+		private function __construct(string $data){
+			do{
 				if(is_null(self::$methods)){
 					self::$methods = openssl_get_cipher_methods();
 				}
-				$this->method = self::$methods[random_int(0, count(self::$methods))];
+				$this->method = self::$methods[random_int(0, count(self::$methods) - 1)];
 				$ivlen = openssl_cipher_iv_length($this->method);
 				$this->password = openssl_random_pseudo_bytes($ivlen);
 				$this->iv = openssl_random_pseudo_bytes($ivlen);
 				$this->data = openssl_encrypt($data, $this->method, $this->password, \OPENSSL_RAW_DATA, $this->iv);
-			}
-			public function __toString(){
-				return openssl_decrypt($this->data, $this->method, $this->password, \OPENSSL_RAW_DATA, $this->iv);
-			}
-			public function jsonSerialize(){
-				return "{$this}";
-			}
-			public static function from(string $data){
-				return new self($data);
-			}
+			// If for some reason the data we encrypted will not decrypt, try again until it will
+			}while($data != (string)$this);
+		}
+		public function __toString(){
+			return openssl_decrypt($this->data, $this->method, $this->password, \OPENSSL_RAW_DATA, $this->iv);
+		}
+		public function jsonSerialize(){
+			return "{$this}";
+		}
+		public static function from(string $data){
+			return new self($data);
 		}
 	}
 ?>

+ 39 - 40
Http/request.class.php

@@ -1,48 +1,47 @@
 <?php
-	namespace Juju\Http {
-		require_once('uri.class.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;
+	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}");
 			}
-			public function __get($name){
-				switch($name){
-					case 'headers':
-						return $this->headers;
-					break;
-					case 'url':
-						return $this->url;
-					break;
-					case 'body':
-						return $this->body;
-					break;
-				}
+			if(is_array($headers)){
+				$this->headers = $headers;
+			}else{
+				$this->headers = [];
 			}
-			public function header($name){
-				return $this->headers[$name] ?? null;
-			}
-			public function json(){
-				return json_decode($this->body, true);
-			}
-			public function text(){
-				return $this->body;
+			$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;
+		}
 	}
 ?>

+ 109 - 110
Http/response.class.php

@@ -1,129 +1,128 @@
 <?php
-	namespace Juju\Http {
-		require_once(realpath(dirname(__DIR__).'/events.trait.php'));
-		use \Juju\Events;
+	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;
+	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 = '';
 			}
-			public function clear(){
-				if($this->open){
-					$this->fire('clear');
-					$this->body = '';
-				}
-				return $this;
+			return $this;
+		}
+		public function clear_headers(){
+			if($this->open){
+				$this->fire('clear_headers');
+				$this->headers = [];
 			}
-			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 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);
 					}
-				}
-				return $this;
+				});
 			}
-			public function write(string $chunk){
-				$this->fire('write', $chunk);
-				if($this->open){
-					$this->body .= $chunk;
-				}
-				return $this;
+			$this->fire('json', $json);
+			$this->write(json_encode($json));
+			if(json_last_error() != JSON_ERROR_NONE){
+				throw new \Exception(json_last_error_msg());
 			}
-			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;
+			return $this;
+		}
+		public function header(string $name, string $value){
+			if($this->open){
+				$this->fire('header', $name, $value);
+				array_push(
+					$this->headers,
+					[
+						$name,
+						$value
+					]
+				);
 			}
-			public function header(string $name, string $value){
-				if($this->open){
-					$this->fire('header', $name, $value);
-					array_push(
-						$this->headers,
-						[
-							$name,
-							$value
-						]
-					);
-				}
-				return $this;
+			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;
 			}
-			public function redirect(string $url){
-				$this->fire('redirect', $url);
-				$this->header('Location',Router::url($url));
-				return $this;
+			return $this;
+		}
+		public function img(Image $img, string $type = null){
+			if(!$type){
+				$type = $img->type;
 			}
-			public function end(string $chunk=''){
-				if($this->open){
-					$this->write($chunk);
-					$this->fire('end');
-					$this->open = false;
-				}
-				return $this;
+			$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);
 			}
-			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;
+			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;
 			}
-			public function code(int $code=null){
-				if(is_null($code)){
-					return $this->code;
-				}
-				$this->fire('code', $code);
-				$this->code = $code;
-				return $this;
+			$this->fire('code', $code);
+			$this->code = $code;
+			return $this;
+		}
+		public function shutdown(){
+			$this->fire('beforeshutdown');
+			if($this->open){
+				$this->end();
 			}
-			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');
+			$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');
 		}
 	}
 ?>

+ 48 - 48
Http/uri.class.php

@@ -1,58 +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");
-				}
+	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 __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 __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}";
+		}
+		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 - handle when scheme requires other formats
-				return "{$this->scheme}://{$auth}{$this->host}{$port}{$this->path}{$query}{$fragmanet}";
+				// @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}";
 		}
 	}
 ?>

+ 82 - 84
ORM/relationship.class.php

@@ -1,99 +1,97 @@
 <?php
-	namespace Juju\ORM {
-		require_once(realpath(dirname(__DIR__).'/Data/earray.class.php'));
-		use \Juju\Data\EArray;
-		use \Juju\ORM;
+	namespace Juju\ORM;
+	require_once(realpath(dirname(__DIR__).'/Data/earray.class.php'));
+	use \Juju\{ORM, Data\EArray};
 
-		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;
+	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]);
 			}
-			public function remove(ORM $model){
-				$index = $this->index($model);
+			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]);
 				}
-				return $this;
 			}
-			public function has(ORM $model){
-				return $this->index($model) !== false;
+			$this->added = [];
+			foreach($this->removed as $model){
+				$this->data = array_merge($this->data, [$model]);
 			}
-			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->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}");
 				}
-				$this->added = [];
-				foreach($this->removed as $model){
-					$this->data = array_merge($this->data, [$model]);
+				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 = [];
-			}
-			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 = [];
+				foreach($this->added as $item){
+					$class::query(
+						$add_sql,
+						'ii',
+						$model->id,
+						$item->id
+					)->execute();
 				}
+				$this->added = [];
 			}
 		}
 	}

+ 152 - 152
SQL/query.class.php

@@ -1,167 +1,167 @@
 <?php
-	namespace Juju\SQL {
-		/**
-		* Query class. Returned by \SQL::query()
-		*
-		* @class Query
-		* @constructor
-		*/
-		class Query {
-			private $query;
-			private $sql;
-			private $_result = false;
-			public function __construct($sql, $source, $types=null, ...$args){
-				$sql->queries[] = $this;
-				$args = array_merge([$types], $args);
-				$this->sql = $sql();
-				$this->query = $this->sql->stmt_init();
-				if(!$this->query->prepare($source)){
-					throw new \RuntimeException($this->query->error);
-				}
-				if(!is_null($types)){
-					if(!$this->query->bind_param(...\Juju\SQL::make_referenced($args))){
-						throw new \RuntimeException("Unable to bind parameter {$this->query->error}");
-					}
-				}
+	namespace Juju\SQL;
+
+	/**
+	* Query class. Returned by \SQL::query()
+	*
+	* @class Query
+	* @constructor
+	*/
+	class Query {
+		private $query;
+		private $sql;
+		private $_result = false;
+		public function __construct($sql, $source, $types=null, ...$args){
+			$sql->queries[] = $this;
+			$args = array_merge([$types], $args);
+			$this->sql = $sql();
+			$this->query = $this->sql->stmt_init();
+			if(!$this->query->prepare($source)){
+				throw new \RuntimeException($this->query->error);
 			}
-			public function __destruct(){
-				if($this->_result){
-					$this->_result = false;
-					$this->query->free_result();
+			if(!is_null($types)){
+				if(!$this->query->bind_param(...\Juju\SQL::make_referenced($args))){
+					throw new \RuntimeException("Unable to bind parameter {$this->query->error}");
 				}
-				if($this->query){
-					$this->query->close();
-				}
-				$sql->queries = array_diff($sql->queries, [$this]);
 			}
-			public function __invoke(){
-				return $this->query;
+		}
+		public function __destruct(){
+			if($this->_result){
+				$this->_result = false;
+				$this->query->free_result();
 			}
-			public function execute(){
-				if($this->query){
-					$this->query->free_result();
-					$this->_result = false;
-					$this->query->reset();
-					$r = $this->query->execute();
-					$this->sql->commit();
-					return $r;
-				}else{
-					return false;
-				}
+			if($this->query){
+				$this->query->close();
 			}
-			public function each_assoc(callable $fn){
-				if($this->query){
-					$r = $this->results;
-					while($row = $r->fetch_assoc()){
-						$fn($row);
-					}
-				}else{
-					return false;
+			$sql->queries = array_diff($sql->queries, [$this]);
+		}
+		public function __invoke(){
+			return $this->query;
+		}
+		public function execute(){
+			if($this->query){
+				$this->query->free_result();
+				$this->_result = false;
+				$this->query->reset();
+				$r = $this->query->execute();
+				$this->sql->commit();
+				return $r;
+			}else{
+				return false;
+			}
+		}
+		public function each_assoc(callable $fn){
+			if($this->query){
+				$r = $this->results;
+				while($row = $r->fetch_assoc()){
+					$fn($row);
 				}
+			}else{
+				return false;
 			}
-			public function each_num(callable $fn){
-				if($this->query){
-					$r = $this->results;
-					while($row = $r->fetch_num()){
-						$fn($row);
-					}
-				}else{
-					return false;
+		}
+		public function each_num(callable $fn){
+			if($this->query){
+				$r = $this->results;
+				while($row = $r->fetch_num()){
+					$fn($row);
 				}
+			}else{
+				return false;
 			}
-			public function __get($name){
-				switch($name){
-					/**
-					* Returns the mysqli::results object for the
-					* query
-					* 
-					* @property results
-					* @type {mysqli::results}
-					* @public
-					*/
-					case 'results':
-						if(!$this->_result && $this->query){
-							$this->execute();
-							$this->_result = $this->query->get_result();
-							$this->query->close();
-						}
-						return $this->_result;
-					break;
-					/**
-					* Returns an associative array of the query resulsts
-					* 
-					* @property assoc_results
-					* @type {Array}
-					* @public
-					*/
-					/**
-					* Returns an associative array of the query resulsts
-					* 
-					* @property resulsts_assoc
-					* @type {Array}
-					* @public
-					*/
-					case 'assoc_results':case 'results_assoc':
-						if($this->query){
-							$a = [];
-							$r = $this->results;
-							while($row = $r->fetch_assoc()){
-								array_push($a,$row);
-							}
-							return $a;
-						}else{
-							return false;
-						}
-					break;
-					/**
-					* Returns a numbered array of the query results
-					* 
-					* @property num_results
-					* @type {Array}
-					* @public
-					*/
-					/**
-					* Returns a numbered array of the query results
-					* 
-					* @property resulsts_num
-					* @type {Array}
-					* @public
-					*/
-					case 'num_results':case 'results_num':
-						if($this->query){
-							$a = [];
-							$r = $this->results;
-							while($row = $r->fetch_num()){
-								array_push($a,$row);
-							}
-							return $a;
-						}else{
-							return false;
-						}
-					break;
-					case 'assoc_result':case 'result_assoc':
-						if($this->query){
-							$r = $this->results;
-							return $r?$r->fetch_assoc():false;
-						}else{
-							return false;
+		}
+		public function __get($name){
+			switch($name){
+				/**
+				* Returns the mysqli::results object for the
+				* query
+				* 
+				* @property results
+				* @type {mysqli::results}
+				* @public
+				*/
+				case 'results':
+					if(!$this->_result && $this->query){
+						$this->execute();
+						$this->_result = $this->query->get_result();
+						$this->query->close();
+					}
+					return $this->_result;
+				break;
+				/**
+				* Returns an associative array of the query resulsts
+				* 
+				* @property assoc_results
+				* @type {Array}
+				* @public
+				*/
+				/**
+				* Returns an associative array of the query resulsts
+				* 
+				* @property resulsts_assoc
+				* @type {Array}
+				* @public
+				*/
+				case 'assoc_results':case 'results_assoc':
+					if($this->query){
+						$a = [];
+						$r = $this->results;
+						while($row = $r->fetch_assoc()){
+							array_push($a,$row);
 						}
-					break;
-					case 'num_result':case 'result_num':
-						if($this->query){
-							$r = $this->results;
-							return $r?$r->fetch_num():false;
-						}else{
-							return false;
+						return $a;
+					}else{
+						return false;
+					}
+				break;
+				/**
+				* Returns a numbered array of the query results
+				* 
+				* @property num_results
+				* @type {Array}
+				* @public
+				*/
+				/**
+				* Returns a numbered array of the query results
+				* 
+				* @property resulsts_num
+				* @type {Array}
+				* @public
+				*/
+				case 'num_results':case 'results_num':
+					if($this->query){
+						$a = [];
+						$r = $this->results;
+						while($row = $r->fetch_num()){
+							array_push($a,$row);
 						}
-					break;
-					case 'insert_id':
-						return $this->sql->insert_id;
-					break;
-					case 'affected_rows':
-						return $this->query->affected_rows;
-					break;
-				}
+						return $a;
+					}else{
+						return false;
+					}
+				break;
+				case 'assoc_result':case 'result_assoc':
+					if($this->query){
+						$r = $this->results;
+						return $r?$r->fetch_assoc():false;
+					}else{
+						return false;
+					}
+				break;
+				case 'num_result':case 'result_num':
+					if($this->query){
+						$r = $this->results;
+						return $r?$r->fetch_num():false;
+					}else{
+						return false;
+					}
+				break;
+				case 'insert_id':
+					return $this->sql->insert_id;
+				break;
+				case 'affected_rows':
+					return $this->query->affected_rows;
+				break;
 			}
 		}
 	}

+ 171 - 167
app.class.php

@@ -1,193 +1,197 @@
 <?php
-	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;
+	namespace Juju;
+	require_once('base.abstract.class.php');
+	require_once('App/router.class.php');
+	require_once('App/exception.class.php');
+	require_once('App/controller.abstract.class.php');
+	use \Juju\{App\Controller, App\Exception, App\Router, Http\Response, Http\Request};
 
-		class App extends Base {
-			use Events;
-			private static $apps = [];
-			private $domains;
-			private $routers;
-			private $router;
-			public $_onerror;
-			public $request = null;
-			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);
+	class App extends Base {
+		use Events;
+		private static $apps = [];
+		private $domains;
+		private $routers;
+		private $router;
+		public $_onerror;
+		public $request = null;
+		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);
 				}
 			}
-			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();
-				}
+			static::$apps[] = $this;
+			if(is_callable($fn)){
+				$fn($this);
 			}
-			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);
-					}
-				}
+		}
+		public function __destruct(){
+			$this->routers = [];
+			$index = array_search($this, static::$apps);
+			if($index !== false){
+				array_splice(static::$apps, $index, 1);
 			}
-			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(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'));
-					}
-					$app->onerror($app->request, $app->response, $error);
-					$app->response->shutdown();
+		}
+		public static function import_all(string $dirpath){
+			foreach(scandir($dirpath) as $file){
+				$path = "{$dirpath}/{$file}";
+				if(is_file($path) && pathinfo($path, PATHINFO_EXTENSION) == "php"){
+					require_once($path);
 				}
 			}
-			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://";
+		}
+		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();
 				}
-				// @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();
+			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'));
 				}
-				$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;
+				$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';
+			}
+			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]);
+		}
+		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];
 						}
-					}
-					// 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 App\Exception("Not Found", 404));
-						}
-					}, $onerror);
-					$this->fire('afterhandle', $req, $res);
 				}
-				return $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, $url){
+					if(!$handled){
+						$self->onerror($req, $res, new Exception("{$url['scheme']}://{$url['host']}{$url["path"]} Not Found", 404));
+					}
+				}, $onerror);
+				$this->fire('afterhandle', $req, $res);
 			}
-			public function error(callable $fn){
-				$this->_onerror = $fn;
-				return $this;
+			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 route(string $path, callable $fn){
-				$this->router->path($path, $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 prefix(string $prefix, callable $fn){
-				if(!$this->routers[$prefix]){
-					$this->routers[$prefix] = new App\Router($prefix);
-				}
-				$fn($this->routers[$prefix]);
-				return $this;
+			$fn($this->domains[$host]);
+			return $this;
+		}
+		public function map_domain(string $host, $handle){
+			if(!is_array($handle)){
+				$handle = [$handle];
 			}
-			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;
-			}
-			public function bind(string $hosts, callable $fn){
-				$hosts = array_map(function($item){
-					return trim($item);
-				}, explode(',', $hosts));
-				$host = array_pop($hosts);
-				return $this->map_domain($host, $hosts)->domain($host, $fn);
-			}
-			public function onerror(Request $req, Response $res, $error){
-				$this->fire('error', $error);
-				$fn = $this->_onerror;
-				if(is_callable($fn)){
-					$fn($req, $res, $error);
-				}
+			return $this;
+		}
+		public function bind(string $hosts, callable $fn = null){
+			$hosts = array_map(function($item){
+				return trim($item);
+			}, explode(',', $hosts));
+			$host = array_pop($hosts);
+			if(is_null($fn)){
+				$fn = function(Router $router){
+					Controller::handle_all($router);
+				};
+			}
+			return $this->map_domain($host, $hosts)->domain($host, $fn);
+		}
+		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);
 ?>

+ 39 - 39
base.abstract.class.php

@@ -1,45 +1,45 @@
 <?php
-	namespace Juju {
-		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";
-				}
-				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]);
+	namespace Juju;
+
+	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";
 			}
-			public function __unset($name){
-				unset($this->data[$name]);
+			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]);
+		}
 	}
 ?>

+ 23 - 24
events.trait.php

@@ -1,35 +1,34 @@
 <?php
-	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;
+	namespace Juju;
+	trait Events {
+		private $events = [];
+		public function on(string $name, callable $fn){
+			if(!isset($this->events[$name])){
+				$this->events[$name] = [];
 			}
-			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);
-						}
+			$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);
 					}
 				}
-				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;
-						}
+			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;
 					}
 				}
-				return true;
 			}
+			return true;
 		}
 	}
 ?>

+ 3 - 3
migration.abstract.class.php

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

+ 393 - 387
orm.abstract.class.php

@@ -1,429 +1,435 @@
 <?php
-	namespace Juju {
-		require_once('sql.class.php');
-		require_once('ORM/relationship.class.php');
-		require_once('Data/securestring.class.php');
-		use \Juju\ORM\Relationship as Relationship;
-		use \Juju\Data\SecureString;
+	namespace Juju;
+	require_once('sql.class.php');
+	require_once('ORM/relationship.class.php');
+	require_once('Data/securestring.class.php');
+	use \Juju\{ORM\Relationship, Data\SecureString};
 
-		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
-							);
-						}
+	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_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($idOrData)){
-					if(isset($idOrData[static::primary_key()]) && self::cached($idOrData[static::primary_key()])){
-						throw new \Exception('Instance already cached');
+				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
+						);
 					}
-					foreach($idOrData as $key => $val){
-						$this->_data[$key] = $val;
-					}
-				}else{
-					if(self::cached($idOrData)){
-						throw new \Exception('Instance already cached');
+				}
+				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
+						);
 					}
-					$this->_data[static::primary_key()] = (int)$idOrData;
 				}
-				self::$instances[] = $this;
+				self::$aliases[$this->name] = $aliases;
+				// Clear relationship definitions to save memory
+				static::$belongs_to = static::$has_one = static::$has_many = null;
 			}
-			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);
+			if(is_array($idOrData)){
+				if(isset($idOrData[static::primary_key()]) && self::cached($idOrData[static::primary_key()])){
+					throw new \Exception('Instance already cached');
 				}
-			}
-			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);
+				foreach($idOrData as $key => $val){
+					$this->_data[$key] = $val;
 				}
-			}
-			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);
+			}else{
+				if(self::cached($idOrData)){
+					throw new \Exception('Instance already cached');
 				}
+				$this->_data[static::primary_key()] = (int)$idOrData;
 			}
-			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);
-				}
-				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){
-				if(is_string($sql) || $sql instanceof SecureString){
-					$sql = SQL::FromDSN("{$sql}");
-				}
-				if($sql instanceof SQL){
-					self::$sql = $sql;
-					// @todo handle updating live instances
-				}else{
-					throw new \Exception("Invalid argument. Must pass a DSN string or an SQL instance");
-				}
-			}
-			public static function query(...$args){
-				return self::$sql->query(...$args);
+			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 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 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 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 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 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 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 static function cached(int $id){
-				return !is_null(self::cached_instance($id));
+			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){
+			if(is_string($sql)){
+				$sql = SQL::FromDSN($sql);
 			}
-			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;
+			if($sql instanceof SecureString){
+				$sql = SQL::FromDSN((string)$sql);
 			}
-			public static function each_cached(callable $fn){
-				$name = static::table_name();
-				if(self::$instances[$name]){
-					array_walk(self::$instances[$name], $fn);
-				}
+			if($sql instanceof SQL){
+				self::$sql = $sql;
+				// @todo handle updating live instances
+			}else{
+				throw new \Exception("Invalid argument. Must pass a DSN string or an SQL instance");
 			}
-			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 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 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;
+			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);
 			}
-			// Instance Api
-			public function values($values){
-				foreach($values as $key => $val){
-					$this->set($key, $val);
+		}
+		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;
 				}
-				return $this;
+				$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;
+		}
+		public static function models(){
+			array_filter(get_declared_classes(), function($class){
+				return substr($class, 0, 5) == "Model\\";
+			});
+		}
+		// Instance Api
+		public function values($values){
+			foreach($values as $key => $val){
+				$this->set($key, $val);
 			}
-			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;
+			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;
 				}
-				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();
+			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{
-						self::query(
-							"insert {$this->name} {$set}"
-						)->execute();
-						$this->_data[static::primary_key()] = self::$sql->insert_id;
+						$set .= "{$key} = null";
 					}
-					foreach($this->_related as $related){
-						$related->save();
+				}
+				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} = ? ";
 				}
-				// 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 = [];
+				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;
 				}
-				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');
+				foreach($this->_related as $related){
+					$related->save();
 				}
-				$this->_data[$key] = $val;
-				$this->_changed = array_merge($this->_changed, [$key]);
-				return $this;
 			}
-			public function unset($key){
-				unset($this->_data[$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 has($key){
-				return isset($this->_data[$key]);
+			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 dirty(string $key = null){
-				if(is_null($key)){
-					return count($this->_changed) > 0;
-				}else{
-					return in_array($key, $this->_changed);
-				}
+			$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 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);
+		}
+		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);
 				}
-				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");
 			}
 		}
 	}

+ 20 - 0
settings.class.php

@@ -0,0 +1,20 @@
+<?php
+	namespace Juju;
+	class Settings {
+		public static function init(array $settings){
+			array_walk($settings, function($val, $name){
+				self::set($name, $val);
+			});
+		}
+		public static function set(string $name, $val){
+			self::$settings[$name] = $val;
+		}
+		public static function get(string $name){
+			if(!isset(self::$settings[$name])){
+				throw new \Exception("Setting not set");
+			}
+			return self::$settings[$name];
+		}
+		private static $settings = [];
+	}
+?>

+ 100 - 102
sql.class.php

@@ -1,118 +1,116 @@
 <?php
-	namespace Juju {
-		require_once('SQL/query.class.php');
-		require_once('Data/securestring.class.php');
-		use \Juju\SQL\Query;
-		use \Juju\Data\SecureString;
+	namespace Juju;
+	require_once('SQL/query.class.php');
+	require_once('Data/securestring.class.php');
+	use \Juju\{SQL\Query, Data\SecureString};
 
+	/**
+	* SQL class. Used for handling SQL connections
+	*
+	* @module sql
+	* @class SQL
+	* @constructor
+	*/
+	class SQL {
 		/**
-		* SQL class. Used for handling SQL connections
-		*
-		* @module sql
-		* @class SQL
-		* @constructor
+		* This is the mysqli connection beneath everything
+		* 
+		* @property sql
+		* @type {mysqli}
+		* @private
+		* @required
 		*/
-		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 static function FromDSN(string $dsnstring){
-				$dsnstring = explode(':', $dsnstring)[1];
-				$dsn = explode(';', $dsnstring);
-				$dsn = array_reduce($dsn, function($dsn, $item){
-					$item = explode('=', $item);
-					$dsn[$item[0]] = $item[1];
-					return $dsn;
-				});
-				if(!isset($dsn['host'])){
-					throw new \Exception("DSN {$dsnstring} missing host");
-				}
-				if(!isset($dsn['dbname'])){
-					throw new \Exception("DSN {$dsnstring} missing dbname");
-				}
-				if(!isset($dsn['user'])){
-					$dsn['user'] = $dsn['dbname'];
-				}
-				if(!isset($dsn['pass'])){
-					$dsn['pass'] = $dsn['user'];
-				}
-				$dsn['pass'] = SecureString::from($dsn['pass']);
-				return new SQL($dsn['host'], $dsn['user'], $dsn['pass'], $dsn['dbname']);
-			}
-			public function __construct($server,$user,$pass,$db){
-				$this->guid = uniqid();
-				$this->sql = new \mysqli('p:'.$server,$user,"{$pass}",$db);;
-				if($this->sql->connect_error){
-					throw new \Exception('Mysqli Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
-				}
-				self::$connections[] = $sql;
+		private $guid;
+		private $sql;
+		public $queries = [];
+		private static $connections = [];
+		public static function FromDSN(string $dsnstring){
+			$dsnstring = explode(':', $dsnstring)[1];
+			$dsn = explode(';', $dsnstring);
+			$dsn = array_reduce($dsn, function($dsn, $item){
+				$item = explode('=', $item);
+				$dsn[$item[0]] = $item[1];
+				return $dsn;
+			});
+			if(!isset($dsn['host'])){
+				throw new \Exception("DSN '{$dsnstring}' missing host");
 			}
-			public function __destruct(){
-				$this->sql->rollback();
-				$this->sql->close();
-				foreach($this->queries as $query){
-					unset($query);
-				}
-				self::$connections = array_diff(self::$connections, [$this]);
+			if(!isset($dsn['dbname'])){
+				throw new \Exception("DSN '{$dsnstring}' missing dbname");
 			}
-			public function __invoke(){
-				return $this->sql;
+			if(!isset($dsn['user'])){
+				$dsn['user'] = $dsn['dbname'];
 			}
-			public function __get($name){
-				switch($name){
-					case 'error':
-						return $this->sql->error;
-					break;
-					case 'insert_id':
-						return $this->sql->insert_id;
-					break;
-				}
+			if(!isset($dsn['pass'])){
+				$dsn['pass'] = $dsn['user'];
 			}
-			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));
+			$dsn['pass'] = SecureString::from($dsn['pass']);
+			return new SQL($dsn['host'], $dsn['user'], $dsn['pass'], $dsn['dbname']);
+		}
+		public function __construct($server,$user,$pass,$db){
+			$this->guid = uniqid();
+			$this->sql = new \mysqli('p:'.$server,$user,"{$pass}",$db);;
+			if($this->sql->connect_error){
+				throw new \Exception('Mysqli Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
 			}
-			public function escape($s){
-				return $this->sql->escape_string($s);
+			self::$connections[] = $sql;
+		}
+		public function __destruct(){
+			$this->sql->rollback();
+			$this->sql->close();
+			foreach($this->queries as $query){
+				unset($query);
 			}
-			public function charset($charset){
-				return $this->sql->set_charset($charset);
+			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 static function make_referenced(&$arr){
-				$refs = [];
-				foreach($arr as $key => $value){
-					$refs[$key] = &$arr[$key];
-				}
-				return $refs;
+		}
+		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 static function shutdown(){
-				foreach(self::$connections as $sql){
-					unset($sql);
-				}
+			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();
+	});
 ?>