Файл: system/core/database.php
Строк: 723
<?php
class cmsDatabase {
private static $instance;
/**
* Префикс таблиц
* @var string
*/
public $prefix;
/**
* deprecated, use cmsDebugging
*/
public $query_count = 0;
public $query_list = array();
/**
* Массив кешированного списка полей для запрашиваемых таблиц
* @var array
*/
private $table_fields = array();
/**
* Объект, представляющий подключение к серверу MySQL
* @var mysqli
*/
private $mysqli;
/**
* Время соединения с базой
* @var integer
*/
private $init_start_time;
/**
* Время, через которое при PHP_SAPI == 'cli' нужно сделать реконнект
* для случаев, когда mysql.connect_timeout по дефолту (60 с) и переопределить это поведение нельзя
* @var integer
*/
private $reconnect_time = 60;
/**
* Ошибка подключения к базе
* @var boolean|string
*/
private $connect_error = false;
/**
*
* @var boolean|null
*/
public $query_quiet = null;
/**
* Настройки базы данных
* @var array
*/
private $options = [
'db_charset' => 'utf8'
];
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self;
}
return self::$instance;
}
//============================================================================//
//============================================================================//
public function __construct($options = array()){
$this->setOptions($options ? $options : cmsConfig::getInstance()->getAll());
$this->connect();
}
public function __destruct(){
if($this->ready()){
// откатываемся, если была транзакция и ошибка в ней
if(!$this->isAutocommitOn() && $this->mysqli->errno){
$this->rollback();
}
$this->mysqli->close();
}
}
/**
* Устанавливает массив опций для работы с БД
* @param array $options
*/
public function setOptions($options) {
$this->options = array_merge($this->options, $options);
}
/**
* Устанавливает опцию по ключу
* @param string $key Ключ опции
* @param mixed $value Значение
*/
public function setOption($key, $value) {
$this->options[$key] = $value;
}
public function __get($name) {
if ($name == 'nestedSets') {
$this->nestedSets = new cmsNestedsets($this);
return $this->nestedSets;
}
}
private function connect() {
if (!empty($this->options['debug'])){
cmsDebugging::pointStart('db');
}
mysqli_report(MYSQLI_REPORT_STRICT);
try {
$this->mysqli = new mysqli($this->options['db_host'], $this->options['db_user'], $this->options['db_pass'], $this->options['db_base']);
} catch (Exception $e) {
$this->connect_error = $e->getMessage();
return false;
}
$this->mysqli->set_charset($this->options['db_charset']);
if(!empty($this->options['clear_sql_mode'])){
$this->mysqli->query("SET sql_mode=''");
}
// Устанавливаем ключ шифрования, если он задан в конфиге
if (!empty($this->options['aes_key'])){
$key = $this->mysqli->real_escape_string($this->options['aes_key']);
$this->mysqli->query("SELECT @aeskey:='{$key}'");
}
if (!empty($this->options['debug'])){
cmsDebugging::pointProcess('db', array(
'data' => 'Database connection'
), 3);
}
$this->setTimezone();
$this->prefix = $this->options['db_prefix'];
$this->init_start_time = time();
return true;
}
public function ready(){
return $this->connect_error === false;
}
public function connectError(){
return $this->connect_error;
}
public function reconnect($is_force = false){
if ($is_force || !$this->mysqli->ping()){
$this->mysqli->close();
return $this->connect();
}
return true;
}
public function getStat(){
if (isset($this->mysqli->stat)){
return $this->mysqli->stat;
}
return '';
}
public function setTimezone(){
$this->query("SET `time_zone` = '%s'", date('P')); return $this;
}
public function setLcMessages(){
if(defined('LC_LANGUAGE_TERRITORY')){
$this->mysqli->query("SET lc_messages = '".LC_LANGUAGE_TERRITORY."'");
}
return $this;
}
//============================================================================//
//============================================================================//
public function autocommitOn() {
$this->mysqli->autocommit(true); return $this;
}
public function autocommitOff() {
$this->mysqli->autocommit(false); return $this;
}
public function isAutocommitOn() {
$result = $this->mysqli->query('SELECT @@autocommit');
if($result){
$row = $result->fetch_row();
$result->free();
return isset($row[0]) && $row[0] == 1;
}
return false;
}
public function rollback() {
$this->mysqli->rollback(); return $this;
}
public function commit() {
$this->mysqli->commit(); return $this;
}
public function beginTransaction() {
$this->mysqli->begin_transaction(); return $this;
}
//============================================================================//
//============================================================================//
/**
* Подготавливает строку перед запросом
*
* @param string $string
* @return string
*/
public function escape($string){
return $this->mysqli->real_escape_string($string);
}
/**
* Формирует префиксы таблиц в SQL запросе
* @param string $sql
* @return string
*/
public function replacePrefix($sql) {
return str_replace([
'{#}{users}', '{users}', '{#}'
], [
$this->options['db_users_table'], $this->options['db_users_table'], $this->prefix
], $sql);
}
/**
* Выполняет запрос в базе
* @param string $sql Строка запроса
* @param array|string $params Аргументы запроса, которые будут переданы в vsprintf
* @param boolean $quiet В случае ошибки запроса отдавать false, а не "умирать"
* @return boolean
*/
public function query($sql, $params = false, $quiet = false){
if(!$this->ready()){
return false;
}
if (!empty($this->options['debug'])){
cmsDebugging::pointStart('db');
}
$sql = $this->replacePrefix($sql);
if ($params){
if (!is_array($params)){
$params = array($params);
}
foreach($params as $index=>$param){
if (!is_numeric($param)){
$params[$index] = $this->escape($param);
}
}
$sql = vsprintf($sql, $params);
}
if(PHP_SAPI == 'cli' && (time() - $this->init_start_time) >= $this->reconnect_time){
$this->reconnect();
}
$result = $this->mysqli->query($sql);
if (!empty($this->options['debug'])){
cmsDebugging::pointProcess('db', array(
'data' => $sql
));
}
if(!$this->mysqli->errno) { return $result; }
if($quiet || $this->query_quiet === true) {
error_log(sprintf(ERR_DATABASE_QUERY, $this->error()));
return false;
}
cmsCore::error(sprintf(ERR_DATABASE_QUERY, $this->error()), $sql);
}
//============================================================================//
//============================================================================//
public function freeResult($result){
$result->close();
}
public function affectedRows(){
return $this->mysqli->affected_rows;
}
public function numRows($result){
if(!$result){
return 0;
}
return $result->num_rows;
}
public function fetchAssoc($result) {
return $result->fetch_assoc();
}
public function fetchRow($result) {
return $result->fetch_row();
}
public function error() {
return $this->mysqli->error;
}
//============================================================================//
//============================================================================//
/**
* Возвращает ID последней вставленной записи из таблицы
* При работе с транзакциями вызывать необходимо
* До коммита
*
* @return integer
*/
public function lastId(){
return $this->mysqli->insert_id;
}
/**
* Возвращает все названия полей для таблицы
* @param string $table
* @return array
*/
public function getTableFields($table) {
if(isset($this->table_fields[$table])){
return $this->table_fields[$table];
}
$result = $this->query("SHOW COLUMNS FROM `{#}{$table}`");
$fields = array();
while($data = $this->fetchAssoc($result)){
$fields[] = $data['Field'];
}
$this->table_fields[$table] = $fields;
return $fields;
}
/**
* Возвращает названия полей и их типы для таблицы
* @param string $table
* @return array
*/
public function getTableFieldsTypes($table) {
$result = $this->query("SELECT DATA_TYPE, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = '{#}{$table}'");
$fields = [];
while($data = $this->fetchAssoc($result)){
$fields[$data['COLUMN_NAME']] = $data['DATA_TYPE'];
}
return $fields;
}
//============================================================================//
//============================================================================//
/**
* Подготавливает значение $value поля $field для вставки в запрос
* @param string $field
* @param string $value
* @param boolean $array_as_json Переходная опция для миграции с Yaml на Json
* @return string
*/
public function prepareValue($field, $value, $array_as_json = false){
$is_date_field = strpos($field, 'date_') === 0;
// если значение поля - массив,
// то преобразуем его в YAML
if (is_array($value)){
if($array_as_json){
$value = "'". $this->escape(cmsModel::arrayToString($value)) ."'";
} else {
$value = "'". $this->escape(cmsModel::arrayToYaml($value)) ."'";
}
} else
// если это поле даты и оно не установлено,
// то используем текущее время
if ($is_date_field && ($value === false)) { $value = 'NULL'; } else
if ($is_date_field && ($value == '' || is_null($value))) { $value = 'CURRENT_TIMESTAMP'; } else
// если это поле булево,
// то преобразуем его в число
if (is_bool($value)) { $value = (int)$value; } else
// если значение поля не задано,
// то запишем в базу NULL
if ($value === '' || is_null($value)) { $value = 'NULL'; } else
// если значение поля как результат функции
if (is_callable($value) && ($value instanceof Closure)) { $value = $value($this); }
else {
// Убираем только пробелы и NUL-байт
$value = $this->escape(trim($value, "