app.class.php 5.3 KB

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