app.class.php 4.6 KB

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