211 lines
7.2 KiB
PHP
211 lines
7.2 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Core;
|
||
|
|
|
||
|
|
use App\Core\Router;
|
||
|
|
use App\Core\Container;
|
||
|
|
use App\Core\Middleware;
|
||
|
|
use App\Core\Security;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* NovaCore Framework Bootstrap
|
||
|
|
* Main application kernel
|
||
|
|
*/
|
||
|
|
class Bootstrap
|
||
|
|
{
|
||
|
|
private Container $container;
|
||
|
|
private Router $router;
|
||
|
|
private Middleware $middleware;
|
||
|
|
private Security $security;
|
||
|
|
|
||
|
|
public function __construct()
|
||
|
|
{
|
||
|
|
$this->container = new Container();
|
||
|
|
$this->router = new Router();
|
||
|
|
$this->middleware = new Middleware();
|
||
|
|
$this->security = new Security();
|
||
|
|
|
||
|
|
// Set the global container so helpers can use it
|
||
|
|
app_set_container($this->container);
|
||
|
|
|
||
|
|
$this->registerServices();
|
||
|
|
$this->loadRoutes();
|
||
|
|
$this->setupMiddleware();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Run the application
|
||
|
|
*/
|
||
|
|
public function run(): void
|
||
|
|
{
|
||
|
|
// Start session
|
||
|
|
if (session_status() === PHP_SESSION_NONE) {
|
||
|
|
session_start();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize security
|
||
|
|
$this->security->initialize();
|
||
|
|
|
||
|
|
// Get request method and URI
|
||
|
|
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||
|
|
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||
|
|
|
||
|
|
// Run middleware pipeline
|
||
|
|
$this->middleware->run($method, $uri);
|
||
|
|
|
||
|
|
// Route the request
|
||
|
|
$route = $this->router->match($method, $uri);
|
||
|
|
|
||
|
|
if (!$route) {
|
||
|
|
$this->handleNotFound();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Execute controller
|
||
|
|
$this->executeController($route);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Register services in container
|
||
|
|
*/
|
||
|
|
private function registerServices(): void
|
||
|
|
{
|
||
|
|
$this->container->singleton('request', function () {
|
||
|
|
return new Request();
|
||
|
|
});
|
||
|
|
|
||
|
|
$this->container->singleton('response', function () {
|
||
|
|
return new Response();
|
||
|
|
});
|
||
|
|
|
||
|
|
$this->container->singleton('view', function () {
|
||
|
|
return new View();
|
||
|
|
});
|
||
|
|
|
||
|
|
$this->container->singleton('security', function () {
|
||
|
|
return $this->security;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Load routes from all modules
|
||
|
|
*/
|
||
|
|
private function loadRoutes(): void
|
||
|
|
{
|
||
|
|
$modulesPath = __DIR__ . '/../Modules';
|
||
|
|
|
||
|
|
if (is_dir($modulesPath)) {
|
||
|
|
$modules = scandir($modulesPath);
|
||
|
|
foreach ($modules as $module) {
|
||
|
|
if ($module === '.' || $module === '..') continue;
|
||
|
|
|
||
|
|
$routesFile = $modulesPath . '/' . $module . '/routes.php';
|
||
|
|
if (file_exists($routesFile)) {
|
||
|
|
// Pass router instance to routes file
|
||
|
|
$router = $this->router;
|
||
|
|
require $routesFile;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setup default middleware stack
|
||
|
|
*/
|
||
|
|
private function setupMiddleware(): void
|
||
|
|
{
|
||
|
|
$this->middleware->add(new \App\Core\Middleware\SecurityMiddleware());
|
||
|
|
$this->middleware->add(new \App\Core\Middleware\CsrfMiddleware());
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute controller method
|
||
|
|
*/
|
||
|
|
private function executeController(array $route): void
|
||
|
|
{
|
||
|
|
[$controllerClass, $method] = explode('@', $route['handler']);
|
||
|
|
|
||
|
|
// Normalize controller class to fully-qualified name
|
||
|
|
if (!str_contains($controllerClass, '\\')) {
|
||
|
|
// No backslash provided → assume default Controller in module
|
||
|
|
$controllerClass = "App\\Modules\\{$route['module']}\\Controller";
|
||
|
|
} else {
|
||
|
|
// Has backslash but may be relative like "Home\\Controller"
|
||
|
|
if (strpos($controllerClass, 'App\\') !== 0) {
|
||
|
|
$segments = explode('\\', $controllerClass);
|
||
|
|
$moduleName = $segments[0] ?? $route['module'];
|
||
|
|
$className = end($segments);
|
||
|
|
$controllerClass = "App\\Modules\\{$moduleName}\\{$className}";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!class_exists($controllerClass)) {
|
||
|
|
$this->handleNotFound();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$controller = new $controllerClass();
|
||
|
|
|
||
|
|
if (!method_exists($controller, $method)) {
|
||
|
|
$this->handleNotFound();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Inject dependencies
|
||
|
|
$this->container->inject($controller);
|
||
|
|
|
||
|
|
// Execute method
|
||
|
|
$result = $controller->$method();
|
||
|
|
|
||
|
|
// Handle response
|
||
|
|
if ($result instanceof Response) {
|
||
|
|
$result->send();
|
||
|
|
} elseif (is_array($result) || is_object($result)) {
|
||
|
|
$response = $this->container->get('response');
|
||
|
|
$response->json($result)->send();
|
||
|
|
} else {
|
||
|
|
echo $result;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Handle 404 Not Found
|
||
|
|
*/
|
||
|
|
private function handleNotFound(): void
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$errorController = new \App\Modules\Error\Controller();
|
||
|
|
$errorController->notFound();
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
// Fallback to basic 404
|
||
|
|
http_response_code(404);
|
||
|
|
echo "<!DOCTYPE html>\n";
|
||
|
|
echo "<html>\n<head>\n";
|
||
|
|
echo "<title>404 - Page Not Found</title>\n";
|
||
|
|
echo "<script src=\"https://cdn.tailwindcss.com\"></script>\n";
|
||
|
|
echo "<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n";
|
||
|
|
echo "</head>\n<body class=\"font-sans bg-slate-50 min-h-screen\">\n";
|
||
|
|
echo "<div class=\"min-h-screen flex items-center justify-center px-4 sm:px-6 lg:px-8\">\n";
|
||
|
|
echo "<div class=\"max-w-md w-full space-y-8\">\n";
|
||
|
|
echo "<div class=\"text-center\">\n";
|
||
|
|
echo "<div class=\"mx-auto h-24 w-24 bg-slate-100 rounded-full flex items-center justify-center mb-6\">\n";
|
||
|
|
echo "<svg class=\"h-12 w-12 text-slate-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n";
|
||
|
|
echo "<path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 12a3 3 0 11-6 0 3 3 0 016 0z\"></path>\n";
|
||
|
|
echo "</svg>\n";
|
||
|
|
echo "</div>\n";
|
||
|
|
echo "<h1 class=\"text-6xl font-bold text-slate-900 mb-2\">404</h1>\n";
|
||
|
|
echo "<h2 class=\"text-2xl font-semibold text-slate-900 mb-4\">Page Not Found</h2>\n";
|
||
|
|
echo "<p class=\"text-slate-600 mb-8\">The page you are looking for could not be found.</p>\n";
|
||
|
|
echo "<div class=\"space-y-4\">\n";
|
||
|
|
echo "<a href=\"/\" class=\"w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-slate-900 hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-900\">Return to Home</a>\n";
|
||
|
|
echo "<button onclick=\"history.back()\" class=\"w-full flex justify-center py-3 px-4 border border-slate-300 rounded-md shadow-sm text-sm font-medium text-slate-700 bg-white hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500\">Go Back</button>\n";
|
||
|
|
echo "</div>\n";
|
||
|
|
echo "<div class=\"mt-8 text-sm text-slate-500\">If you believe this is an error, please contact support.</div>\n";
|
||
|
|
echo "</div>\n";
|
||
|
|
echo "</div>\n";
|
||
|
|
echo "</div>\n";
|
||
|
|
echo "</body>\n</html>\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|