Explorar o código

* Fix a problem with dypaths were /path/ and /path are not treated as the same route
* Make Arguments countable
* Add ORM::create($data)
* Simplify Request->__get($name)
* Add Request->xml() and Request->msgpack()
* Add get_url, get_headers, get_body, and get_verb to Requests as statics and update everything to use them
* Add options to the router to handle specific verbs
* Allow routes to match but return a false on the match so that the default handled is false

Nathaniel van Diepen %!s(int64=7) %!d(string=hai) anos
pai
achega
53370b2769
Modificáronse 7 ficheiros con 144 adicións e 42 borrados
  1. 4 1
      App/arguments.class.php
  2. 1 1
      App/dypath.class.php
  3. 6 2
      App/path.class.php
  4. 84 6
      App/router.class.php
  5. 40 12
      Http/request.class.php
  6. 6 20
      app.class.php
  7. 3 0
      orm.abstract.class.php

+ 4 - 1
App/arguments.class.php

@@ -1,7 +1,7 @@
 <?php
 	namespace Juju\App;
 
-	class Arguments implements \JsonSerializable, \ArrayAccess{
+	class Arguments implements \JsonSerializable, \ArrayAccess, \Countable{
 		public $args;
 		public function __construct($args){
 			$this->args = $args;
@@ -29,5 +29,8 @@
 		public function offsetUnset($key){
 			unset($this->args[$key]);
 		}
+		public function count(){
+			return count($this->args);
+		}
 	}
 ?>

+ 1 - 1
App/dypath.class.php

@@ -24,7 +24,7 @@
 					return $m;
 				break;
 				case 'regex':
-					return '/^'.preg_replace('/\\\{[^#\/][^}\n]+?\\\}/','([^\/]*)',preg_quote($this->path,'/')).'$/';
+					return '/^'.preg_replace('/\\\{[^#\/][^}\n]+?\\\}/','([^\/]+)', preg_quote($this->path,'/')).'\/?$/';
 				break;
 			}
 		}

+ 6 - 2
App/path.class.php

@@ -15,13 +15,17 @@
 		}
 		public function __invoke(Request $req, Response $res, Arguments $args){
 			$err = null;
+			$ret = true;
 			foreach($this->handles as $k => $fn){
 				try{
-					$fn($req, $res, $args, $err);
+					if($fn($req, $res, $args, $err) === false){
+						$ret = false;
+					}
 				}catch(\Exception $e){
 					$err = $e;
 				}
 			}
+			return $ret;
 		}
 		public function __clone(){
 			// No cloning for now
@@ -38,7 +42,7 @@
 			return "[Path {$this->path}]";
 		}
 		public function handle(callable $fn){
-			array_push($this->handles,$fn);
+			array_push($this->handles, $fn);
 			return $this;
 		}
 		public function matches(string $path){

+ 84 - 6
App/router.class.php

@@ -63,7 +63,6 @@
 				$fn($router);
 			}
 		}
