* @package Wombat * @subpackage db */ WBClass::load('WBLog'); /** * Simple SQL class * * Allows to connect and query MySQL DB * * @version 0.4.1 * @package Wombat * @subpackage db */ class WBDatasource_SQL extends WBStdClass { /** * Id of current database connection * @var string */ protected $dbcId = null; /** * Bufferd DB connections * @var array */ static protected $dbcs = array(); /** * Logger * @var WBLog */ protected $log; /** * Config * * Holds configuration variables: * - dbconfig * - host * - user * - database * - password * - driver * - port * * @var array */ protected $config = array( 'dbconfig' => '', 'driver' => 'MySQL', 'port' => 3306, 'host' => null, 'user' => null, 'password' => null, 'database' => null, ); /** * Constructor * * Parameter: * - "dbconfig": name of database configuration file * - "driver": db driver (MySQL) like MySQL or Postgres * - "port": db port to connect (3306) * - "host": database * - "database": database * - "user": db user * - "password": db user's password * * @param array $parameter */ public function __construct( $parameter = array() ) { $this->log = WBLog::start(__CLASS__); // get config either from parameter or file /** @var WBConfig */ $conf = WBClass::create( 'WBConfig' ); if (empty($parameter['dbconfig'])) { $parameter['dbconfig'] = 'config'; } // load from db config file $conf->load($parameter['dbconfig']); $parameter = array_merge($conf->get('db', array()), $parameter); // override default configuration with parameter foreach ($this->config as $k => $v) { if (!empty($parameter[$k])) { $this->config[$k] = $parameter[$k]; } } // verify config foreach (array('driver', 'port', 'host', 'database', 'user', 'password') as $k) { if (empty($this->config[$k])) { WBClass::load('WBException_Argument'); throw new WBException_Argument('Invalid parameter set to connect DB. Parameter driver (MySQL), port (3306), host, database, user, password must not be empty.', 7, __CLASS__); } } } /** * disconnect from database * */ public function __destruct() { if( !$this->dbcId ) { return; } // decrese reference counter and disconnect --self::$dbcs[$this->dbcId]['count']; $log = array( 'action' => 'diconnect', 'connection'=> 'keep', 'id' => $this->dbcId, 'refcount' => self::$dbcs[$this->dbcId]['count'] ); if( self::$dbcs[$this->dbcId]['count'] > 0 ) { $this->log->notice( $log ); return; } $log['connection'] = 'close'; $this->log->notice( $log ); self::$dbcs[$this->dbcId]['dbc']->close(); } /** * connect to databse * * dynamicly connect to mysql database. Each connection is buffered to avoid * multiple connection to the same databse. This implies, that there can be * be only one connection per user, host and databse. * * @todo switch on caching * @return mysqli */ public function connect() { // only once if( $this->dbcId ) { return self::$dbcs[$this->dbcId]['dbc']; } // allow only one instance per host, database and user $id = $this->getId(); if( isset( self::$dbcs[$id] ) && self::$dbcs[$id]['count'] > 0 ) { ++self::$dbcs[$id]['count']; $this->dbcId = $id; $log = array( 'action' => 'connect', 'connection'=> 'old', 'id' => $this->dbcId, 'refcount' => self::$dbcs[$this->dbcId]['count'] ); $this->log->notice( $log ); return self::$dbcs[$this->dbcId]['dbc']; } $dbc = new mysqli( $this->config['host'], $this->config['user'], $this->config['password'], $this->config['database'], $this->config['port'] ); if( mysqli_connect_errno() ) { WBClass::load( 'WBException_Datasource' ); throw new WBException_Datasource( 'Could not connect to database', 1, __CLASS__ ); return false; } $dbc->set_charset( 'utf8' ); // buffer self::$dbcs[$id] = array( 'dbc' => $dbc, 'count' => 1 ); $this->dbcId = $id; $log = array( 'action' => 'connect', 'connection'=> 'new', 'id' => $this->dbcId, 'refcount' => 1 ); $this->log->notice( $log ); return self::$dbcs[$this->dbcId]['dbc']; } /** * receive connection id * * Create connection id using current config * * @return string */ public function getId() { $id = $this->config['host'] . '_' . $this->config['user'] . '_' . $this->config['database']; return $id; } /** * Quote anything for SQL * * @param mixed $value * @param bool $like whether the quote should be done for LIKE * @return string */ public function quote($value, $like = false) { if ($like) { $dbc = $this->connect(); $value = $dbc->real_escape_string($value); $replace = array( '\\\\' => '\\\\\\\\', '%' => '\\%', '_' => '\\_' ); return str_replace(array_keys($replace), array_values($replace), $value); } // integer values are just OK if (is_int($value)) { return $value; } if (is_double($value) || is_float($value)) { return sprintf('%F', $value); } if (is_string($value) && preg_match('/^\d+$/', $value)) { // convert leading zero values to string if (preg_match('/^0+/', $value)) { return '"' . $value . '"'; } return $value; } if (is_bool($value)) { return $value ? '1' : '0'; } if (is_null($value)) { return 'NULL'; } $dbc = $this->connect(); return "'" . $dbc->real_escape_string($value) . "'"; } /** * execute query string * * Connects to database and execute query * * @param string $query * @return mysqli_result */ public function query($q) { $dbc = $this->connect(); $log = array( 'action' => 'query', 'id' => $this->dbcId, 'query' => $q, ); try { $res = $dbc->query($q); if (!$res) { $log['error'] = $dbc->error; $this->log->err($log); WBClass::load( 'WBException_Datasource' ); throw new WBException_Datasource('Database query failed! ' . $q, 2, __CLASS__); } } catch(Exception $e) { $log['error'] = $dbc->error; $this->log->err($log); WBClass::load( 'WBException_Datasource' ); throw new WBException_Datasource('Database query failed! ' . $q, 2, __CLASS__); } // choose log level if (0 == strncmp('SELECT ', $q, 6)) { $this->log->notice($log); } else { $this->log->warn($log); } return $res; } }