artabro/wire/core/Database.php
2024-08-27 11:35:37 +02:00

270 lines
6.7 KiB
PHP

<?php namespace ProcessWire;
/**
* ProcessWire MySQLi Database
*
* Serves as a wrapper to PHP's mysqli classes
*
* This file is licensed under the MIT license
* https://processwire.com/about/license/mit/
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* https://processwire.com
*
*
*/
/**
* Database class provides a layer on top of mysqli
*
*/
class Database extends \mysqli implements WireDatabase {
/**
* Log of all queries performed in this instance
*
*/
protected $queryLog = array();
/**
* Should WireDatabaseException be thrown on error?
*
*/
protected $throwExceptions = true;
/**
* @var ProcessWire
*
*/
protected $wire;
/**
* @var bool
*
*/
protected $debug = false;
/**
* Construct the Database
*
* Since this extends MySQL all the MySQL construct params are kept in tact.
* However, you may just supply an object with the following properties if you prefer:
* $o->dbUser, $o->dbPass, $o->dbHost, $o->dbName, $config->dbPort, $config->dbSocket (optional).
* This would usually be from a ProcessWire Config ($config) API var, but kept as generic object
* in case someone wants to use this class elsewhere.
*
* @param string|Config $host Hostname or object with config properties.
* @param string $user Username
* @param string $pass Password
* @param string $db Database name
* @param int $port Port
* @param string $socket Socket
* @throws WireDatabaseException
*
*/
public function __construct($host = 'localhost', $user = null, $pass = null, $db = null, $port = null, $socket = null) {
if(is_object($host) && $host->dbHost) {
$config = $host;
$this->debug = $config->debug;
$host = $config->dbHost;
$user = $config->dbUser;
$pass = $config->dbPass;
$db = $config->dbName;
$port = $config->dbPort;
$socket = $config->dbSocket ? $config->dbSocket : null;
} else $config = null;
@parent::__construct($host, $user, $pass, $db, $port, $socket);
if(mysqli_connect_error()) throw new WireDatabaseException("DB connect error " . mysqli_connect_errno() . ' - ' . mysqli_connect_error());
if($config) {
if($config->dbCharset) $this->set_charset($config->dbCharset);
else if($config->dbSetNamesUTF8) $this->query("SET NAMES 'utf8'");
}
}
/**
* Overrides default mysqli query method so that it also records and times queries.
*
* @param string $sql SQL Query
* @param int $resultmode See http://www.php.net/manual/en/mysqli.query.php
* @return mixed Returns FALSE on failure.
* For successful SELECT, SHOW, DESCRIBE or EXPLAIN queries mysqli_query() will return a MySQLi_Result object.
* For other successful queries mysqli_query() will return TRUE.
* @throws WireDatabaseException
*
*/
#[\ReturnTypeWillChange]
public function query($sql, $resultmode = MYSQLI_STORE_RESULT) {
static $timerTotalQueryTime = 0;
static $timerFirstStartTime = 0;
if(is_object($sql) && $sql instanceof DatabaseQuery) $sql = $sql->getQuery();
if($this->debug) {
$timerKey = Debug::timer();
if(!$timerFirstStartTime) $timerFirstStartTime = (float) $timerKey;
} else $timerKey = null;
$result = @parent::query($sql, $resultmode);
if($result) {
if($this->debug) {
if(isset($result->num_rows)) $sql .= " [" . $result->num_rows . " rows]";
if(!is_null($timerKey)) {
$elapsed = (float) Debug::timer($timerKey);
$timerTotalQueryTime += $elapsed;
$timerTotalSinceStart = ((float) Debug::timer()) - $timerFirstStartTime;
$sql .= " [{$elapsed}s, {$timerTotalQueryTime}s, {$timerTotalSinceStart}s]";
}
$this->queryLog($sql);
}
} else if($this->throwExceptions) {
throw new WireDatabaseException($this->error . ($this->debug ? "\n$sql" : ''));
}
return $result;
}
/**
* Get an array of all queries that have been executed thus far
*
* Active in ProcessWire debug mode only
*
* @param ProcessWire $wire ProcessWire instance, if omitted returns queries for all instances
* @return array
* @deprecated
*
*/
static public function getQueryLog(ProcessWire $wire = null) {
if($wire) {
return $wire->database->queryLog();
} else {
$log = array();
foreach(ProcessWire::getInstances() as $wire) {
$log = array_merge($log, $wire->database->queryLog());
}
}
return $log;
}
/**
* Log a query or return the query log
*
* @param string $sql Omit to instead return the query log
* @return array|bool Returns query log array when $sql argument is omitted
*
*/
public function queryLog($sql = '') {
if($sql) {
$this->queryLog[] = $sql;
return true;
} else {
return $this->queryLog;
}
}
/**
* Get array of all tables in this database.
*
* @return array
*
*/
public function getTables() {
static $tables = array();
if(!count($tables)) {
$result = $this->query("SHOW TABLES");
while($row = $result->fetch_array()) $tables[] = current($row);
}
return $tables;
}
/**
* Is the given string a database comparison operator?
*
* @param string $str 1-2 character opreator to test
* @return bool
*
*/
public function isOperator($str) {
return in_array($str, array('=', '<', '>', '>=', '<=', '<>', '!=', '&', '~', '|', '^', '<<', '>>'));
}
/**
* Set whether Exceptions should be thrown on query errors
*
* @param bool $throwExceptions Default is true
*
*/
public function setThrowExceptions($throwExceptions = true) {
$this->throwExceptions = $throwExceptions;
}
/**
* Sanitize a table name for _a-zA-Z0-9
*
* @param string $table
* @return string
*
*/
public function escapeTable($table) {
$table = (string) trim($table);
if(ctype_alnum($table)) return $table;
if(ctype_alnum(str_replace('_', '', $table))) return $table;
return preg_replace('/[^_a-zA-Z0-9]/', '_', $table);
}
/**
* Sanitize a column name for _a-zA-Z0-9
*
* @param string $col
* @return string
*
*/
public function escapeCol($col) {
return $this->escapeTable($col);
}
/**
* Sanitize a table.column string, where either part is optional
*
* @param string $str
* @return string
*
*/
public function escapeTableCol($str) {
if(strpos($str, '.') === false) return $this->escapeTable($str);
list($table, $col) = explode('.', $str);
return $this->escapeTable($table) . '.' . $this->escapeCol($col);
}
/**
* Escape a string value, camelCase alias of escape_string()
*
* @param string $str
* @return string
*
*/
public function escapeStr($str) {
return $this->escape_string($str);
}
/**
* Escape a string value, plus escape characters necessary for a MySQL 'LIKE' phrase
*
* @param string $like
* @return string
*
*/
public function escapeLike($like) {
$like = $this->escape_string($like);
return addcslashes($like, '%_');
}
}