app.class.php 5.3 KB

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