125 lines
3.8 KiB
PHP
125 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace App\Core;
|
|
|
|
use Throwable;
|
|
use App\Middleware\MiddlewareInterface;
|
|
|
|
class App
|
|
{
|
|
public static App $app;
|
|
public Container $container;
|
|
public Request $request;
|
|
public Response $response;
|
|
public Session $session;
|
|
public Router $router;
|
|
|
|
public function __construct(Container $container)
|
|
{
|
|
self::$app = $this;
|
|
$this->container = $container;
|
|
$this->request = new Request();
|
|
$this->response = new Response();
|
|
$this->session = new Session();
|
|
$this->router = new Router();
|
|
}
|
|
|
|
/**
|
|
* Boot and run the application.
|
|
*/
|
|
public function run(): void
|
|
{
|
|
try {
|
|
// Match request to route
|
|
$routeInfo = $this->router->resolve($this->request);
|
|
$callback = $routeInfo['callback'];
|
|
$middlewares = $routeInfo['middleware'];
|
|
$params = $routeInfo['params'];
|
|
|
|
// Inject matched route parameters into Request
|
|
$this->request->setRouteParams($params);
|
|
|
|
// Run Middleware Chain
|
|
$this->executeMiddlewareChain($middlewares, function() use ($callback) {
|
|
// Execute Route action
|
|
if (is_callable($callback)) {
|
|
$response = $callback($this->request, $this->response);
|
|
} else {
|
|
[$controllerClass, $method] = $callback;
|
|
$controller = $this->container->get($controllerClass);
|
|
$response = $controller->$method($this->request, $this->response);
|
|
}
|
|
|
|
// Auto-output string responses as HTML
|
|
if (is_string($response)) {
|
|
$this->response->html($response);
|
|
}
|
|
});
|
|
|
|
} catch (Throwable $e) {
|
|
$this->handleException($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executes the array of middlewares sequentially before firing destination action.
|
|
*/
|
|
private function executeMiddlewareChain(array $middlewares, callable $destination): void
|
|
{
|
|
$runner = function (int $index) use ($middlewares, $destination, &$runner) {
|
|
if ($index >= count($middlewares)) {
|
|
$destination();
|
|
return;
|
|
}
|
|
|
|
$middlewareClass = $middlewares[$index];
|
|
/** @var MiddlewareInterface $middleware */
|
|
$middleware = $this->container->get($middlewareClass);
|
|
|
|
$middleware->handle($this->request, $this->response, function() use ($runner, $index) {
|
|
$runner($index + 1);
|
|
});
|
|
};
|
|
|
|
$runner(0);
|
|
}
|
|
|
|
/**
|
|
* Global exception handler.
|
|
*/
|
|
private function handleException(Throwable $e): void
|
|
{
|
|
$code = (int) $e->getCode();
|
|
if ($code < 100 || $code > 599) {
|
|
$code = 500;
|
|
}
|
|
|
|
// Return API error JSON for API routes or JSON requests
|
|
if ($this->request->isJson() || str_starts_with($this->request->getPath(), '/api')) {
|
|
$this->response->json([
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
'code' => $code
|
|
], $code);
|
|
return;
|
|
}
|
|
|
|
// Render error templates for Web requests
|
|
$viewPath = __DIR__ . "/../../resources/views/errors/{$code}.php";
|
|
if (!file_exists($viewPath)) {
|
|
$viewPath = __DIR__ . '/../../resources/views/errors/500.php';
|
|
}
|
|
|
|
if (file_exists($viewPath)) {
|
|
ob_start();
|
|
$message = $e->getMessage();
|
|
$exception = $e;
|
|
include $viewPath;
|
|
$content = ob_get_clean();
|
|
$this->response->html($content, $code);
|
|
} else {
|
|
$this->response->html("<h1>Error {$code}</h1><p>{$e->getMessage()}</p>", $code);
|
|
}
|
|
}
|
|
}
|