. */ use PDO; use PDOException; /** * Class Database Class - Extend PHP's native PDO class * to provide database access with logging, etc. */ 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; /** * 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 * * @return void */ 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::$instance = new self; } /** * We don't access $instance directly, only via query(), exec() and prepare() * * @return Database * * @throws \Exception */ 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 boolean */ public static function isConnected() { return self::$pdo instanceof PDO; } /** * Log the details of a query, for debugging and analysis. * * @param string $query * @param integer $rows * @param double $microtime * @param string[] $bind_variables * * @return void */ 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 $query2 = ''; foreach ($bind_variables as $key => $value) { if (is_null($value)) { $bind_variables[$key] = '[NULL]'; } } foreach (str_split(Filter::escapeHtml($query)) as $char) { if ($char == '?') { $query2 .= '' . $char . ''; } else { $query2 .= $char; } } // Highlight embedded literal strings. if (preg_match('/[\'"]/', $query)) { $query2 = '' . $query2 . ''; } // 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) |
|---|