mirror of
https://github.com/processwire/processwire.git
synced 2025-08-13 02:04:35 +02:00
Add support for custom PDO statement class WireDatabasePDOStatement. This is used rather than PDOStatement when in debug mode, so that it can translate bind values to actual values in queries that are used in the debug mode query log.
This commit is contained in:
@@ -52,6 +52,8 @@ class WireDatabasePDO extends Wire implements WireDatabase {
|
||||
/**
|
||||
* Instance of PDO
|
||||
*
|
||||
* @var \PDO
|
||||
*
|
||||
*/
|
||||
protected $pdo = null;
|
||||
|
||||
@@ -178,6 +180,14 @@ class WireDatabasePDO extends Wire implements WireDatabase {
|
||||
$config = $this->wire('config');
|
||||
$this->stripMB4 = $config->dbStripMB4 && strtolower($config->dbEngine) != 'utf8mb4';
|
||||
$this->queryLogMax = (int) $config->dbQueryLogMax;
|
||||
if($config->debug && $this->pdo) {
|
||||
// custom PDO statement for debug mode
|
||||
$this->debugMode = true;
|
||||
$this->pdo->setAttribute(
|
||||
\PDO::ATTR_STATEMENT_CLASS,
|
||||
array(__NAMESPACE__ . "\\WireDatabasePDOStatement", array($this))
|
||||
);
|
||||
}
|
||||
$sqlModes = $config->dbSqlModes;
|
||||
if(is_array($sqlModes)) {
|
||||
// ["5.7.0" => "remove:mode1,mode2/add:mode3"]
|
||||
@@ -217,8 +227,6 @@ class WireDatabasePDO extends Wire implements WireDatabase {
|
||||
$this->pdoConfig['pass'],
|
||||
$this->pdoConfig['options']
|
||||
);
|
||||
// custom PDO statement for later maybe
|
||||
// $this->pdo->setAttribute(\PDO::ATTR_STATEMENT_CLASS,array(__NAMESPACE__.'\WireDatabasePDOStatement',array($this)));
|
||||
}
|
||||
if(!$this->init) $this->_init();
|
||||
return $this->pdo;
|
||||
@@ -437,8 +445,16 @@ class WireDatabasePDO extends Wire implements WireDatabase {
|
||||
$note = $driver_options;
|
||||
$driver_options = array();
|
||||
}
|
||||
if($this->debugMode) $this->queryLog($statement, $note);
|
||||
return $this->pdo()->prepare($statement, $driver_options);
|
||||
$pdoStatement = $this->pdo()->prepare($statement, $driver_options);
|
||||
if($this->debugMode) {
|
||||
if($pdoStatement instanceof WireDatabasePDOStatement) {
|
||||
/** @var WireDatabasePDOStatement $pdoStatement */
|
||||
$pdoStatement->setDebugNote($note);
|
||||
} else {
|
||||
$this->queryLog($statement, $note);
|
||||
}
|
||||
}
|
||||
return $pdoStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,26 +583,25 @@ class WireDatabasePDO extends Wire implements WireDatabase {
|
||||
*
|
||||
* @param string $sql Query (string) to log
|
||||
* @param string $note Any additional debugging notes about the query
|
||||
* @return array|bool Returns query log array, or boolean true if you've logged a query
|
||||
* @return array|bool|int Returns query log array, boolean true if added, boolean false if not
|
||||
*
|
||||
*/
|
||||
public function queryLog($sql = '', $note = '') {
|
||||
if(empty($sql)) return $this->queryLog;
|
||||
if($this->debugMode) {
|
||||
if(count($this->queryLog) > $this->queryLogMax) {
|
||||
if(isset($this->queryLog['error'])) {
|
||||
$qty = (int) $this->queryLog['error'];
|
||||
} else {
|
||||
$qty = 0;
|
||||
}
|
||||
$qty++;
|
||||
$this->queryLog['error'] = "$qty additional queries omitted because \$config->dbQueryLogMax = $this->queryLogMax";
|
||||
if(!$this->debugMode) return false;
|
||||
if(count($this->queryLog) > $this->queryLogMax) {
|
||||
if(isset($this->queryLog['error'])) {
|
||||
$qty = (int) $this->queryLog['error'];
|
||||
} else {
|
||||
$this->queryLog[] = $sql . ($note ? " -- $note" : "");
|
||||
return true;
|
||||
$qty = 0;
|
||||
}
|
||||
$qty++;
|
||||
$this->queryLog['error'] = "$qty additional queries omitted because \$config->dbQueryLogMax = $this->queryLogMax";
|
||||
return false;
|
||||
} else {
|
||||
$this->queryLog[] = $sql . ($note ? " -- $note" : "");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1002,14 +1017,3 @@ class WireDatabasePDO extends Wire implements WireDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* custom PDOStatement for later maybe
|
||||
*
|
||||
class WireDatabasePDOStatement extends \PDOStatement {
|
||||
protected $database;
|
||||
protected function __construct(WireDatabasePDO $database) {
|
||||
$this->database = $database;
|
||||
// $database->message($this->queryString);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
174
wire/core/WireDatabasePDOStatement.php
Normal file
174
wire/core/WireDatabasePDOStatement.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?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 = '';
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* PDO requires the PDOStatement constructor to be protected for some reason
|
||||
*
|
||||
* @param WireDatabasePDO $database
|
||||
*
|
||||
*/
|
||||
protected function __construct(WireDatabasePDO $database) {
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(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
|
||||
*
|
||||
*/
|
||||
public function execute($input_parameters = NULL) {
|
||||
|
||||
$timer = Debug::startTimer();
|
||||
$result = parent::execute($input_parameters);
|
||||
$timer = Debug::stopTimer($timer, 'ms');
|
||||
|
||||
if(!$this->database) return $result;
|
||||
|
||||
if(is_array($input_parameters)) {
|
||||
foreach($input_parameters as $key => $value) {
|
||||
$this->setDebugParam($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$debugNote = trim("$this->debugNote [$timer]");
|
||||
|
||||
if($this->debugParamsQty) {
|
||||
$sql = $this->queryString;
|
||||
if(count($this->debugParams)) {
|
||||
$sql = str_replace(
|
||||
array_keys($this->debugParams),
|
||||
array_values($this->debugParams),
|
||||
$sql
|
||||
);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user