-		// fn = function(response,args){}
 		public function path(string $path, callable $fn){
 			$obj = false;
 			foreach($this->_paths as $k => $p){
@@ -73,14 +72,91 @@
 			}
 			if(!$obj){
 				$obj = new Path($path);
-				array_push($this->_paths,$obj);
+				array_push($this->_paths, $obj);
 			}
-			return $obj->handle($fn);
+			$obj->handle($fn);
+			return $this;
+		}
+		public function get(string $path, callable $fn){
+			return $this->path($path, function($req, $res, $args) use($fn){
+				if($req->verb === 'GET'){
+					return $fn($req, $res, $args);
+				}else{
+					return false;
+				}
+			});
+		}
+		public function post(string $path, callable $fn){
+			return $this->path($path, function($req, $res, $args) use($fn){
+				if($req->verb === 'POST'){
+					return $fn($req, $res, $args);
+				}else{
+					return false;
+				}
+			});
+		}
+		public function put(string $path, callable $fn){
+			return $this->path($path, function($req, $res, $args) use($fn){
+				if($req->verb === 'PUT'){
+					return $fn($req, $res, $args);
+				}else{
+					return false;
+				}
+			});
+		}
+		public function delete(string $path, callable $fn){
+			return $this->path($path, function($req, $res, $args) use($fn){
+				if($req->verb === 'DELETE'){
+					return $fn($req, $res, $args);
+				}else{
+					return false;
+				}
+			});
+		}
+		public function patch(string $path, callable $fn){
+			return $this->path($path, function($req, $res, $args) use($fn){
+				if($req->verb === 'PATCH'){
+					return $fn($req, $res, $args);
+				}else{
+					return false;
+				}
+			});
 		}
 		public function paths(array $paths){
 			foreach($paths as $path => $fn){
-				$this->path($path,$fn);
+				$this->path($path, $fn);
+			}
+			return $this;
+		}
+		public function gets(array $paths){
+			foreach($paths as $path => $fn){
+				$this->get($path, $fn);
 			}
+			return $this;
+		}
+		public function posts(array $paths){
+			foreach($paths as $path => $fn){
+				$this->post($path, $fn);
+			}
+			return $this;
+		}
+		public function puts(array $paths){
+			foreach($paths as $path => $fn){
+				$this->put($path, $fn);
+			}
+			return $this;
+		}
+		public function deletes(array $paths){
+			foreach($paths as $path => $fn){
+				$this->delete($path, $fn);
+			}
+			return $this;
+		}
+		public function patches(array $paths){
+			foreach($paths as $path => $fn){
+				$this->patch($path, $fn);
+			}
+			return $this;
 		}
 		public function clear(){
 			$this->_paths = [];
@@ -92,7 +168,7 @@
 					$path = '/'.$path;
 				}
 				if(is_null($req)){
-					$req = new Request();
+					$req = new Request(Request::get_verb(), Request::get_url(), Request::get_headers(), Request::get_body());
 				}
 				if(is_null($res)){
 					$res = new Response();
@@ -111,7 +187,9 @@
 					if($p->matches($path)){
 						$handled = true;
 						try{
-							$p($req, $res, $p->args($path));
+							if($p($req, $res, $p->args($path)) === false){
+								$handled = false;
+							}
 						}catch(\Exception $e){
 							if(!is_null($onerror)){
 								$onerror($req, $res,$e);

+ 40 - 12
Http/request.class.php

@@ -3,10 +3,11 @@
 	require_once('uri.class.php');
 
 	class Request {
+		private $verb;
 		private $url;
 		private $headers;
 		private $body;
-		public function __construct($url, array $headers = [], string $body = ''){
+		public function __construct($verb, $url, array $headers = [], string $body = ''){
 			if(is_string($url) || is_array($url)){
 				$this->url = new Uri($url);
 			}elseif($url instanceof Uri){
@@ -19,29 +20,56 @@
 			}else{
 				$this->headers = [];
 			}
+			$this->verb = $verb;
 			$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;
+				case 'headers':case 'url':case 'body':case 'verb':
+					return $this->$name;
 				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;
 		}
+		public function json(bool $pretty = true){
+			return json_decode($this->body, $pretty);
+		}
+		public function xml(string $classname = 'SimpleXMLElement', int $options, string $ns = '', bool $is_prefix = false){
+			return simplexml_load_string($this->body, $classname, $options, $ns, $is_prefix);
+		}
+		public function msgpack(){
+			if(!extension_loaded("msgpack")){
+				throw new \Exception("Unable to load msgpack extension");
+			}
+			return msgpack_unpack($this->body);
+		}
+		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 static function get_headers(){
+			return getallheaders();
+		}
+		public static function get_body(){
+			return file_get_contents( 'php://input','r');
+		}
+		public static function get_verb(){
+			return $_SERVER['REQUEST_METHOD'];
+		}
 	}
 ?>

+ 6 - 20
app.class.php

@@ -63,9 +63,9 @@
 			}
 		}
 		public static function shutdown(){
-			$verb = $_SERVER['REQUEST_METHOD'];
-			$url = App::get_url();
-			$data = file_get_contents( 'php://input','r');
+			$verb = Request::get_verb();
+			$url = Request::get_url();
+			$data = Request::get_body();
 			foreach(static::$apps as $k => $app){
 				if($app instanceof App){
 					$app->handle($verb, $url, $data)->shutdown();
@@ -78,32 +78,18 @@
 		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->request = new Request(Request::get_verb(), Request::get_url(), Request::get_headers(), Request::get_body());
 				}
 				$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();
+				$headers = Request::get_headers();
 			}
 			$res = $this->response;
-			$this->request = $req = new Request($url, $headers, $data);
+			$this->request = $req = new Request($verb, $url, $headers, $data);
 			if($this->fire('handle', $req, $res)){
 				$self = $this;
 				$onerror = function($res, $error) use($self){

+ 3 - 0
orm.abstract.class.php

@@ -211,6 +211,9 @@
 			}
 			return null;
 		}
+		public static function create(array $data){
+			return new static($data);
+		}
 		public static function exists(int $id){
 			$query = self::query(
 				"select count(1) as count ".