Skip to content

Instantly share code, notes, and snippets.

@nmcgann
Created November 23, 2015 21:57
Show Gist options
  • Save nmcgann/128f4da51eeb83868638 to your computer and use it in GitHub Desktop.
Save nmcgann/128f4da51eeb83868638 to your computer and use it in GitHub Desktop.
PDO class to include timing and logging. Tested with mysql and mssql
<?php
/**
* dbpdo.php
*
* Wrapper class for PDO to handle connecting to different types of db (mssql and mysql).
* Also handles mssql on both windows and linux platforms by using sqlsrv and dblib pdo drivers.
* Adds a logging and timing facility for queries and prepared statements.
* A logging function can be passed to the constructor to capture all calls to the PDO routines.
*
* All normal PDO and PDOStatement functions can be used with no alterations.
*
* Constructor parameters:
* $params['db_type'] - currently 'mssql' or 'mysql'
* $params['db_host']
* $params['db_port'] - (optional)
* $params['db_name'] - (optional)
* $params['db_username']
* $params['db_password']
* $params['commands'] - (optional) array of command strings to be run after connection made.
*
* After connection made isConnectionError() should be called to check if successful and then
* getConnectionError() gets the error message. All other errors raise an exception.
*
* The db logging routine passed should be able to accept a variable number of
* parameters to log each of which can be either scalar or arrays.
*
*/
class Dbpdo {
protected $db = null;
protected $dbError = '';
protected $dbLogHook = null;
// --------------------------------------------------------------------- //
public function __construct($params, $dbLogHook = null) {
$this->dbLogHook = $dbLogHook;
$this->openConnection($params);
}
// --------------------------------------------------------------------- //
protected function openConnection($params) {
$commands = array(); //special setup commands
//handle missing params (e.g. future proofing if sqlite used)
$dbtype = isset($params['db_type']) ? $params['db_type'] : 'mysql';
$host = isset($params['db_host']) ? $params['db_host'] : '';
$port = isset($params['db_port']) ? $params['db_port'] : '';
$dbname = isset($params['db_name']) ? $params['db_name'] : '';
$username = isset($params['db_username']) ? $params['db_username'] : '';
$password = isset($params['db_password']) ? $params['db_password'] : '';
//add any extra init commands
$commands = isset($params['commands']) ? array_merge($commands, $params['commands']) : $commands;
if($dbtype == 'mssql') {
$port = ($port != '') ? $port : 1433; //default port if not set
//windows servers have different drivers & dsn strings (Mac shows darwin, so can't just look for "win")
if(strncasecmp(PHP_OS, 'WIN', 3) == 0) {
$dsn = "sqlsrv:server=$host,$port". (($dbname != "") ? ";database=$dbname" : "");
} else {
//linux etc.
$dsn = "dblib:host=$host:$port".(($dbname != "") ? ";dbname=$dbname" : "");
}
$commands[] = "SET QUOTED_IDENTIFIER ON"; //should be unnecessary, but safest to be explicit
} else {
//assumed mysql
$port = ($port != '') ? $port : 3306;
$charset = isset($params['db_charset']) ? ';charset=' . $params['db_charset'] : ';charset=utf8';
$dsn = "mysql:host=$host;port=$port". (($dbname != "") ? ";dbname=$dbname" : "") . $charset;
}
//open up (suppressing separate messages)
try {
$this->db = @new PDO($dsn, $username, $password);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if($dbtype != 'mssql') {
$this->db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
//run any special set up commands
foreach($commands as $c) {
$this->db->exec($c);
}
} catch(PDOException $e) {
$this->dbError = $e->getMessage();
$this->db = null;
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn('Error connecting to database', $this->dbError);
}
//exit($this->dbError);
}
}
// --------------------------------------------------------------------- //
public function isConnectionError () {
return is_null($this->db);
}
// --------------------------------------------------------------------- //
public function getConnectionError () {
return $this->dbError;
}
// --------------------------------------------------------------------- //
public function query(){
$args = func_get_args();
$start = microtime(true);
$result = call_user_func_array(array($this->db, 'query'), $args);
$time = microtime(true) - $start;
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn('PDO->query took ' . round($time * 1000, 3) . 'ms ', $args);
}
return $result;
}
// --------------------------------------------------------------------- //
public function exec($statement){
$start = microtime(true);
$result = $this->db->exec($statement);
$time = microtime(true) - $start;
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn('PDO->exec took ' . round($time * 1000, 3) . 'ms ', $statement);
}
return $result;
}
// --------------------------------------------------------------------- //
//call standard PDO methods
public function __call($name, $args) {
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn($name, $args);
}
$result = call_user_func_array(array($this->db, $name), $args);
//substitute logged PDOStatement if a logger is present
if(is_object($result) && get_class($result) === 'PDOStatement' && is_callable($this->dbLogHook)) {
return new LoggedPDOStatement($result, $this->dbLogHook);
}
return $result;
}
} //eoc
/**
* LoggedPDOStatement is returned instead of PDOStatement when logging is active.
* This allows the prepared statement execution time to be logged.
*
*/
class LoggedPDOStatement {
private $statement;
private $dbLogHook = null;
// --------------------------------------------------------------------- //
public function __construct(PDOStatement $statement, $dbLogHook = null) {
$this->statement = $statement;
$this->dbLogHook = $dbLogHook;
}
// --------------------------------------------------------------------- //
public function execute($args = null) {
$start = microtime(true);
$result = $this->statement->execute($args);
$time = microtime(true) - $start;
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn('PDOStatement->execute took ' . round($time * 1000, 3) . 'ms ', is_null($args) ? '(no parameters)' : $args);
}
return $result;
}
// --------------------------------------------------------------------- //
//functions with pass-by-reference parameter(s) need special treatment
public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null) {
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn('PDOStatement->bindColumn', $column, $param, $type, $maxlen, $driverdata);
}
return $this->statement->bindColumn($column, $param, $type, $maxlen, $driverdata);
}
// --------------------------------------------------------------------- //
public function bindParam($parameter, &$variable, $data_type = PDO::PARAM_STR, $length = null, $driver_options = null) {
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn('PDOStatement->bindParam', $parameter, $variable, $data_type, $length, $driver_options);
}
return $this->statement->bindParam($parameter, $variable, $data_type, $length, $driver_options);
}
// --------------------------------------------------------------------- //
public function bindValue($parameter, $value, $data_type = PDO::PARAM_STR) {
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn('PDOStatement->bindValue', $parameter, $value, $data_type);
}
return $this->statement->bindValue($parameter, $value, $data_type);
}
// --------------------------------------------------------------------- //
//all other functions go direct to PDOStatement object
public function __call($name, $param) {
//call logging hook if present
if(is_callable($this->dbLogHook)) {
$fn = $this->dbLogHook;
$fn($name, $param);
}
return call_user_func_array(array($this->statement, $name), $param);
}
} //eoc
/* end */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment