. */ namespace Fisharebest\Webtrees; use Fisharebest\Webtrees\Schema\MigrationInterface; use PDO; use PDOException; /** * Extend PHP's native PDO class. */ class Database { /** @var Database Implement the singleton pattern */ private static $instance; /** @var PDO Native PHP database driver */ private static $pdo; /** @var array Keep a log of all the SQL statements that we execute */ private static $log; /** @var Statement[] Cache of prepared statements */ private static $prepared = array(); /** * Prevent instantiation via new Database */ private function __construct() { self::$log = array(); } /** * Begin a transaction. * * @return bool */ public static function beginTransaction() { return self::$pdo->beginTransaction(); } /** * Commit this transaction. * * @return bool */ public static function commit() { return self::$pdo->commit(); } /** * Disconnect from the server, so we can connect to another one */ public static function disconnect() { self::$pdo = null; } /** * Implement the singleton pattern, using a static accessor. * * @param string $DBHOST * @param string $DBPORT * @param string $DBNAME * @param string $DBUSER * @param string $DBPASS * * @throws \Exception */ public static function createInstance($DBHOST, $DBPORT, $DBNAME, $DBUSER, $DBPASS) { if (self::$pdo instanceof PDO) { throw new \Exception('Database::createInstance() can only be called once.'); } // Create the underlying PDO object self::$pdo = new PDO( (substr($DBHOST, 0, 1) === '/' ? "mysql:unix_socket={$DBHOST};dbname={$DBNAME}" : "mysql:host={$DBHOST};dbname={$DBNAME};port={$DBPORT}" ), $DBUSER, $DBPASS, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_AUTOCOMMIT => true, ) ); self::$pdo->exec("SET NAMES UTF8"); self::$pdo->prepare("SET time_zone = :time_zone")->execute(array('time_zone' => date('P'))); self::$instance = new self; } /** * We don't access $instance directly, only via query(), exec() and prepare() * * @throws \Exception * * @return Database */ public static function getInstance() { if (self::$pdo instanceof PDO) { return self::$instance; } else { throw new \Exception('createInstance() must be called before getInstance().'); } } /** * Are we currently connected to a database? * * @return bool */ public static function isConnected() { return self::$pdo instanceof PDO; } /** * Log the details of a query, for debugging and analysis. * * @param string $query * @param int $rows * @param float $microtime * @param string[] $bind_variables */ public static function logQuery($query, $rows, $microtime, $bind_variables) { if (WT_DEBUG_SQL) { // Full logging // Trace $trace = debug_backtrace(); array_shift($trace); array_shift($trace); foreach ($trace as $n => $frame) { if (isset($frame['file']) && isset($frame['line'])) { $trace[$n] = basename($frame['file']) . ':' . $frame['line'] . ' ' . $frame['function']; } else { unset($trace[$n]); } } $stack = '' . (count(self::$log) + 1) . ''; // Bind variables foreach ($bind_variables as $key => $value) { if (is_null($value)) { $value = 'NULL'; } elseif (!is_integer($value)) { $value = '\'' . $value . '\''; } if (is_integer($key)) { $query = preg_replace('/\?/', $value, $query, 1); } else { $query = str_replace(':' . $key, $value, $query); } } // Highlight slow queries $microtime *= 1000; // convert to milliseconds if ($microtime > 1000) { $microtime = sprintf('%.3f', $microtime); } elseif ($microtime > 100) { $microtime = sprintf('%.3f', $microtime); } elseif ($microtime > 1) { $microtime = sprintf('%.3f', $microtime); } else { $microtime = sprintf('%.3f', $microtime); } self::$log[] = "
# | Query | Rows | Time (ms) |
---|