{"record":{"id":"47561ff3e1d66372","repo":"star7th/showdoc","slug":"bootstrap-initialization-failed-e-getmessage","errorCode":null,"errorMessage":"Bootstrap initialization failed: {$e->getMessage()}","messagePattern":"Bootstrap initialization failed: (.+?)","errorType":"http","errorClass":"RuntimeException","httpStatus":500,"severity":"critical","filePath":"server/app/Common/bootstrap.php","lineNumber":21,"sourceCode":"namespace App\\Common;\n\nuse App\\Common\\Database\\Database;\nuse App\\Common\\Database\\Upgrade;\nuse App\\Common\\Cache\\CacheManager;\n\n// 基础引导文件：后续可在此初始化配置、日志等。\n\ntry {\n    Database::getInstance();\n    CacheManager::getInstance();\n    \n    // 检查并执行数据库升级（仅在 Web 环境下执行，避免 CLI 任务时重复执行）\n    if (PHP_SAPI !== 'cli') {\n        Upgrade::checkAndUpgrade();\n    }\n} catch (\\Throwable $e) {\n    // 初始化失败时抛出异常，让 Slim 错误处理器处理\n    throw new \\RuntimeException('Bootstrap initialization failed: ' . $e->getMessage(), 0, $e);\n}\n\n","sourceCodeStart":3,"sourceCodeEnd":24,"githubUrl":"https://github.com/star7th/showdoc/blob/6a3fa91eee5ebf36ba9d9cba17e0fea6dfd4bb89/server/app/Common/bootstrap.php#L3-L24","documentation":"This is the server's composition-root wrapper: bootstrap.php runs on every web request, initializes the database (Database::getInstance(), SQLite by default or MySQL when DB_TYPE=mysql), the cache (CacheManager::getInstance(), optional Redis with errors silently swallowed), and (web SAPI only) Upgrade::checkAndUpgrade() for schema migrations. Any Throwable escaping those three calls is rethrown as RuntimeException('Bootstrap initialization failed: ' . message, 0, $e) for Slim's error handler — so the message you see is the original cause (e.g. a PDO connection error) prefixed with 'Bootstrap initialization failed:'.","triggerScenarios":"SQLite mode: DB_NAME path (default Sqlite/showdoc.db.php) missing, outside open_basedir, or not writable by the PHP user; pdo_sqlite extension disabled. MySQL mode (DB_TYPE=mysql): wrong DB_HOST/DB_USER/DB_PWD, server down, unknown database -> PDO connect exception. Upgrade::checkAndUpgrade(): ALTER/CREATE statements failing on a locked or read-only database, or a partially migrated schema from an interrupted upgrade.","commonSituations":"Fresh clone deployed without creating Sqlite/ directory or without chmod (very common after git pull on a new host); Docker container where the data volume is mounted read-only or owned by root; ops switches DB_TYPE=mysql but leaves default credentials root/''; shared hosting open_basedir restrictions excluding the SQLite path; restoring a backup of an older schema so the first web request's upgrade step fails; Redis being down is NOT a cause here — CacheManager catches its own connection errors.","solutions":["Read the suffixed original message: 'SQLSTATE[HY000] ... unable to open database file' -> SQLite path/permission issue; 'Connection refused'/'Access denied' -> MySQL config; 'no such table'/'syntax error' near ALTER -> upgrade/schema issue.","SQLite: ensure the Sqlite/ directory and showdoc.db.php exist and are writable by the web user (`mkdir -p Sqlite && chown www-data: Sqlite && chmod 755 Sqlite`), and that pdo_sqlite is enabled (`php -m | grep sqlite`).","MySQL: set DB_TYPE=mysql and verify DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PWD via Env (env file or web-server env), test with `mysql -h $DB_HOST -u $DB_USER -p $DB_NAME`.","Upgrade failures: check the PHP error log for the failing migration statement, back up the database, fix the reported statement's cause (usually permissions or a duplicate column from a re-run), then reload — checkAndUpgrade() only runs on web requests, so one fixed reload completes it.","Temporarily diagnose by running `php -r \"require 'server/app/Common/bootstrap.php';\"` from CLI (upgrade step is skipped in CLI) to isolate DB init from migration errors.","If open_basedir is set, add the SQLite directory (and Sqlite/showdoc.db.php) to the allowed paths."],"exampleFix":"// before (server/app/Common/bootstrap.php)\ntry {\n    Database::getInstance();\n    CacheManager::getInstance();\n    if (PHP_SAPI !== 'cli') {\n        Upgrade::checkAndUpgrade();\n    }\n} catch (\\Throwable $e) {\n    throw new \\RuntimeException('Bootstrap initialization failed: ' . $e->getMessage(), 0, $e);\n}\n\n// after: fail with an actionable, environment-aware message while keeping the cause chained\n} catch (\\Throwable $e) {\n    $hint = match (true) {\n        str_contains($e->getMessage(), 'unable to open database file') => ' Check DB_NAME path exists and is writable by the web user.',\n        str_contains($e->getMessage(), 'Connection refused') => ' Check DB_HOST/DB_PORT — the MySQL server is unreachable.',\n        default => '',\n    };\n    throw new \\RuntimeException('Bootstrap initialization failed: ' . $e->getMessage() . $hint, 0, $e);\n}","handlingStrategy":"validation","validationCode":"// pre-flight before any request reaches Slim (e.g. in public/index.php or a health route):\n$dbPath = Env::get('DB_NAME', __DIR__ . '/../Sqlite/showdoc.db.php');\nif (strtolower(Env::get('DB_TYPE', 'sqlite')) === 'sqlite') {\n    $dir = dirname($dbPath);\n    if (!is_dir($dir) || !is_writable($dir)) {\n        http_response_code(503);\n        exit(\"DB directory not writable: {$dir}\");\n    }\n}","typeGuard":null,"tryCatchPattern":"// Slim 4 error middleware / handler: the RuntimeException is already chained ($e->getPrevious()),\n// so surface BOTH messages and return 503 instead of a raw 500:\nset_error_handler(function (\\Throwable $e) {\n    $cause = $e->getPrevious() ? ' caused by ' . get_class($e->getPrevious()) . ': ' . $e->getPrevious()->getMessage() : '';\n    error_log('[bootstrap] ' . $e->getMessage() . $cause);\n});","preventionTips":["Make the SQLite directory part of deployment scripts (mkdir + chown web-user) so a fresh host cannot boot without it.","Add a /health endpoint that calls Database::getInstance() and reports 200/503 — monitoring catches bootstrap failures before users do.","Keep the chained previous exception when logging; the prefix alone hides whether it was PDO, Redis, or an ALTER statement that failed.","Back up the database before deploying upgrades, since checkAndUpgrade() runs automatically on the first web request and a failed migration repeats on every request.","Document required env vars (DB_TYPE, DB_HOST, DB_USER, DB_PWD, DB_NAME, REDIS_HOST) and validate them at deploy time, not at first request."],"tags":["php","bootstrap","database-connection","sqlite","mysql","schema-upgrade"],"backgroundTag":"database-connection-failed","analyzedSha":"6a3fa91eee5ebf36ba9d9cba17e0fea6dfd4bb89","analyzedAt":"2026-08-21T01:16:20.916Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}