211 lines
4.6 KiB
PHP
211 lines
4.6 KiB
PHP
|
<?php namespace ProcessWire;
|
|||
|
|
|||
|
/**
|
|||
|
* ProcessWire PDO Statement
|
|||
|
*
|
|||
|
* Serves as a wrapper to PHP’s PDOStatement class, purely for debugging purposes.
|
|||
|
* When ProcessWire is not in debug mode, this class is not used at present.
|
|||
|
*
|
|||
|
* The primary thing this class does is log queries with the bind parameters
|
|||
|
* populated into the SQL query string, purely for readability purposes. These
|
|||
|
* populated queries are not ever used for actual database queries, just for logs.
|
|||
|
*
|
|||
|
* Note that this class only tracks bindValue() and does not track bindParam().
|
|||
|
*
|
|||
|
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
|
|||
|
* https://processwire.com
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
class WireDatabasePDOStatement extends \PDOStatement {
|
|||
|
|
|||
|
/**
|
|||
|
* @var WireDatabasePDO
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $database = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Debug params in format [ ":param_name" => "param value" ]
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $debugParams = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Debug params that require PCRE, in format [ "/:param_name\b/" => "param value" ]
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $debugParamsPCRE = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Quantity of debug params
|
|||
|
*
|
|||
|
* @var int
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $debugParamsQty = 0;
|
|||
|
|
|||
|
/**
|
|||
|
* Debug note
|
|||
|
*
|
|||
|
* @var string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $debugNote = '';
|
|||
|
|
|||
|
/**
|
|||
|
* Debug mode?
|
|||
|
*
|
|||
|
* @var bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $debugMode = false;
|
|||
|
|
|||
|
/**
|
|||
|
* Construct
|
|||
|
*
|
|||
|
* PDO requires the PDOStatement constructor to be protected for some reason
|
|||
|
*
|
|||
|
* @param WireDatabasePDO $database
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function __construct(WireDatabasePDO $database) {
|
|||
|
$this->database = $database;
|
|||
|
$this->debugMode = $database->debugMode;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set debug note
|
|||
|
*
|
|||
|
* @param string $note
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setDebugNote($note) {
|
|||
|
$this->debugNote = $note;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set a named debug parameter
|
|||
|
*
|
|||
|
* @param string $parameter
|
|||
|
* @param int|string|null $value
|
|||
|
* @param int|null $data_type \PDO::PARAM_* type
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setDebugParam($parameter, $value, $data_type = null) {
|
|||
|
if($data_type === \PDO::PARAM_INT) {
|
|||
|
$value = (int) $value;
|
|||
|
} else if($data_type === \PDO::PARAM_NULL) {
|
|||
|
$value = 'NULL';
|
|||
|
} else {
|
|||
|
$value = $this->database->quote($value);
|
|||
|
}
|
|||
|
if($parameter[strlen($parameter)-1] !== 'X') {
|
|||
|
// user-specified param name: partial name collisions possible, so use boundary
|
|||
|
$this->debugParamsPCRE['/' . $parameter . '\b/'] = $value;
|
|||
|
} else {
|
|||
|
// auto-generated param name: already protected against partial name collisions
|
|||
|
$this->debugParams[$parameter] = $value;
|
|||
|
}
|
|||
|
$this->debugParamsQty++;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Bind a value for this statement
|
|||
|
*
|
|||
|
* @param string|int $parameter
|
|||
|
* @param mixed $value
|
|||
|
* @param int $data_type
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
public function bindValue($parameter, $value, $data_type = \PDO::PARAM_STR) {
|
|||
|
$result = parent::bindValue($parameter, $value, $data_type);
|
|||
|
if($this->debugMode && strpos($parameter, ':') === 0) {
|
|||
|
$this->setDebugParam($parameter, $value, $data_type);
|
|||
|
} else {
|
|||
|
// note we do not handle index/question-mark parameters for debugging
|
|||
|
}
|
|||
|
return $result;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Execute prepared statement
|
|||
|
*
|
|||
|
* @param array|null $input_parameters
|
|||
|
* @return bool
|
|||
|
* @throws \PDOException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function execute($input_parameters = NULL) {
|
|||
|
if($this->debugMode) {
|
|||
|
return $this->executeDebug($input_parameters);
|
|||
|
} else {
|
|||
|
return parent::execute($input_parameters);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Execute prepared statement when in debug mode only
|
|||
|
*
|
|||
|
* @param array|null $input_parameters
|
|||
|
* @return bool
|
|||
|
* @throws \PDOException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function executeDebug($input_parameters = NULL) {
|
|||
|
|
|||
|
$timer = Debug::startTimer();
|
|||
|
$exception = null;
|
|||
|
|
|||
|
try {
|
|||
|
$result = parent::execute($input_parameters);
|
|||
|
} catch(\PDOException $e) {
|
|||
|
$exception = $e;
|
|||
|
$result = false;
|
|||
|
}
|
|||
|
|
|||
|
$timer = Debug::stopTimer($timer, 'ms');
|
|||
|
|
|||
|
if(!$this->database) {
|
|||
|
if($exception) throw $exception;
|
|||
|
return $result;
|
|||
|
}
|
|||
|
|
|||
|
if(is_array($input_parameters)) {
|
|||
|
foreach($input_parameters as $key => $value) {
|
|||
|
if(is_string($key)) $this->setDebugParam($key, $value);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$debugNote = trim("$this->debugNote [$timer]");
|
|||
|
if($exception) $debugNote .= ' FAIL SQLSTATE[' . $exception->getCode() . ']';
|
|||
|
|
|||
|
if($this->debugParamsQty) {
|
|||
|
$sql = $this->queryString;
|
|||
|
if(count($this->debugParams)) {
|
|||
|
$sql = strtr($sql, $this->debugParams);
|
|||
|
}
|
|||
|
if(count($this->debugParamsPCRE)) {
|
|||
|
$sql = preg_replace(
|
|||
|
array_keys($this->debugParamsPCRE),
|
|||
|
array_values($this->debugParamsPCRE),
|
|||
|
$sql
|
|||
|
);
|
|||
|
}
|
|||
|
$this->database->queryLog($sql, $debugNote);
|
|||
|
} else {
|
|||
|
$this->database->queryLog($this->queryString, $debugNote);
|
|||
|
}
|
|||
|
|
|||
|
if($exception) throw $exception;
|
|||
|
|
|||
|
return $result;
|
|||
|
}
|
|||
|
|
|||
|
}
|