Skip to content

Commit

Permalink
Reworking database connection checking and adding a test script.
Browse files Browse the repository at this point in the history
MySQL PDO extension throws an error instead of an exception when trying to query from a connection that has gone away, it fucked everything up. Hopefully the other PDO extensions don't do the same.
Also added the ability to manually check the connection and force reconnect yourself.
  • Loading branch information
jurchiks committed Jan 5, 2016
1 parent 3b9e1bf commit bae2400
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 39 deletions.
21 changes: 21 additions & 0 deletions autoloader.php
@@ -0,0 +1,21 @@
<?php
spl_autoload_register(
function ($className)
{
$ds = DIRECTORY_SEPARATOR;
$className = str_replace('js\\tools\\dbhandler', '', $className);
$className = str_replace('\\', $ds, $className);
$className = trim($className, $ds);

$path = __DIR__ . $ds . 'src' . $ds . $className . '.php';

if (!is_readable($path))
{
return false;
}

require $path;
return true;
},
true
);
47 changes: 32 additions & 15 deletions src/Handler.php
Expand Up @@ -33,13 +33,16 @@ public static function getConnection($name = 'default', array $connectionParamet
throw new DbException('Invalid connection name ' . var_export($name, true));
}

/** @var Handler[] */
/** @var Handler[] $connections */
static $connections = [];

if (isset($connections[$name]))
{
// existing connection, check if it is valid and if not - try to reconnect
$connections[$name]->checkConnection();
if (!$connections[$name]->checkConnection())
{
$connections[$name]->connect();
}
}
else
{
Expand Down Expand Up @@ -82,9 +85,10 @@ private function __construct($name, $connectionParameters, $customOptions)
$options = array_merge($defaultOptions, $customOptions);

$this->name = $name;
$this->connection = self::connect($connectionParameters, $options);
$this->connectionParameters = $connectionParameters;
$this->connectionOptions = $options;

$this->connect();
}

private function __clone()
Expand Down Expand Up @@ -286,36 +290,49 @@ public function getLastInsertId()
return $this->connection->lastInsertId();
}

private function checkConnection()
public function checkConnection()
{
try
{
$sum = $this->connection->query('SELECT 1 + 1')->fetchColumn(0);
// this throws an error (not an exception) if the connection has gone away;
// needs to be suppressed because we want only exceptions
$sum = @$this->connection->query('SELECT 1 + 1');

if ($sum instanceof \PDOStatement)
{
$sum = $sum->fetchColumn(0);
}
else
{
$sum = 0;
}

if (intval($sum) !== 2)
{
// Invalid result, try reconnecting because something is clearly wrong.
$this->connection = self::connect($this->connectionParameters, $this->connectionOptions);
// Invalid result, something is clearly wrong.
return false;
}
}
catch (\Exception $e)
{
// Connection may have timed out, try reconnecting.
// Connection may have timed out.
// There is no single standard SQLSTATE code for "connection timed out", nor is there a built-in way to check this,
// so we can only guess that that's what happened.
$this->connection = self::connect($this->connectionParameters, $this->connectionOptions);
return false;
}

return true;
}

private static function connect($connectionParameters, $connectionOptions)
public function connect()
{
try
{
return new \PDO(
self::buildDNSString($connectionParameters),
$connectionParameters['username'],
$connectionParameters['password'],
$connectionOptions
$this->connection = new \PDO(
self::buildDNSString($this->connectionParameters),
$this->connectionParameters['username'],
$this->connectionParameters['password'],
$this->connectionOptions
);
}
catch (\PDOException $e)
Expand Down
26 changes: 2 additions & 24 deletions test.php
@@ -1,28 +1,6 @@
<?php
// composer code:
// require __DIR__ . '/vendor/autoload.php';
// non-composer code (can run this in console immediately):
spl_autoload_register(
function ($className)
{
$ds = DIRECTORY_SEPARATOR;
$className = str_replace('js\\tools\\dbhandler', '', $className);
$className = str_replace('\\', $ds, $className);
$className = trim($className, $ds);

$path = __DIR__ . $ds . 'src' . $ds . $className . '.php';

if (!is_readable($path))
{
return false;
}

require $path;
return true;
},
true
);
// common code:
require 'autoloader.php';

use js\tools\dbhandler\exceptions\DbException;
use js\tools\dbhandler\exceptions\QueryException;
use js\tools\dbhandler\Handler;
Expand Down
46 changes: 46 additions & 0 deletions test_mysql_timeout.php
@@ -0,0 +1,46 @@
<?php
require 'autoloader.php';

use js\tools\dbhandler\exceptions\DbException;
use js\tools\dbhandler\Handler;

try
{
// TODO set your own test connection parameters!
$handler = Handler::getConnection('test', [
'driver' => 'mysql',
'username' => 'test',
'password' => 'test',
'database' => 'test',
]);
// this is the mysql.ini variable that should be adjusted according to needs
// but it works via query as well
$handler->exec('SET SESSION wait_timeout = 1');

echo 'connection timeout set to 1 second, sleeping', PHP_EOL;
sleep(3);

if ($handler->checkConnection())
{
echo 'connection is good', PHP_EOL;
}
else
{
echo 'connection is down, reconnecting', PHP_EOL;
$handler->connect();
}

if ($handler->checkConnection())
{
echo 'connection is now good', PHP_EOL;
}
else
{
echo 'connection is still down, failed to reconnect', PHP_EOL;
}
}
catch (DbException $e)
{
echo $e->getMessage(), PHP_EOL;
die();
}

0 comments on commit bae2400

Please sign in to comment.