354 lines
6.9 KiB
PHP
354 lines
6.9 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace PicoDb;
|
||
|
|
||
|
use PDO;
|
||
|
use PDOException;
|
||
|
use PDOStatement;
|
||
|
|
||
|
/**
|
||
|
* Statement Handler
|
||
|
*
|
||
|
* @package PicoDb
|
||
|
* @author Frederic Guillot
|
||
|
*/
|
||
|
class StatementHandler
|
||
|
{
|
||
|
/**
|
||
|
* Database instance
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var Database
|
||
|
*/
|
||
|
protected $db = null;
|
||
|
|
||
|
/**
|
||
|
* Flag to calculate query time
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var boolean
|
||
|
*/
|
||
|
protected $stopwatch = false;
|
||
|
|
||
|
/**
|
||
|
* Start time
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var float
|
||
|
*/
|
||
|
protected $startTime = 0;
|
||
|
|
||
|
/**
|
||
|
* Execution time of all queries
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var float
|
||
|
*/
|
||
|
protected $executionTime = 0;
|
||
|
|
||
|
/**
|
||
|
* Flag to log generated SQL queries
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var boolean
|
||
|
*/
|
||
|
protected $logQueries = false;
|
||
|
|
||
|
/**
|
||
|
* Run explain command on each query
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var boolean
|
||
|
*/
|
||
|
protected $explain = false;
|
||
|
|
||
|
/**
|
||
|
* Number of SQL queries executed
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var integer
|
||
|
*/
|
||
|
protected $nbQueries = 0;
|
||
|
|
||
|
/**
|
||
|
* SQL query
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var string
|
||
|
*/
|
||
|
protected $sql = '';
|
||
|
|
||
|
/**
|
||
|
* Positional SQL parameters
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $positionalParams = array();
|
||
|
|
||
|
/**
|
||
|
* Named SQL parameters
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $namedParams = array();
|
||
|
|
||
|
/**
|
||
|
* Flag to use named params
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var boolean
|
||
|
*/
|
||
|
protected $useNamedParams = false;
|
||
|
|
||
|
/**
|
||
|
* LOB params
|
||
|
*
|
||
|
* @access protected
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $lobParams = array();
|
||
|
|
||
|
/**
|
||
|
* Constructor
|
||
|
*
|
||
|
* @access public
|
||
|
* @param Database $db
|
||
|
*/
|
||
|
public function __construct(Database $db)
|
||
|
{
|
||
|
$this->db = $db;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enable query logging
|
||
|
*
|
||
|
* @access public
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function withLogging()
|
||
|
{
|
||
|
$this->logQueries = true;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Record query execution time
|
||
|
*
|
||
|
* @access public
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function withStopWatch()
|
||
|
{
|
||
|
$this->stopwatch = true;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Execute explain command on query
|
||
|
*
|
||
|
* @access public
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function withExplain()
|
||
|
{
|
||
|
$this->explain = true;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set SQL query
|
||
|
*
|
||
|
* @access public
|
||
|
* @param string $sql
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function withSql($sql)
|
||
|
{
|
||
|
$this->sql = $sql;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set positional parameters
|
||
|
*
|
||
|
* @access public
|
||
|
* @param array $params
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function withPositionalParams(array $params)
|
||
|
{
|
||
|
$this->positionalParams = $params;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set named parameters
|
||
|
*
|
||
|
* @access public
|
||
|
* @param array $params
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function withNamedParams(array $params)
|
||
|
{
|
||
|
$this->namedParams = $params;
|
||
|
$this->useNamedParams = true;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Bind large object parameter
|
||
|
*
|
||
|
* @access public
|
||
|
* @param $name
|
||
|
* @param $fp
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function withLobParam($name, &$fp)
|
||
|
{
|
||
|
$this->lobParams[$name] =& $fp;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get number of queries executed
|
||
|
*
|
||
|
* @access public
|
||
|
* @return int
|
||
|
*/
|
||
|
public function getNbQueries()
|
||
|
{
|
||
|
return $this->nbQueries;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Execute a prepared statement
|
||
|
*
|
||
|
* Note: returns false on duplicate keys instead of SQLException
|
||
|
*
|
||
|
* @access public
|
||
|
* @return PDOStatement|false
|
||
|
*/
|
||
|
public function execute()
|
||
|
{
|
||
|
try {
|
||
|
$this->beforeExecute();
|
||
|
|
||
|
$pdoStatement = $this->db->getConnection()->prepare($this->sql);
|
||
|
$this->bindParams($pdoStatement);
|
||
|
$pdoStatement->execute();
|
||
|
|
||
|
$this->afterExecute();
|
||
|
return $pdoStatement;
|
||
|
} catch (PDOException $e) {
|
||
|
return $this->handleSqlError($e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Bind parameters to PDOStatement
|
||
|
*
|
||
|
* @access protected
|
||
|
* @param PDOStatement $pdoStatement
|
||
|
*/
|
||
|
protected function bindParams(PDOStatement $pdoStatement)
|
||
|
{
|
||
|
$i = 1;
|
||
|
|
||
|
foreach ($this->lobParams as $name => $variable) {
|
||
|
if (! $this->useNamedParams) {
|
||
|
$parameter = $i;
|
||
|
$i++;
|
||
|
} else {
|
||
|
$parameter = $name;
|
||
|
}
|
||
|
|
||
|
$pdoStatement->bindParam($parameter, $variable, PDO::PARAM_LOB);
|
||
|
}
|
||
|
|
||
|
foreach ($this->positionalParams as $value) {
|
||
|
$pdoStatement->bindValue($i, $value, PDO::PARAM_STR);
|
||
|
$i++;
|
||
|
}
|
||
|
|
||
|
foreach ($this->namedParams as $name => $value) {
|
||
|
$pdoStatement->bindValue($name, $value, PDO::PARAM_STR);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Method executed before query execution
|
||
|
*
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function beforeExecute()
|
||
|
{
|
||
|
if ($this->logQueries) {
|
||
|
$this->db->setLogMessage($this->sql);
|
||
|
}
|
||
|
|
||
|
if ($this->stopwatch) {
|
||
|
$this->startTime = microtime(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Method executed after query execution
|
||
|
*
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function afterExecute()
|
||
|
{
|
||
|
if ($this->stopwatch) {
|
||
|
$duration = microtime(true) - $this->startTime;
|
||
|
$this->executionTime += $duration;
|
||
|
$this->db->setLogMessage('query_duration='.$duration);
|
||
|
$this->db->setLogMessage('total_execution_time='.$this->executionTime);
|
||
|
}
|
||
|
|
||
|
if ($this->explain) {
|
||
|
$this->db->setLogMessages($this->db->getDriver()->explain($this->sql, $this->positionalParams));
|
||
|
}
|
||
|
|
||
|
$this->nbQueries++;
|
||
|
$this->cleanup();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reset internal properties after execution
|
||
|
* The same object instance is used
|
||
|
*
|
||
|
* @access protected
|
||
|
*/
|
||
|
protected function cleanup()
|
||
|
{
|
||
|
$this->sql = '';
|
||
|
$this->useNamedParams = false;
|
||
|
$this->positionalParams = array();
|
||
|
$this->namedParams = array();
|
||
|
$this->lobParams = array();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handle PDOException
|
||
|
*
|
||
|
* @access public
|
||
|
* @param PDOException $e
|
||
|
* @return bool
|
||
|
* @throws SQLException
|
||
|
*/
|
||
|
public function handleSqlError(PDOException $e)
|
||
|
{
|
||
|
$this->cleanup();
|
||
|
$this->db->cancelTransaction();
|
||
|
$this->db->setLogMessage($e->getMessage());
|
||
|
|
||
|
if ($this->db->getDriver()->isDuplicateKeyError($e->getCode())) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
throw new SQLException('SQL error'.($this->logQueries ? ': '.$e->getMessage() : ''));
|
||
|
}
|
||
|
}
|