123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- <?php
- namespace Juju;
- use \Juju\{App\Controller, App\Exception, App\Router, Http\Response, Http\Request};
- 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');
- class App extends Base {
- use Events;
- private static $apps = [];
- private $domains;
- private $routers;
- private $router;
- public $_onerror;
- public $request = null;
- public $response;
- public $name;
- public $textdomain;
- public $textdomain_folder;
- public $textdomain_codeset;
- public function __construct(string $name, callable $fn = null, array $events = null){
- $this->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();
- }
- ?>
|