safeLoad(); } $storagePath = self::env('STORAGE_PATH', $basePath . '/storage'); if (!is_dir($storagePath)) { mkdir($storagePath, 0775, true); } $databasePath = self::env('DATABASE_PATH', $storagePath . '/database.sqlite'); if (!self::isAbsolutePath($databasePath)) { $databasePath = $basePath . '/' . ltrim($databasePath, '/'); } $jwtSecret = self::env('JWT_SECRET') ?? self::resolveSecret($storagePath . '/secret.key'); $jwtTtl = (int) (self::env('JWT_TTL') ?? '86400'); $displayErrors = filter_var(self::env('APP_DEBUG', 'false'), FILTER_VALIDATE_BOOL); return new self($databasePath, $jwtSecret, $jwtTtl, $displayErrors); } private static function env(string $key, ?string $default = null): ?string { $value = $_ENV[$key] ?? $_SERVER[$key] ?? getenv($key); if ($value === false || $value === null || $value === '') { return $default; } return (string) $value; } private static function isAbsolutePath(string $path): bool { return str_starts_with($path, '/') || preg_match('#^[A-Za-z]:[\\\\/]#', $path) === 1; } /** * Return the persisted signing secret, generating and storing one on first run * so local development works with zero configuration. */ private static function resolveSecret(string $path): string { if (is_file($path)) { return trim((string) file_get_contents($path)); } $secret = bin2hex(random_bytes(32)); file_put_contents($path, $secret); @chmod($path, 0600); return $secret; } }