star7th/showdoc · critical · RuntimeException
Bootstrap initialization failed: {$e->getMessage()}
Error message
Bootstrap initialization failed: {$e->getMessage()} What it means
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:'.
Source
Thrown at server/app/Common/bootstrap.php:21
namespace App\Common;
use App\Common\Database\Database;
use App\Common\Database\Upgrade;
use App\Common\Cache\CacheManager;
// 基础引导文件:后续可在此初始化配置、日志等。
try {
Database::getInstance();
CacheManager::getInstance();
// 检查并执行数据库升级(仅在 Web 环境下执行,避免 CLI 任务时重复执行)
if (PHP_SAPI !== 'cli') {
Upgrade::checkAndUpgrade();
}
} catch (\Throwable $e) {
// 初始化失败时抛出异常,让 Slim 错误处理器处理
throw new \RuntimeException('Bootstrap initialization failed: ' . $e->getMessage(), 0, $e);
}
View on GitHub (pinned to 6a3fa91eee)
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.
Example fix
// before (server/app/Common/bootstrap.php)
try {
Database::getInstance();
CacheManager::getInstance();
if (PHP_SAPI !== 'cli') {
Upgrade::checkAndUpgrade();
}
} catch (\Throwable $e) {
throw new \RuntimeException('Bootstrap initialization failed: ' . $e->getMessage(), 0, $e);
}
// after: fail with an actionable, environment-aware message while keeping the cause chained
} catch (\Throwable $e) {
$hint = match (true) {
str_contains($e->getMessage(), 'unable to open database file') => ' Check DB_NAME path exists and is writable by the web user.',
str_contains($e->getMessage(), 'Connection refused') => ' Check DB_HOST/DB_PORT — the MySQL server is unreachable.',
default => '',
};
throw new \RuntimeException('Bootstrap initialization failed: ' . $e->getMessage() . $hint, 0, $e);
} Defensive patterns
Strategy: validation
Validate before calling
// pre-flight before any request reaches Slim (e.g. in public/index.php or a health route):
$dbPath = Env::get('DB_NAME', __DIR__ . '/../Sqlite/showdoc.db.php');
if (strtolower(Env::get('DB_TYPE', 'sqlite')) === 'sqlite') {
$dir = dirname($dbPath);
if (!is_dir($dir) || !is_writable($dir)) {
http_response_code(503);
exit("DB directory not writable: {$dir}");
}
} Try / catch
// Slim 4 error middleware / handler: the RuntimeException is already chained ($e->getPrevious()),
// so surface BOTH messages and return 503 instead of a raw 500:
set_error_handler(function (\Throwable $e) {
$cause = $e->getPrevious() ? ' caused by ' . get_class($e->getPrevious()) . ': ' . $e->getPrevious()->getMessage() : '';
error_log('[bootstrap] ' . $e->getMessage() . $cause);
}); Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
AI-assisted analysis of star7th/showdoc@6a3fa91eee (2026-08-21).
Data as JSON: /api/errors/47561ff3e1d66372.
Report an issue: GitHub.