name = $name; $this->router = new Router(); $this->response = new Response(); $this->routers = []; $this->domains = []; $this->_onerror = function($req, $res, $error){ $o = json_encode([ 'message'=>$error->getMessage(), 'code'=>$error->getCode(), 'file'=>$error->getFile(), 'line'=>$error->getLine(), 'traceback'=>$error->getTrace() ]); $res->code(404); $res->header('Content-Type', 'application/json'); $res->end($o); $res->shutdown(); }; if(!is_null($events)){ foreach($events as $name => $handler){ $this->on($name, $handler); } } static::$apps[] = $this; if(is_callable($fn)){ $fn($this); } } public function __destruct(){ $this->routers = []; $index = array_search($this, static::$apps); if($index !== false){ array_splice(static::$apps, $index, 1); } if(is_callable('parent::__destruct')){ parent::__destruct(); } } public static function import_all(string $dirpath){ foreach(scandir($dirpath) as $file){ $path = "{$dirpath}/{$file}"; if(is_file($path) && pathinfo($path, PATHINFO_EXTENSION) == "php"){ require_once($path); } } } 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(); } } 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 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]; } $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, $url){ if(!$handled){ $self->onerror($req, $res, new Exception("{$url['scheme']}://{$url['host']}{$url["path"]} Not Found", 404)); } }, $onerror); $this->fire('afterhandle', $req, $res); } return $res; } public function error(callable $fn){ $this->_onerror = $fn; return $this; } public function route(string $path, callable $fn){ $this->router->path($path, $fn); return $this; } public function prefix(string $prefix, callable $fn){ if(!$this->routers[$prefix]){ $this->routers[$prefix] = new Router($prefix); } $fn($this->routers[$prefix]); return $this; } public function domain(string $host, callable $fn){ if(!isset($this->domains[$host])){ $this->domains[$host] = new Router(); } $fn($this->domains[$host]); return $this; } 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 = 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); ?>