app.class.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <?php
  2. namespace Juju;
  3. require_once('base.abstract.class.php');
  4. require_once('App/router.class.php');
  5. require_once('App/exception.class.php');
  6. require_once('App/controller.abstract.class.php');
  7. use \Juju\{App\Controller, App\Exception, App\Router, Http\Response, Http\Request};
  8. class App extends Base {
  9. use Events;
  10. private static $apps = [];
  11. private $domains;
  12. private $routers;
  13. private $router;
  14. public $_onerror;
  15. public $request = null;
  16. public $response;
  17. public function __construct(string $name, callable $fn = null, array $events = null){
  18. $this->name = $name;
  19. $this->router = new Router();
  20. $this->response = new Response();
  21. $this->routers = [];
  22. $this->domains = [];
  23. $this->_onerror = function($req, $res, $error){
  24. $o = json_encode([
  25. 'message'=>$error->getMessage(),
  26. 'code'=>$error->getCode(),
  27. 'file'=>$error->getFile(),
  28. 'line'=>$error->getLine(),
  29. 'traceback'=>$error->getTrace()
  30. ]);
  31. $res->code(404);
  32. $res->header('Content-Type', 'application/json');
  33. $res->end($o);
  34. $res->shutdown();
  35. };
  36. if(!is_null($events)){
  37. foreach($events as $name => $handler){
  38. $this->on($name, $handler);
  39. }
  40. }
  41. static::$apps[] = $this;
  42. if(is_callable($fn)){
  43. $fn($this);
  44. }
  45. }
  46. public function __destruct(){
  47. $this->routers = [];
  48. $index = array_search($this, static::$apps);
  49. if($index !== false){
  50. array_splice(static::$apps, $index, 1);
  51. }
  52. if(is_callable('parent::__destruct')){
  53. parent::__destruct();
  54. }
  55. }
  56. public static function import_all(string $dirpath){
  57. foreach(scandir($dirpath) as $file){
  58. $path = "{$dirpath}/{$file}";
  59. if(is_file($path) && pathinfo($path, PATHINFO_EXTENSION) == "php"){
  60. require_once($path);
  61. }
  62. }
  63. }
  64. public static function shutdown(){
  65. $verb = Request::get_verb();
  66. $url = Request::get_url();
  67. $data = Request::get_body();
  68. foreach(static::$apps as $k => $app){
  69. if($app instanceof App){
  70. $app->handle($verb, $url, $data)->shutdown();
  71. }
  72. }
  73. if(is_callable('parent::__destruct')){
  74. parent::__destruct();
  75. }
  76. }
  77. public static function shutdown_error($error){
  78. foreach(static::$apps as $k => $app){
  79. if(is_null($app->request)){
  80. $app->request = new Request(Request::get_verb(), Request::get_url(), Request::get_headers(), Request::get_body());
  81. }
  82. $app->onerror($app->request, $app->response, $error);
  83. $app->response->shutdown();
  84. }
  85. }
  86. public function handle(string $verb, array $url, string $data, array $headers = null){
  87. if(is_null($headers)){
  88. $headers = Request::get_headers();
  89. }
  90. $res = $this->response;
  91. $this->request = $req = new Request($verb, $url, $headers, $data);
  92. if($this->fire('handle', $req, $res)){
  93. $self = $this;
  94. $onerror = function($res, $error) use($self){
  95. $self->onerror($req, $res, $error);
  96. };
  97. $handled = false;
  98. // Domain routers
  99. foreach($this->domains as $host => $router){
  100. if($url['host'] == $host){
  101. while(is_string($router)){
  102. $router = $this->domains[$router];
  103. }
  104. $router->handle($url["path"], $req, $res, null, $onerror);
  105. $handled = $handled || $router->handled;
  106. }
  107. }
  108. // Prefixed path routers
  109. foreach($this->routers as $prefix => $router){
  110. if(strpos($url["path"], $prefix, 0 ) == 0){
  111. $router->handle($url["path"], $req, $res, null, $onerror);
  112. $handled = $handled || $router->handled;
  113. }
  114. }
  115. // Base router for non-prefixed paths
  116. $this->router->handle($url["path"], $req, $res, function($req, $res) use($handled, $self, $url){
  117. if(!$handled){
  118. $self->onerror($req, $res, new Exception("{$url['scheme']}://{$url['host']}{$url["path"]} Not Found", 404));
  119. }
  120. }, $onerror);
  121. $this->fire('afterhandle', $req, $res);
  122. }
  123. return $res;
  124. }
  125. public function error(callable $fn){
  126. $this->_onerror = $fn;
  127. return $this;
  128. }
  129. public function base(string $base = null){
  130. if(is_null($base)){
  131. return $this->router->base;
  132. }else{
  133. foreach($this->domains as &$router){
  134. if($router instanceof Router){
  135. $router->base($base);
  136. }
  137. }
  138. foreach($this->routers as $prefix => &$router){
  139. $router->base("{$base}/{$prefix}");
  140. }
  141. $this->router->base($base);
  142. return $this;
  143. }
  144. }
  145. public function route(string $path, callable $fn){
  146. $this->router->path($path, $fn);
  147. return $this;
  148. }
  149. public function prefix(string $prefix, callable $fn){
  150. if(!$this->routers[$prefix]){
  151. $this->routers[$prefix] = new Router("{$this->router->base}/{$prefix}");
  152. }
  153. $fn($this->routers[$prefix]);
  154. return $this;
  155. }
  156. public function domain(string $host, callable $fn){
  157. if(!isset($this->domains[$host])){
  158. $this->domains[$host] = new Router($this->router->base);
  159. }
  160. $fn($this->domains[$host]);
  161. return $this;
  162. }
  163. public function map_domain(string $host, $handle){
  164. if(!is_array($handle)){
  165. $handle = [$handle];
  166. }
  167. foreach($handle as $host2){
  168. $this->domains[$host2] = $host;
  169. }
  170. return $this;
  171. }
  172. public function bind(string $hosts, callable $fn = null){
  173. $hosts = array_map(function($item){
  174. return trim($item);
  175. }, explode(',', $hosts));
  176. $host = array_pop($hosts);
  177. if(is_null($fn)){
  178. $fn = function(Router $router){
  179. Controller::handle_all($router);
  180. };
  181. }
  182. return $this->map_domain($host, $hosts)->domain($host, $fn);
  183. }
  184. public function onerror(Request $req, Response $res, $error){
  185. $this->fire('error', $error);
  186. $fn = $this->_onerror;
  187. if(is_callable($fn)){
  188. $fn($req, $res, $error);
  189. }
  190. }
  191. }
  192. set_error_handler(function($errno, $errstr, $errfile, $errline){
  193. App::shutdown_error(new App\Exception($errstr, $errno, null, $errfile, $errline, debug_backtrace()));
  194. }, E_ALL);
  195. register_shutdown_function(function(){
  196. error_reporting(E_ALL);
  197. ini_set('display_errors', 'On');
  198. try{
  199. App::shutdown();
  200. }catch(Exception $error){
  201. App::shutdown_error(new App\Exception($error->getMessage(), $error->getCode(), $error, $error->getFile(), $error->getLine(), $error->getTrace()));
  202. }
  203. });
  204. gc_enable();
  205. ?>