name = $name; $this->router = new Router(); $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() ]); if($res){ $res->code(404); $res->header('Content-Type', 'application/json'); $res->end($o); }else{ http_response_code(404); header('Content-Type: application/json'); print($o); } }; static::$apps[] = $this; if(is_callable($fn)){ $fn($this); } } public function __destruct(){ $this->routers = []; $index = array_search(static::$apps, $this); if($index !== false){ array_splice(static::$apps, $index, 1); } if(is_callable('parent::__destruct')){ parent::__destruct(); } } public static function shutdown(){ $verb = $_SERVER['REQUEST_METHOD']; 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 $url = parse_url($root.$_SERVER['HTTP_HOST'].$_SERVER[$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(); } } public static function shutdown_error($error){ foreach(static::$apps as $k => $app){ if(is_callable($app->onerror)){ $app->onerror(null, null, $error); } } } public function handle($verb, $url, $data, $headers = null){ if(is_null($headers)){ $headers = getallheaders(); } $res = new Response(); $req = new Request($url, $headers, $data); $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){ $router->handle($url["path"], $req, $res, null, $onerror); $handled = $handled || $router->handled; } } // Prefixed path routers foreach($this->routers as $prefix => $router){ $router->handle($url["path"], $req, $res, null, $onerror); $handled = $handled || $router->handled; } // Base router for non-prefixed paths $this->router->handle($url["path"], $req, $res, function($req, $res) use($handled, $self){ if(!$handled){ $self->onerror($req, $res,new Error("Not Found", 404)); } }, $onerror); return $res; } public function error(Callable $fn){ $this->_onerror = $fn; return $this; } public function route($path, Callable $fn){ $this->router->path($path, $fn); return $this; } public function prefix($prefix, Callable $fn){ if(!$this->routers[$prefix]){ $this->routers[$prefix] = new Router($prefix); } $fn($this->routers[$prefix]); return $this; } public function domain($host, Callable $fn){ if(!isset($this->domains[$host])){ $this->domains[$host] = new Router(); } $fn($this->domains[$host]); return $this; } public function onerror($req, $res, $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([ 'number'=> $errno, 'msg'=> $errstr, 'file'=> $errfile, 'line'=> $errline, 'backtrace'=> debug_backtrace(), 'included'=> get_included_files() ]); },E_ALL); register_shutdown_function(function(){ $error = error_get_last(); if($error['type'] == 1){ App::shutdown_error([ 'number'=> $error['type'], 'msg'=> $error['message'], 'file'=> $error['file'], 'line'=> $error['line'], 'backtrace'=> [], 'included'=> get_included_files() ]); } }); ?>