217 lines
5.3 KiB
PHP
217 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace KTXC;
|
|
|
|
use KTXC\Http\Request\Request;
|
|
use KTXC\Http\Response\Response;
|
|
use Psr\Container\ContainerInterface;
|
|
|
|
/**
|
|
* Application class - entry point for the framework
|
|
* Handles configuration loading and kernel lifecycle
|
|
*/
|
|
class Application
|
|
{
|
|
private static $composerLoader = null;
|
|
|
|
private Kernel $kernel;
|
|
private array $config;
|
|
private string $rootDir;
|
|
|
|
public function __construct(string $rootDir, ?string $environment = null, ?bool $debug = null)
|
|
{
|
|
$this->rootDir = $this->resolveProjectRoot($rootDir);
|
|
|
|
// Load configuration
|
|
$this->config = $this->loadConfig();
|
|
|
|
// Determine environment and debug mode
|
|
$environment = $environment ?? $this->config['environment'] ?? 'prod';
|
|
$debug = $debug ?? $this->config['debug'] ?? false;
|
|
|
|
// Create kernel with configuration
|
|
$this->kernel = new Kernel($environment, $debug, $this->config, $rootDir);
|
|
}
|
|
|
|
/**
|
|
* Run the application - handle incoming request and send response
|
|
*/
|
|
public function run(): void
|
|
{
|
|
try {
|
|
$request = Request::createFromGlobals();
|
|
$response = $this->handle($request);
|
|
$response->send();
|
|
$this->terminate();
|
|
} catch (\Throwable $e) {
|
|
// Last resort error handling for kernel initialization failures
|
|
error_log('Application error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
|
|
$content = $this->kernel->debug()
|
|
? '<pre>' . htmlspecialchars((string) $e) . '</pre>'
|
|
: 'An error occurred. Please try again later.';
|
|
$response = new Response($content, Response::HTTP_INTERNAL_SERVER_ERROR, [
|
|
'Content-Type' => 'text/html; charset=UTF-8',
|
|
]);
|
|
$response->send();
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a request
|
|
*/
|
|
public function handle(Request $request): Response
|
|
{
|
|
return $this->kernel->handle($request);
|
|
}
|
|
|
|
/**
|
|
* Terminate the application - process deferred events
|
|
*/
|
|
public function terminate(): void
|
|
{
|
|
$this->kernel->processEvents();
|
|
}
|
|
|
|
/**
|
|
* Get the kernel instance
|
|
*/
|
|
public function kernel(): Kernel
|
|
{
|
|
return $this->kernel;
|
|
}
|
|
|
|
/**
|
|
* Get the container instance
|
|
*/
|
|
public function container(): ContainerInterface
|
|
{
|
|
return $this->kernel->container();
|
|
}
|
|
|
|
/**
|
|
* Get the application root directory
|
|
*/
|
|
public function rootDir(): string
|
|
{
|
|
return $this->rootDir;
|
|
}
|
|
|
|
/**
|
|
* Get the modules directory
|
|
*/
|
|
public function moduleDir(): string
|
|
{
|
|
return $this->rootDir . '/modules';
|
|
}
|
|
|
|
/**
|
|
* Get configuration value
|
|
*/
|
|
public function config(?string $key = null, mixed $default = null): mixed
|
|
{
|
|
if ($key === null) {
|
|
return $this->config;
|
|
}
|
|
|
|
// Support dot notation: 'database.uri'
|
|
$keys = explode('.', $key);
|
|
$value = $this->config;
|
|
|
|
foreach ($keys as $k) {
|
|
if (!is_array($value) || !array_key_exists($k, $value)) {
|
|
return $default;
|
|
}
|
|
$value = $value[$k];
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Get environment
|
|
*/
|
|
public function environment(): string
|
|
{
|
|
return $this->kernel->environment();
|
|
}
|
|
|
|
/**
|
|
* Check if debug mode is enabled
|
|
*/
|
|
public function debug(): bool
|
|
{
|
|
return $this->kernel->debug();
|
|
}
|
|
|
|
/**
|
|
* Load configuration from config directory
|
|
*/
|
|
protected function loadConfig(): array
|
|
{
|
|
$configFile = $this->rootDir . '/config/system.php';
|
|
|
|
if (!file_exists($configFile)) {
|
|
error_log('Configuration file not found: ' . $configFile);
|
|
return [];
|
|
}
|
|
|
|
$config = include $configFile;
|
|
|
|
if (!is_array($config)) {
|
|
throw new \RuntimeException('Configuration file must return an array');
|
|
}
|
|
|
|
return $config;
|
|
}
|
|
|
|
/**
|
|
* Resolve the project root directory.
|
|
*
|
|
* Some entrypoints may pass the public/ directory or another subdirectory.
|
|
* We walk up the directory tree until we find composer.json.
|
|
*/
|
|
private function resolveProjectRoot(string $startDir): string
|
|
{
|
|
$dir = rtrim($startDir, '/');
|
|
if ($dir === '') {
|
|
return $startDir;
|
|
}
|
|
|
|
// If startDir is a file path, use its directory.
|
|
if (is_file($dir)) {
|
|
$dir = dirname($dir);
|
|
}
|
|
|
|
$current = $dir;
|
|
while (true) {
|
|
if (is_file($current . '/composer.json')) {
|
|
return $current;
|
|
}
|
|
|
|
$parent = dirname($current);
|
|
if ($parent === $current) {
|
|
// Reached filesystem root
|
|
return $dir;
|
|
}
|
|
$current = $parent;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the Composer ClassLoader instance
|
|
*/
|
|
public static function setComposerLoader($loader): void
|
|
{
|
|
self::$composerLoader = $loader;
|
|
}
|
|
|
|
/**
|
|
* Get the Composer ClassLoader instance
|
|
*/
|
|
public static function getComposerLoader()
|
|
{
|
|
return self::$composerLoader;
|
|
}
|
|
}
|