Enh: Attempt to show better error messages when DB-Connection is invalid

This commit is contained in:
Martin Rüegg 2023-08-12 14:40:38 +02:00
parent 28fa6d9485
commit 582a2cac6c
No known key found for this signature in database
8 changed files with 211 additions and 33 deletions

View File

@ -1,6 +1,10 @@
HumHub Changelog
================
1.16.0 (Unreleased)
--------------------------
- Enh #6512: Show error messages when DB connection configuration is invalid
1.15.0-beta.2 (Unreleased)
--------------------------
- Enh #6478: Add pseudo test class to allow population of DB with standard test data

View File

@ -7,6 +7,8 @@
*/
// comment out the following two lines when deployed to production
use humhub\helpers\DatabaseHelper;
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
@ -22,4 +24,10 @@ $config = yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/protected/config/web.php')
);
(new humhub\components\Application($config))->run();
try {
(new humhub\components\Application($config))->run();
} catch (\Throwable $ex) {
if (null === DatabaseHelper::handleConnectionErrors($ex)) {
throw $ex;
}
}

View File

@ -9,6 +9,7 @@
namespace humhub\commands;
use humhub\components\Module;
use humhub\helpers\DatabaseHelper;
use Yii;
use yii\console\Exception;
use yii\web\Application;
@ -78,9 +79,17 @@ class MigrateController extends \yii\console\controllers\MigrateController
*/
public function beforeAction($action)
{
// Make sure to define default table storage engine
if (in_array(Yii::$app->db->getDriverName(), ['mysql', 'mysqli'], true)) {
Yii::$app->db->pdo->exec('SET default_storage_engine=' . Yii::$app->params['databaseDefaultStorageEngine']);
// Make sure to define a default table storage engine
$db = Yii::$app->db;
try {
$db->open();
} catch (\Throwable $ex) {
DatabaseHelper::handleConnectionErrors($ex);
}
if (in_array($db->getDriverName(), ['mysql', 'mysqli'], true)) {
$db->pdo->exec('SET default_storage_engine=' . Yii::$app->params['databaseDefaultStorageEngine']);
}
return parent::beforeAction($action);
}

View File

@ -59,7 +59,7 @@ class Application extends \yii\console\Application implements \humhub\interfaces
));
}
if (BaseSettingsManager::isDatabaseInstalled()) {
if (BaseSettingsManager::isDatabaseInstalled(Yii::$app->params['databaseInstalled'] ?? false)) {
$baseUrl = Yii::$app->settings->get('baseUrl');
if (!empty($baseUrl)) {
if (Yii::getAlias('@web', false) === false) {

View File

@ -0,0 +1,147 @@
<?php
/*
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017-2023 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\helpers;
use Throwable;
use Yii;
/**
* @since 1.15
*/
class DatabaseHelper
{
public static function handleConnectionErrors(
Throwable $ex,
bool $print = true,
bool $die = true,
bool $forcePlainText = false
): ?string {
static $last = false;
if (!$ex instanceof \yii\db\Exception) {
return null;
}
if ($last) {
return null;
}
$last = true;
$trace = debug_backtrace(0);
$trace = end($trace);
if ($trace && $trace['function'] === 'handleException' && $trace['args'][0] instanceof \yii\db\Exception) {
return null;
}
switch ($ex->getCode()) {
case 2002:
$error = 'Hostname not found.';
break;
case 1044:
$error = 'Database not found or not accessible.';
break;
case 1049:
$error = 'Database not found.';
break;
default:
$error = $ex->getMessage();
}
/**
* @see https://www.php.net/manual/en/ref.pdo-odbc.connection.php
* @see https://www.php.net/manual/en/ref.pdo-ibm.connection.php
* @see https://www.php.net/manual/en/ref.pdo-pgsql.connection.php
*/
$dsn = preg_replace(
'@((?<=:|;)(?:user|uid|User ID|pwd|password)=)(.*?)(?=;(?:$|\w+=)|$)@i',
'$1****',
Yii::$app->db->dsn
);
try {
$additionalInfo = json_encode([get_class($ex), ...$ex->errorInfo], JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
$additionalInfo = 'N/A';
}
while ($ex->getPrevious()) {
$ex = $ex->getPrevious();
}
$htmlMessage = defined('YII_DEBUG') && YII_DEBUG
? sprintf('
<h1>Invalid database configuration</h1>
<p><strong>%s</strong></p>
<p>The following connection string was used:<br><code>%s</code></p>
<br>
<h2>Technical information</h2>
<p><code>%s</code></p>
<p><pre>%s</pre></p>
', $error, $dsn, $additionalInfo, $ex)
: sprintf('
<h1>Invalid database configuration</h1>
<p><strong>%s</strong></p>
', $error);
$txtMessage = defined('YII_DEBUG') && YII_DEBUG
? sprintf('
Invalid database configuration
==============================
%s
The following connection string was used:
%s
Technical information
---------------------
%s
%s
', $error, $dsn, $additionalInfo, $ex)
: sprintf('
Invalid database configuration
==============================
%s
The following connection string was used:
%s
Technical information
---------------------
%s
', $error, $dsn, $additionalInfo);
if ($print) {
if ($forcePlainText) {
echo $txtMessage;
} elseif (Yii::$app instanceof \yii\console\Application && Yii::$app->controller instanceof \yii\console\Controller) {
Yii::$app->controller->stderr($txtMessage);
} else {
header("HTTP/1.1 500 Internal Server Error");
echo $htmlMessage;
}
}
if (!$die) {
return $txtMessage;
}
die(1);
}
}

View File

@ -10,12 +10,12 @@ namespace humhub\libs;
use humhub\components\SettingActiveRecord;
use humhub\exceptions\InvalidArgumentTypeException;
use humhub\helpers\DatabaseHelper;
use Stringable;
use Yii;
use yii\base\Component;
use yii\base\InvalidArgumentException;
use yii\base\InvalidConfigException;
use yii\db\conditions\LikeCondition;
use yii\db\StaleObjectException;
use yii\helpers\Json;
@ -303,19 +303,20 @@ abstract class BaseSettingsManager extends Component
/**
* Checks if settings table exists or application is not installed yet
*
* @return bool
* @since 1.3
*/
public static function isDatabaseInstalled()
public static function isDatabaseInstalled(bool $dieOnError = false): bool
{
try {
if (in_array('setting', Yii::$app->db->schema->getTableNames())) {
return true;
}
$db = Yii::$app->db;
$db->open();
} catch (\Exception $ex) {
if ($dieOnError) {
DatabaseHelper::handleConnectionErrors($ex);
}
return false;
}
return false;
return in_array('setting', $db->schema->getTableNames());
}
}

View File

@ -8,27 +8,28 @@
namespace humhub\modules\installer\commands;
use humhub\helpers\DatabaseHelper;
use humhub\libs\DynamicConfig;
use humhub\libs\UUID;
use humhub\modules\installer\libs\InitialData;
use humhub\modules\user\models\Group;
use humhub\modules\user\models\Password;
use humhub\modules\user\models\User;
use Yii;
use yii\base\Exception;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\Console;
use yii\base\Exception;
use humhub\modules\user\models\User;
use humhub\modules\user\models\Password;
use humhub\modules\user\models\Group;
use humhub\modules\installer\libs\InitialData;
use humhub\libs\UUID;
use humhub\libs\DynamicConfig;
/**
* Console Install
*
*
* Example usage:
* php yii installer/write-db-config "$HUMHUB_DB_HOST" "$HUMHUB_DB_NAME" "$HUMHUB_DB_USER" "$HUMHUB_DB_PASSWORD"
* php yii installer/install-db
* php yii installer/write-site-config "$HUMHUB_NAME" "$HUMHUB_EMAIL"
* php yii installer/create-admin-account
*
*
*/
class InstallController extends Controller
{
@ -42,9 +43,9 @@ class InstallController extends Controller
return ExitCode::OK;
}
/**
* Tries to open a connection to given db.
* Tries to open a connection to given db.
* On success: Writes given settings to config-file and reloads it.
* On failure: Throws exception
*/
@ -80,12 +81,12 @@ class InstallController extends Controller
$this->stdout("Install DB:\n\n", Console::FG_YELLOW);
$this->stdout(" * Checking Database Connection\n", Console::FG_YELLOW);
if(!$this->checkDBConnection()){
throw new Exception("Could not connect to DB!");
if (true !== $message = $this->checkDBConnection()) {
throw new Exception($message ?? "Could not connect to DB!");
}
$this->stdout(" * Installing Database\n", Console::FG_YELLOW);
Yii::$app->cache->flush();
// Disable max execution time to avoid timeouts during migrations
@ini_set('max_execution_time', 0);
@ -119,7 +120,7 @@ class InstallController extends Controller
$user->profile->firstname = 'Sys';
$user->profile->lastname = 'Admin';
$user->profile->save();
$password = new Password();
$password->user_id = $user->id;
$password->setPassword($admin_pass);
@ -179,6 +180,8 @@ class InstallController extends Controller
/**
* Tries to open global db connection and checks result.
*
* @return true|null|string
*/
private function checkDBConnection()
{
@ -186,10 +189,9 @@ class InstallController extends Controller
// call setActive with true to open connection.
Yii::$app->db->open();
// return the current connection state.
return Yii::$app->db->getIsActive();
return Yii::$app->db->getIsActive() ?: null;
} catch (Exception $e) {
$this->stderr($e->getMessage());
return DatabaseHelper::handleConnectionErrors($e, false, false, true);
}
return false;
}
}

View File

@ -8,6 +8,8 @@
* @license http://www.yiiframework.com/license/
*/
use humhub\helpers\DatabaseHelper;
defined('YII_DEBUG') or define('YII_DEBUG', true);
// fcgi doesn't have STDIN and STDOUT defined by default
@ -25,6 +27,11 @@ $config = yii\helpers\ArrayHelper::merge(
require(__DIR__ . '/config/console.php')
);
$application = new humhub\components\console\Application($config);
$exitCode = $application->run();
exit($exitCode);
try {
$exitCode = (new humhub\components\console\Application($config))->run();
exit($exitCode);
} catch (\Throwable $ex) {
if (null === DatabaseHelper::handleConnectionErrors($ex)) {
throw $ex;
}
}