name = $name; $this->textdomain = $name; $this->textdomain_folder = __DIR__.'/locale/'; $this->textdomain_codeset = 'UFT-8'; $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 function __get($name){ switch($name){ case 'routes': $routes = $this->router->routes; foreach($this->routers as $prefix => $router){ foreach($router->routes as $route => $count){ if(!isset($routes[$prefix.$route])){ $routes[$prefix.$route] = 0; } $routes[$prefix.$route] += $count; } } foreach($this->domains as $domain => $router){ foreach($router->routes as $route => $count){ if(!isset($routes[$domain.$route])){ $routes[$domain.$route] = 0; } $routes[$domain.$route] += $count; } } return $routes; break; default: return parent::__get($name); } } 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 = Request::get_verb(); $url = Request::get_url(); $data = Request::get_body(); if(ob_get_level() > 0){ ob_clean(); } foreach(static::$apps as $k => $app){ if($app instanceof App){ $app->handle($verb, $url, $data)->shutdown(); } } while(ob_get_level() > 0){ ob_end_flush(); } if(is_callable('parent::__destruct')){ parent::__destruct(); } } public static function shutdown_error($error){ while(ob_get_level() > 0){ ob_end_clean(); } if(count(static::$apps)){ foreach(static::$apps as $k => $app){ if(is_null($app->request)){ $app->request = new Request(Request::get_verb(), Request::get_url(), Request::get_headers(), Request::get_body()); } $app->response->open(true); $app->onerror($app->request, $app->response, $error); $app->response->shutdown(); } flush(); }else{ $res =new Response(); $res->code(500) ->header('Content-Type', 'text/plain') ->write(print_r($error, true)) ->shutdown(); } flush(); if(is_callable('parent::__destruct')){ parent::__destruct(); } } public function handle(string $verb, array $url, string $data, array $headers = null) : Response{ if(is_null($headers)){ $headers = Request::get_headers(); } $res = $this->response; $this->request = $req = new Request($verb, $url, $headers, $data); Response::locale($req->locale, $this->textdomain, $this->textdomain_folder, $this->textdomain_codeset); 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){ if(strpos($url["path"], $prefix, 0 ) == 0){ $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 base(string $base = null){ if(is_null($base)){ return $this->router->base; }else{ foreach($this->domains as &$router){ if($router instanceof Router){ $router->base($base); } } foreach($this->routers as $prefix => &$router){ $router->base("{$base}/{$prefix}"); } $this->router->base($base); 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("{$this->router->base}/{$prefix}"); } $fn($this->routers[$prefix]); return $this; } public function domain(string $host, callable $fn){ if(!isset($this->domains[$host])){ $this->domains[$host] = new Router($this->router->base); } $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){ try{ $this->fire('error', $error); $fn = $this->_onerror; if(is_callable($fn)){ $fn($req, $res, $error); } }catch(\Exception $e){ die("Error handlers failed {$e}"); } } public function locale(string $domain, string $folder, string $codeset = 'UTF-8'){ $this->textdomain = $domain; $this->textdomain_codeset = $codeset; $this->textdomain_folder = $folder; return $this; } } if(!defined('JUJU_DISABLE_APP')){ set_exception_handler(function($error){ App::shutdown_error($error); }); set_error_handler(function($errno, $errstr, $errfile, $errline){ // ignore warnings if($errno &~ E_WARNING){ App::shutdown_error(new Exception($errstr, $errno, null, $errfile, $errline, debug_backtrace())); die(); } }, E_ALL); register_shutdown_function(function(){ error_reporting(E_ALL &~ E_WARNING); ini_set('display_errors', 'Off'); App::shutdown(); }); error_reporting(E_ALL &~ E_WARNING); ini_set('display_errors', 'Off'); gc_enable(); } ?>