Файл database.php в Cotonti
Файл database.php в системной папке Cotonti CMF отвечает за работу с базой данных, включая выполнение запросов и подготовку данных для их обработки через интерфейс PDO.
Файл database.php в системной папке Cotonti CMF отвечает за работу с базой данных, включая выполнение запросов и подготовку данных для их обработки через интерфейс PDO. Основное назначение этого кода — обеспечить гибкую, безопасную и удобную работу с базой данных с использованием различных методов для взаимодействия с таблицами и колонками, управления транзакциями, выполнения сложных SQL-операций и работы с параметрами запросов.
Основное назначение и суть:
-
Подготовка данных для запросов: Присутствуют методы для подготовки строк, таблиц и колонок к безопасному использованию в SQL-запросах. Это включает в себя методы для экранирования значений, цитирования имен таблиц и колонок, а также обработки условий запросов.
-
Выполнение запросов: Код содержит методы для выполнения различных типов запросов, включая
SELECT,INSERT,UPDATE, иDELETE, а также поддержку сложных SQL-скриптов, которые могут содержать несколько запросов. -
Транзакции: Для выполнения запросов в транзакциях предусмотрены методы для начала, завершения или отката транзакций.
-
Гибкость с префиксами и схемами: Методология работы с таблицами и колонками позволяет учитывать префиксы и схемы для таблиц, что удобно при работе с различными конфигурациями базы данных.
-
Ошибка и обработка исключений: Обработка ошибок и исключений в запросах с информативными сообщениями о возникших проблемах.
Характеристика стиля PHP:
-
Объектно-ориентированный стиль (OOP): Код написан с использованием объектно-ориентированного подхода, с четким использованием классов, методов и свойств. Примером являются методы с модификаторами доступа
public,protectedиprivate, а также использование исключений черезthrowи обработку ошибок. -
Пользовательские методы для работы с PDO: Использование методов для работы с PDO (например,
prepare(),quote(),exec()и других) демонстрирует строгую работу с базой данных через обертку, чтобы обеспечить безопасность запросов, предотвратить SQL-инъекции и обеспечить гибкость в написании запросов. -
Безопасность и экранирование: Множество функций, таких как
quote(),quoteColumnName(),quoteTableName(), направлены на предотвращение SQL-инъекций путем экранирования данных перед их вставкой в запросы. -
Использование регулярных выражений: Для обработки строки SQL кода применяются регулярные выражения, что позволяет динамически обрабатывать различные шаблоны запросов и выполнять замену префиксов таблиц и колонок.
-
Управление транзакциями: Реализована поддержка транзакций с методами для начала, коммита и отката транзакций, что позволяет надежно работать с несколькими запросами, обрабатываемыми как единая операция.
Вывод:
Код представляет собой мощный инструмент для работы с базой данных, который позволяет безопасно и гибко управлять запросами и взаимодействием с базой данных. Это ключевой компонент, который отвечает за выполнение запросов, обработку ошибок и управление транзакциями в рамках системы Cotonti. Стиль написания придерживается объектно-ориентированного подхода с фокусом на безопасность и производительность работы с базой данных.
/**
* PDO-based database layer
*
* @see http://www.php.net/manual/en/book.pdo.php
*
* @package API - Database
* @copyright (c) Cotonti Team
* @license https://github.com/Cotonti/Cotonti/blob/master/License.txt
*/
// Определение области подключения к базе данных через PDO
// Ссылка на документацию PHP по работе с PDO
// Указание на пакет, к которому относится данный код - API для работы с базой данных
use cot\database\traits\DetectLostConnectionTrait; // Импорт трейта для обработки потерянного соединения
use cot\database\traits\TransactionTrait; // Импорт трейта для работы с транзакциями
defined('COT_CODE') or die('Wrong URL'); // Проверка безопасности: если COT_CODE не определено, выполнение прерывается
/**
* Cotonti Database Connection class.
* A compact extension to standard PHP PDO class with slight Cotonti-specific needs,
* handy functions and query builder.
*
* @see http://www.php.net/manual/en/class.pdo.php
*
* @property-read string $auth 'cot_auth' table name
* @property-read string $cache 'cot_cache' table name
* @property-read string $cache_bindings 'cot_cache_bindings' table name
* @property-read string $core 'cot_core' table name
* @property-read string $config 'cot_config' table name
* @property-read string $extra_fields 'cot_extra_fields' table name
* @property-read string $groups 'cot_groups' table name
* @property-read string $groups_users 'cot_groups_users' table name
* @property-read string $logger 'cot_logger' table name
* @property-read string $online 'cot_online' table name
* @property-read string $plugins 'cot_plugins' table name
* @property-read string $server_events 'cot_server_events' table name
* @property-read string $server_events_observers 'cot_server_events_observers' table name
* @property-read string $structure 'cot_structure' table name
* @property-read string $updates 'cot_updates' table name
* @property-read string $users 'cot_users' table name
*
* @property-read int $affectedRows Number of rows affected by the most recent query
* @property-read int $count Total query count
* @property-read float $timeCount Total query execution time
*/
// Описание класса соединения с базой данных Cotonti, расширяющего стандартный класс PDO с дополнительными функциями и строителем запросов
// Перечисление таблиц базы данных, которые будут использоваться в рамках данного класса. Каждая переменная представляет собой имя таблицы в базе данных Cotonti.
// Включены таблицы для хранения данных пользователей, настроек, кэша, логов и других системных данных.
// Переменные с аннотацией @property указывают на доступ к таблицам базы данных через соответствующие свойства класса
// Переменные с аннотацией @property-read дают информацию о том, что доступ к ним только для чтения.
// Также описывается количество строк, затронутых последним запросом, общее количество запросов и общее время выполнения запросов.
"class CotDB" означает определение нового класса в языке PHP с именем CotDB. Классы в PHP — это шаблоны для создания объектов. Класс может содержать свойства (переменные) и методы (функции), которые определяют поведение объектов этого класса.
В данном случае класс CotDB представляет собой абстракцию для работы с базой данных, с использованием библиотеки PDO для выполнения SQL-запросов.
class CotDB
{
use DetectLostConnectionTrait; // Подключение трейта для обнаружения потери соединения с базой данных.
use TransactionTrait; // Подключение трейта для работы с транзакциями в базе данных.
protected $tableQuoteCharacter = '`'; // Символ, используемый для обрамления имен таблиц в запросах (по умолчанию `).
protected $columnQuoteCharacter = '`'; // Символ, используемый для обрамления имен столбцов в запросах (по умолчанию `).
/**
* Соединение с базой данных с использованием PDO.
* @var PDO
*/
protected $adapter; // Хранит экземпляр PDO, который представляет соединение с базой данных.
/**
* Параметры конфигурации подключения к базе данных.
* @var array{
* adapter: string, // Тип адаптера для подключения к базе данных (например, 'mysql').
* host: string, // Хост базы данных (например, 'localhost').
* port?: int, // Порт для подключения (опционально).
* tablePrefix?: string, // Префикс для имен таблиц (опционально).
* user: string, // Имя пользователя для подключения к базе данных.
* password: string, // Пароль для подключения к базе данных.
* dbName?: string, // Имя базы данных (опционально).
* charset?: string, // Кодировка для базы данных (опционально).
* collate?: string, // Сортировка для базы данных (опционально).
* options?: array<int, string> // Дополнительные параметры для PDO-соединения (опционально).
* }
*/
protected $config; // Массив с параметрами конфигурации подключения к базе данных.
}
/** * @var int Режим выборки по умолчанию для этого соединения. * В Cotonti мы используем PDO::FETCH_ASSOC по умолчанию для экономии памяти. * @see https://www.php.net/manual/en/pdostatement.setfetchmode.php */ //public $fetchMode = \PDO::FETCH_ASSOC; /** * Префикс таблицы для соединения. * @var string */ protected $tablePrefix = ''; /** * Количество строк, затронутых последним запросом. * @var int */ private $_affected_rows = 0; /** * Общее количество запросов. * @var int */ private $_count = 0; /** * Подготовка запросов самостоятельно. Используется для версий API клиента MySQL до 5.1. * @var bool */ private $_prepare_itself = false; /** * Общее время выполнения запроса в микросекундах в виде строки. * @var float */ private $_tcount = .0; /** * Время старта таймера в микросекундах. * @var string */ private $_xtime = '0'; /** * Регистратор имен таблиц. * @var array */ private $_tables = []; /** * Используется только в runScript(). * @var string */ public $error = '';
/**
* Создаёт экземпляр PDO для подключения к запрашиваемой базе данных.
*
* @param array{
* adapter: string, // Тип базы данных
* host: string, // Хост базы данных
* port?: int, // (необязательный) Порт подключения
* tablePrefix?: string, // (необязательный) Префикс таблицы
* user: string, // Имя пользователя для подключения
* password: string, // Пароль для подключения
* dbName?: string, // (необязательный) Имя базы данных
* charset?: string, // (необязательный) Кодировка
* collate?: string, // (необязательный) Сопоставление
* options?: array<int, string> // (необязательный) Массив опций для драйвера подключения
* } $config Массив параметров, необходимых для подключения к базе данных (DSN).
*/
public function __construct($config)
{
$this->config = $config; // Сохраняем параметры конфигурации в свойстве объекта
if (!empty($config['tablePrefix'])) { // Если указан префикс таблицы в конфигурации
$this->tablePrefix = $config['tablePrefix']; // Присваиваем его свойству объекта
}
$this->adapter = static::connect(); // Создаём соединение с базой данных с помощью метода connect
}
/**
* Подключение к базе данных
* @return PDO Возвращает объект PDO для работы с базой данных.
*
* @see http://www.php.net/manual/en/pdo.construct.php
* @todo Когда будет реализовано несколько подключений к базам данных, использовать реестр подключений.
*/
protected function connect(): PDO
{
if ($this->adapter !== null) { // Если подключение уже существует, возвращаем его
return $this->adapter; // Возвращаем уже существующий объект PDO
}
if (!empty($this->config['charset'])) { // Если указана кодировка
$collation_query = "SET NAMES '{$this->config['charset']}'"; // Формируем запрос на установку кодировки
if (!empty($this->config['collate'])) { // Если указано сопоставление
$collation_query .= " COLLATE '{$this->config['collate']}'"; // Добавляем его в запрос
}
$this->config['options'][PDO::MYSQL_ATTR_INIT_COMMAND] = $collation_query; // Устанавливаем команду инициализации в конфигурацию
}
$this->config['adapter'] = !empty($this->config['adapter']) ? $this->config['adapter'] : 'mysql'; // Если не указан адаптер, по умолчанию используем 'mysql'
$port = empty($this->config['port']) ? '' : ';port=' . $this->config['port']; // Если указан порт, добавляем его в строку подключения
$dsn = $this->config['adapter'] . ':host=' . $this->config['host'] . $port; // Формируем строку подключения
if (!empty($this->config['dbName'])) { // Если указано имя базы данных
$dsn .= ';dbname=' . $this->config['dbName']; // Добавляем имя базы данных в строку подключения
}
$this->config['options'] = !empty($this->config['options']) ? $this->config['options'] : null; // Если есть опции подключения, используем их, иначе оставляем null
$adapter = new PDO($dsn, $this->config['user'], $this->config['password'], $this->config['options']); // Создаём объект PDO для подключения к базе данных
$adapter->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Устанавливаем режим обработки ошибок - исключения
if (!method_exists($adapter, 'prepare')) { // Если у адаптера нет метода prepare
$this->_prepare_itself = true; // Устанавливаем флаг, что подготовка запросов будет выполняться вручную
}
return $adapter; // Возвращаем объект PDO
}
/**
* Повторное подключение
*/
public function reconnect(): void
{
$this->adapter = null; // Обнуляем текущее подключение
$this->connect(); // Создаём новое подключение
}
/**
* Получение конфигурации подключения.
* @return array{
* adapter: string, // Тип базы данных (например, 'mysql')
* host: string, // Хост базы данных
* port?: int, // Порт для подключения (необязательный)
* tablePrefix?: string, // Префикс таблиц (необязательный)
* user: string, // Имя пользователя для подключения
* password: string, // Пароль для подключения
* dbName?: string, // Имя базы данных (необязательное)
* charset?: string, // Кодировка для подключения (необязательная)
* collate?: string, // Сопоставление для подключения (необязательное)
* options?: array<int, string> // Опции подключения (необязательные)
* }
*/
public function getConfig()
{
return $this->config; // Возвращаем конфигурацию подключения
}
/**
* Получение объекта подключения PDO.
* @return PDO
*/
public function getConnection()
{
return $this->adapter; // Возвращаем объект PDO, который представляет подключение к базе данных
}
/**
* Предоставляет доступ к свойствам.
* @param string $name Имя свойства
* @return mixed Значение свойства
*/
public function __get($name)
{
switch ($name)
{
case 'affectedRows': // Если запрашивается свойство 'affectedRows'
return $this->_affected_rows; // Возвращаем количество затронутых строк
break;
case 'count': // Если запрашивается свойство 'count'
return $this->_count; // Возвращаем общее количество запросов
break;
case 'timeCount': // Если запрашивается свойство 'timeCount'
return $this->_tcount; // Возвращаем общее время выполнения запросов
break;
default:
return isset($this->_tables[$name]) ? $this->_tables[$name] : null; // Если свойство не найдено, пытаемся вернуть значение из массива _tables, если оно существует, иначе возвращаем null
}
}
/**
* Привязывает параметры к запросу
*
* @param PDOStatement $statement PDO-запрос
* @param array $parameters Массив параметров, числовой или ассоциативный
*/
private function _bindParams($statement, $parameters)
{
$is_numeric = is_int(key($parameters)); // Проверяем, является ли ключ первого элемента массива параметров числовым (для числового массива)
foreach ($parameters as $key => $val) { // Итерируем по всем параметрам
$type = is_int($val) ? PDO::PARAM_INT : PDO::PARAM_STR; // Определяем тип параметра: если значение целое, то PDO::PARAM_INT, иначе PDO::PARAM_STR
$is_numeric ? $statement->bindValue($key + 1, $val, $type) : $statement->bindValue($key, $val, $type); // В зависимости от того, числовой ли массив параметров, привязываем значения с нужным индексом
}
}
/**
* Разбирает сообщение исключения PDO и возвращает его компоненты и статус
*
* @param PDOException $e Исключение PDO
* @param string $err_code Параметр для вывода кода ошибки
* @param string $err_message Параметр для вывода сообщения ошибки
* @return bool TRUE для ошибок, FALSE для уведомлений и предупреждений
*/
private function _parseError(PDOException $e, &$err_code, &$err_message)
{
$pdo_message = $e->getMessage(); // Получаем сообщение исключения PDO
if (preg_match('#SQLSTATE\[(\w+)\].*?: (.*)#', $pdo_message, $matches)) { // Используем регулярное выражение для извлечения кода и сообщения ошибки из строки
$err_code = $matches[1]; // Извлекаем код ошибки
$err_message = $matches[2]; // Извлекаем сообщение ошибки
} else {
$err_code = $e->getCode(); // Если регулярное выражение не сработало, просто берем код ошибки
$err_message = $pdo_message; // И сообщение ошибки
}
return $err_code > '02'; // Возвращаем TRUE, если код ошибки больше '02' (что означает ошибку), иначе FALSE (для уведомлений или предупреждений)
}
/**
* Подготавливает параметризованный запрос на стороне клиента
*
* @param string $query Запрос, который подготавливается
* @param array $parameters Ассоциативный или числовой массив параметров
* @return string Массив с подставленными плейсхолдерами
*/
private function _prepare($query, $parameters = array())
{
if (count($parameters) > 0) { // Проверяем, есть ли параметры
foreach ($parameters as $key => $val) { // Перебираем все параметры
$placeholder = is_int($key) ? '?' : ':' . $key; // Определяем, какой плейсхолдер использовать (числовой или ассоциативный)
$value = is_int($val) ? $val : $this->quote($val); // Если значение параметра - целое число, то оставляем его, если строка - экранируем
$pos = strpos($query, $placeholder); // Находим позицию плейсхолдера в запросе
if ($pos !== false) { // Если плейсхолдер найден
$query = substr_replace($query, $value, $pos, strlen($placeholder)); // Заменяем плейсхолдер на значение
}
}
}
return $query; // Возвращаем подготовленный запрос
}
/**
* Запускает таймер для выполнения запроса
*/
private function _startTimer()
{
// если конфигурация еще не загружена, сохраняем статистику на всякий случай
$showStats = !isset(Cot::$cfg['showsqlstats']) || Cot::$cfg['showsqlstats']; // Проверяем, нужно ли показывать статистику
$this->_count++; // Увеличиваем счетчик запросов
if ($showStats || Cot::$cfg['debug_mode']) { // Если включена статистика или режим отладки
$this->_xtime = microtime(); // Записываем текущее время в микросекундах
}
}
/**
* Останавливает таймер для выполнения запроса
*/
private function _stopTimer($query)
{
// если конфигурация еще не загружена, сохраняем статистику на всякий случай
$showStats = !isset(Cot::$cfg['showsqlstats']) || Cot::$cfg['showsqlstats']; // Проверяем, нужно ли показывать статистику
$devMode = !isset(Cot::$cfg['devmode']) || Cot::$cfg['devmode']; // Проверяем, включен ли режим разработки
if ($showStats || Cot::$cfg['debug_mode']) { // Если включена статистика или режим отладки
$xtime = explode(' ', $this->_xtime); // Разделяем стартовое время
$ytime = explode(' ', microtime()); // Получаем текущее время
$startTime = $xtime[1] + $xtime[0]; // Время начала выполнения
$stopTime = $ytime[1] + $ytime[0]; // Время окончания выполнения
$executionTime = $stopTime - $startTime; // Вычисляем время выполнения
$this->_tcount += $executionTime; // Добавляем время выполнения к общему времени
if ($devMode || Cot::$cfg['debug_mode']) { // Если включен режим разработки или отладки
$calls = ''; // Переменная для хранения вызовов функций
$bt = debug_backtrace(); // Получаем стек вызовов
for ($i = sizeof($bt) - 1; $i > 0; $i--) { // Проходим по стеку вызовов
$object = !empty($bt[$i]['object']); // Проверяем, есть ли объект
$call = (($object && $bt[$i]['class']) ? $bt[$i]['class'] . $bt[$i]['type'] : '') // Формируем строку вызова
. $bt[$i]['function'] . '();'; // Добавляем название функции
$calls .= (empty($calls) ? '' : "\n → ") // Добавляем в строку вызовов
. (!empty($bt[$i]['file']) ? basename($bt[$i]['file']) : '-') // Добавляем имя файла
. ' [' . (!empty($bt[$i]['line']) ? $bt[$i]['line'] : '-') . ']: ' // Добавляем номер строки
. $call; // Добавляем строку вызова функции
}
// Добавляем информацию о запросе в массив для режима разработки
Cot::$sys['devmode']['queries'][] = [$this->_count, $executionTime, $query, $calls];
Cot::$sys['devmode']['timeline'][] = $startTime - Cot::$sys['starttime']; // Время запроса относительно времени старта
}
}
}
/**
* 1) Если вызывается с одним параметром:
* Работает как PDO::query()
* Выполняет SQL-запрос в одном вызове функции, возвращая результат (если он есть) в виде объекта PDOStatement.
* 2) Если вызывается со вторым параметром в виде массива параметров для привязки:
* Работает как PDO::prepare()->execute()
* Подготавливает SQL-запрос и выполняет его.
* @see http://www.php.net/manual/en/pdo.query.php
* @see http://www.php.net/manual/en/pdo.prepare.php
* @param string $query SQL-запрос для подготовки и выполнения.
* @param array|string|int|float $parameters Массив значений для привязки в качестве входных параметров к запросу.
* Параметры типа int будут рассмотрены как PDO::PARAM_INT, остальные — как PDO::PARAM_STR.
* @param int $mode Режим выборки. Подробнее: https://www.php.net/manual/ru/pdo.constants.php
* В Cotonti по умолчанию используется PDO::FETCH_ASSOC для экономии памяти.
*
* @return PDOStatement
*/
public function query($query, $parameters = [], $mode = PDO::FETCH_ASSOC)
{
if (!is_array($parameters)) { // Если параметры не являются массивом, преобразуем их в массив
$parameters = [$parameters]; // Преобразуем в массив
}
$this->_startTimer(); // Запускаем таймер выполнения запроса
if (count($parameters) > 0) { // Если есть параметры
if ($this->_prepare_itself) { // Если нужно подготовить запрос самим
$result = $this->adapter->query($this->_prepare($query, $parameters)); // Выполняем подготовленный запрос
} else { // Если параметры передаются через bind
$result = $this->adapter->prepare($query); // Подготавливаем запрос
$this->_bindParams($result, $parameters); // Привязываем параметры
$result->execute(); // Выполняем запрос
}
} else { // Если параметров нет
$result = $this->adapter->query($query, $mode); // Выполняем запрос без параметров
}
$this->_stopTimer($query); // Останавливаем таймер выполнения запроса
if (!empty($result)) { // Если результат не пустой
$result->setFetchMode($mode); // Устанавливаем режим выборки
$this->_affected_rows = $result->rowCount(); // Получаем количество затронутых строк
}
return $result; // Возвращаем результат
}
Комментарий объясняет, что метод query выполняет SQL-запросы с параметрами или без, используя либо PDO::query(), либо PDO::prepare() и PDO::execute(). Время выполнения запроса измеряется и добавляется информация о затронутых строках.
/**
* Возвращает общее количество записей в таблице
* @param string $tableName Название таблицы
* @return int
*/
public function countRows($tableName)
{
// Выполняем запрос для подсчета общего числа записей в таблице
return $this->query('SELECT COUNT(*) FROM ' . $this->quoteTableName($tableName))->fetchColumn();
}
метод countRows выполняет SQL-запрос для подсчета всех записей в таблице, возвращая результат в виде целого числа, которое представляет количество строк в таблице.
/**
* Выполняет простой SQL-запрос DELETE и возвращает количество удаленных элементов.
*
* @param string $tableName Название таблицы
* @param string|string[] $condition Условие WHERE
* @param array $parameters Массив входных параметров для запроса, см. http://www.php.net/manual/en/pdostatement.execute.php
* @return int Количество удаленных записей при успешном выполнении или FALSE при ошибке
*/
public function delete($tableName, $condition = '', $parameters = [])
{
// Строим основной запрос DELETE для указанной таблицы
$query = 'DELETE FROM ' . $this->quoteTableName($tableName);
// Если есть условие, добавляем его к запросу
if (!empty($condition)) {
// Если условие передано как массив, преобразуем его в строку
if (is_array($condition)) {
$condition = $this->prepareCondition($condition);
}
// Добавляем условие WHERE
$query .= ' WHERE ' . $condition;
}
// Если параметры не переданы как массив, преобразуем их в массив
if (!is_array($parameters)) {
$parameters = array($parameters);
}
// Переменная для хранения результата (количества удаленных строк)
$res = 0;
// Стартуем таймер для измерения времени выполнения запроса
$this->_startTimer();
// Если есть параметры, подготавливаем запрос с параметрами
if (count($parameters) > 0) {
// Если запрос можно подготовить самому
if ($this->_prepare_itself) {
// Выполняем подготовленный запрос
$res = $this->adapter->exec($this->_prepare($query, $parameters));
} else {
// Готовим запрос через PDO
$stmt = $this->adapter->prepare($query);
// Привязываем параметры
$this->_bindParams($stmt, $parameters);
// Выполняем запрос
$stmt->execute();
// Получаем количество удаленных строк
$res = $stmt->rowCount();
}
} else {
// Если параметров нет, просто выполняем запрос
$res = $this->adapter->exec($query);
}
// Останавливаем таймер после выполнения запроса
$this->_stopTimer($query);
// Возвращаем количество удаленных записей
return $res;
}
/**
* Проверяет, существует ли таблица в схеме
*
* @param string $tableName Название таблицы
* @return bool TRUE, если таблица существует, FALSE в противном случае
*/
public function tableExists($tableName)
{
// Проверяем, указана ли схема таблицы (через точку)
if (mb_strpos($tableName, '.') !== false) {
// Если схема указана, разделяем на схему и имя таблицы
[$schema, $tableName] = explode('.', $tableName);
} else {
// Если схема не указана, используем схему из конфигурации
$schema = $this->config['dbName'];
}
// Строим запрос для проверки существования таблицы в информационной схеме
$query = 'SELECT * FROM ' . $this->quoteTableName('information_schema.tables')
. ' WHERE ' . $this->quoteColumnName('table_schema') . ' = :schema AND ' . $this->quoteColumnName('table_name')
. ' = :table LIMIT 1';
// Выполняем запрос и проверяем, найден ли хотя бы один результат (rowCount)
return $this->query($query, ['schema' => $schema, 'table' => $tableName])->rowCount() === 1;
}
метод tableExists проверяет, существует ли таблица в базе данных, проверяя наличие записи в системной таблице information_schema.tables, которая хранит информацию о всех таблицах в базе данных.
/**
* Checks if an index with the same index name or column order exists
*
* @param string $tableName Table name
* @param string $index_name Index/Key name
* @param string[]|string $indexColumns Either a string for a single column name or an array for single/multiple
* columns. No column check will be preformed if left empty.
* @return bool TRUE if the index name or column order exists, FALSE otherwise
*/
public function indexExists($tableName, $index_name, $indexColumns = [])
{
if (empty($indexColumns)) {
return (bool) $this->query('SHOW INDEXES FROM ' . $this->quoteTableName($tableName) .
' WHERE Key_name=' . $this->quote($index_name))->rowCount();
}
$existing_indexes = $this->query('SHOW INDEXES FROM ' . $this->quoteTableName($tableName))->fetchAll();
if (!is_array($indexColumns)) {
$indexColumns = array($indexColumns);
}
$exists = false;
$index_list = array();
foreach ($existing_indexes as $existing_index) {
$index_list[$existing_index['Key_name']][$existing_index['Seq_in_index'] - 1] = $existing_index['Column_name'];
}
foreach ($index_list as $list_index => $list_columns) {
if ($list_index == $index_name) {
$exists = true;
break;
}
if (
count(array_diff_assoc($indexColumns, $list_columns)) === 0 &&
count($indexColumns) === count($list_columns)
) {
$exists = true;
break;
}
}
return $exists;
}
метод fieldExists выполняет запрос SHOW COLUMNS FROM, чтобы получить информацию о столбцах таблицы и проверяет, существует ли столбец с указанным именем ($fieldName). Если столбец найден, возвращается TRUE, иначе — FALSE.
/**
* Проверяет, существует ли индекс с таким же именем или порядком столбцов
*
* @param string $tableName Название таблицы
* @param string $index_name Имя индекса/ключа
* @param string[]|string $indexColumns Массив или строка с именем столбца (если передан массив — проверяется несколько столбцов)
* Проверка столбцов не выполняется, если оставлено пустым.
* @return bool TRUE, если индекс с таким именем или порядком столбцов существует, FALSE в противном случае
*/
public function indexExists($tableName, $index_name, $indexColumns = [])
{
// Если список столбцов пуст, проверяем только имя индекса
if (empty($indexColumns)) {
return (bool) $this->query('SHOW INDEXES FROM ' . $this->quoteTableName($tableName) .
' WHERE Key_name=' . $this->quote($index_name))->rowCount();
}
// Если переданы столбцы, проверяем их порядок и существование индекса
$existing_indexes = $this->query('SHOW INDEXES FROM ' . $this->quoteTableName($tableName))->fetchAll();
// Если $indexColumns — это строка, преобразуем в массив
if (!is_array($indexColumns)) {
$indexColumns = array($indexColumns);
}
$exists = false;
$index_list = array();
// Составляем список индексов и их столбцов
foreach ($existing_indexes as $existing_index) {
$index_list[$existing_index['Key_name']][$existing_index['Seq_in_index'] - 1] = $existing_index['Column_name'];
}
// Проверяем, существует ли индекс с таким же именем или порядком столбцов
foreach ($index_list as $list_index => $list_columns) {
// Если имя индекса совпадает, возвращаем TRUE
if ($list_index == $index_name) {
$exists = true;
break;
}
// Если порядок столбцов совпадает, также возвращаем TRUE
if (
count(array_diff_assoc($indexColumns, $list_columns)) === 0 &&
count($indexColumns) === count($list_columns)
) {
$exists = true;
break;
}
}
return $exists;
}
метод indexExists сначала проверяет, существует ли индекс с заданным именем. Если переданы столбцы для индекса, он проверяет не только имя, но и порядок столбцов, чтобы определить, существует ли индекс с таким же набором столбцов в заданном порядке.
/**
* Добавляет индекс на таблицу
*
* @param string $tableName Название таблицы
* @param string $indexName Имя индекса/ключа
* @param string[]|string $indexColumns Массив или строка с именем столбца (если передан массив — проверяется несколько столбцов).
* $indexName будет использоваться, если столбцы не указаны.
* @return int Количество затронутых строк
*/
public function addIndex($tableName, $indexName, $indexColumns = [])
{
// Если массив столбцов пуст, используем имя индекса в качестве столбца
if (empty($indexColumns)) {
$indexColumns = array($indexName);
}
// Если $indexColumns не является массивом, преобразуем в массив
if (!is_array($indexColumns)) {
$indexColumns = array($indexColumns);
}
// Экранируем имена столбцов для использования в SQL запросе
$quotedColumns = [];
foreach ($indexColumns as $column) {
$quotedColumns[] = $this->quoteColumnName($column);
}
// Выполняем SQL запрос для добавления индекса
return $this->query('ALTER TABLE ' . $this->quoteTableName($tableName) .
' ADD INDEX ' . $this->quoteColumnName($indexName) . ' (' . implode(',', $quotedColumns) . ')')
->rowCount();
}
метод addIndex добавляет индекс в таблицу с использованием переданных столбцов. Если столбцы не указаны, используется имя индекса в качестве столбца. Метод возвращает количество затронутых строк, что свидетельствует об успешном выполнении операции.
/**
* Выполняет SQL INSERT для простого массива данных. Ключи массива должны соответствовать ключам таблицы.
* Опционально можно указать префикс ключей в третьем параметре. Строки автоматически экранируются и заключаются в кавычки.
* Числа и числа с плавающей запятой должны быть приведены к типу.
* В массиве можно использовать специальные значения:
* - PHP NULL => SQL NULL
* - 'NOW()' => SQL NOW()
* Выполняет вставку одной строки, если $data — ассоциативный массив,
* выполняет вставку нескольких строк, если $data — двумерный массив (индексы => ассоциативные массивы).
*
* @param string $tableName Название таблицы
* @param array<string, int|float|string>|list<array<string, int|float|string>> $data Ассоциативный или двумерный массив
* с данными для вставки.
* @param bool $insertNull Вставлять SQL NULL для пустых значений, а не игнорировать их.
* @param bool $ignore Игнорировать ошибки дублирования ключей при вставке
* @param array $updateFields Список полей для обновления с ON DUPLICATE KEY UPDATE
* @return int Количество затронутых записей
*/
public function insert($tableName, $data, $insertNull = false, $ignore = false, $updateFields = [])
{
// Если $data не является массивом или пуст, возвращаем 0
if (!is_array($data) || $data === []) {
return 0;
}
// Инициализация переменных для хранения ключей и значений
$keys = '';
$vals = '';
// Проверка типа массива данных
$arr_keys = array_keys($data);
$multiline = isset($arr_keys[0]) && is_numeric($arr_keys[0]);
// Строим запрос в зависимости от того, один ли это массив или несколько строк данных
if ($multiline) {
$rowset = &$data;
} else {
$rowset = [$data];
}
$keys_built = false; // Флаг, указывающий, построены ли уже ключи
$cnt = count($rowset); // Количество строк данных
// Обрабатываем все строки данных
for ($i = 0; $i < $cnt; $i++) {
$vals .= ($i > 0) ? ',(' : '('; // Если это не первая строка, добавляем запятую перед значениями
$j = 0;
// Обрабатываем каждое поле в строке
if (is_array($rowset[$i])) {
foreach ($rowset[$i] as $key => $val) {
// Пропускаем значения NULL, если не нужно вставлять NULL
if (is_null($val) && !$insertNull) {
continue;
}
// Добавляем разделители для значений
if ($j > 0) {
$vals .= ',';
}
// Добавляем ключи в строку только один раз
if (!$keys_built) {
if ($j > 0) {
$keys .= ','; // Разделитель между ключами
}
$keys .= $this->quoteColumnName($key); // Добавляем ключ в строку с экранированием
}
// Обрабатываем значения в зависимости от их типа
if (is_null($val) || $val === 'NULL') {
$vals .= 'NULL'; // Если значение NULL, добавляем SQL NULL
} elseif (is_bool($val)) {
$vals .= $val ? 'TRUE' : 'FALSE'; // Если булево значение, добавляем TRUE/FALSE
} elseif ($val === 'NOW()') {
$vals .= 'NOW()'; // Если значение NOW(), добавляем SQL NOW()
} elseif (is_int($val) || is_float($val)) {
$vals .= $val; // Если это число, добавляем его без экранирования
} else {
$vals .= $this->quote($val); // Если это строка, экранируем и добавляем её
}
$j++;
}
}
$vals .= ')'; // Закрываем скобки значений
$keys_built = true; // Устанавливаем флаг, что ключи построены
}
// Если нет значений или ключей, возвращаем 0
if (empty($keys) || empty($vals)) {
return 0;
}
// Строим запрос с учетом флага игнорирования ошибок дублирования
$ignore = $ignore ? 'IGNORE' : ''; // Если нужно игнорировать ошибки, добавляем IGNORE в запрос
$query = "INSERT $ignore INTO " . $this->quoteTableName($tableName) . " ($keys) VALUES $vals"; // Строим основной запрос
// Если указаны поля для обновления при дублировании, добавляем ON DUPLICATE KEY UPDATE
if (count($updateFields) > 0) {
$query .= ' ON DUPLICATE KEY UPDATE'; // Добавляем часть запроса для обновления
$j = 0;
foreach ($updateFields as $key) {
if ($j > 0) $query .= ','; // Добавляем запятую между полями
$query .= ' ' . $this->quoteColumnName($key) . ' = VALUES(' . $this->quoteColumnName($key) . ')'; // Формируем обновление
$j++;
}
}
// Запускаем таймер для измерения времени выполнения запроса
$this->_startTimer();
$res = $this->adapter->exec($query); // Выполняем запрос
// Останавливаем таймер
$this->_stopTimer($query);
// Возвращаем количество затронутых записей
return $res;
}
метод insert выполняет вставку данных в таблицу, с возможностью работы с несколькими строками, обработки значений NULL и игнорирования ошибок дублирования ключей. Также можно указать поля для обновления при дублировании ключей.
/**
* Получает последний вставленный идентификатор записи.
*
* @param string $name Необязательно. Требуется для PostgreSQL
* @param string $pkey Необязательно. Требуется для PostgreSQL
* @return string Возвращает последний вставленный идентификатор.
*
* Например, для PDO_PGSQL необходимо указать имя последовательности для параметра $name.
* Формат: "{$table_name}_{$pkey}_seq"
*
* @see https://www.php.net/manual/en/pdo.lastinsertid.php
*/
public function lastInsertId($name = '', $pkey = '')
{
// Вызов метода lastInsertId у адаптера с указанием имени последовательности, если оно задано
return $this->adapter->lastInsertId($name); // Возвращает последний вставленный ID
}
/**
* Выполняет SQL UPDATE с использованием простого массива данных. Ключи массива должны соответствовать ключам таблицы,
* при необходимости можно указать префикс ключа в качестве четвёртого параметра.
* Строки автоматически экранируются и заключаются в кавычки.
* Числа типа int и float должны быть приведены к нужному типу.
* В массиве можно использовать специальные значения:
* - PHP NULL => SQL NULL
* - 'NOW()' => SQL NOW()
*
* @param string $tableName Имя таблицы
* @param array $data Ассоциативный массив, содержащий данные для обновления
* @param string|string[] $condition Тело SQL WHERE клаузулы
* @param array $parameters Массив входных параметров для запроса, см. http://www.php.net/manual/en/pdostatement.execute.php
* @param bool $updateNull Обновить ячейки, содержащие null значения в массиве. По умолчанию они пропускаются
* @return int Количество затронутых записей или FALSE в случае ошибки
*/
public function update($tableName, $data, $condition = '', $parameters = [], $updateNull = false)
{
if (!is_array($data)) { // Проверка, является ли $data массивом
return 0; // Если $data не массив, возвращается 0
}
$upd = ''; // Инициализация строки для построения частей запроса UPDATE
if (!is_array($parameters)) { // Если $parameters не является массивом
$parameters = [$parameters]; // Преобразуем его в массив
}
// Если включена подготовка запроса и условие не пустое, а также есть параметры
if ($this->_prepare_itself && !empty($condition) && count($parameters) > 0) {
$condition = $this->_prepare($condition, $parameters); // Подготовка условия запроса с параметрами
$parameters = []; // Очистка массива параметров после подготовки
}
// Если $condition является массивом, его нужно преобразовать в строку SQL условия
if (!empty($condition) && is_array($condition)) {
$condition = $this->prepareCondition($condition); // Преобразование массива в строку условия
}
// Если условие не пустое, добавляется префикс 'WHERE'
$condition = empty($condition) ? '' : 'WHERE ' . $condition;
// Итерация по данным для построения выражений UPDATE
foreach ($data as $key => $val) {
if (is_null($val) && !$updateNull) { // Если значение NULL и обновление NULL не разрешено
continue; // Пропускаем это поле
}
$upd .= $this->quoteColumnName($key) . '='; // Добавляем в строку обновления имя поля и знак равенства
if (is_null($val) || $val === 'NULL') { // Если значение NULL или строка 'NULL'
$upd .= 'NULL,'; // Добавляем NULL в строку запроса
} elseif (is_bool($val)) { // Если значение булево
$upd .= $val ? 'TRUE,' : 'FALSE,'; // Добавляем TRUE или FALSE в зависимости от значения
} elseif ($val === 'NOW()') { // Если значение 'NOW()' (вставка текущей даты и времени)
$upd .= 'NOW(),'; // Добавляем SQL функцию NOW()
} elseif (is_int($val) || is_float($val)) { // Если значение числовое (целое или с плавающей точкой)
$upd .= $val . ','; // Добавляем числовое значение
} else { // Если значение — строка
$upd .= $this->quote($val) . ','; // Экранируем строку и добавляем её в запрос
}
}
// Если строка обновления не пуста
if (!empty($upd)) {
$upd = mb_substr($upd, 0, -1); // Убираем последнюю запятую
// Формирование полного запроса UPDATE
$query = 'UPDATE ' . $this->quoteTableName($tableName) . " SET $upd $condition";
$this->_startTimer(); // Запуск таймера для измерения времени выполнения запроса
// Если есть параметры, готовим и выполняем запрос с параметрами
if (count($parameters) > 0) {
$stmt = $this->adapter->prepare($query); // Подготовка запроса
$this->_bindParams($stmt, $parameters); // Привязка параметров к запросу
$stmt->execute(); // Выполнение подготовленного запроса
$res = $stmt->rowCount(); // Получение количества затронутых строк
} else { // Если параметров нет
$res = $this->adapter->exec($query); // Выполнение запроса напрямую
}
$this->_stopTimer($query); // Остановка таймера
return $res; // Возвращаем количество затронутых строк
}
return 0; // Если обновление не произошло, возвращаем 0
}
/**
* Подготавливает условие для SQL-запроса.
*
* @param array|string $condition Условие, которое может быть строкой или массивом строк.
* @return string Подготовленное условие для SQL-запроса.
*/
public function prepareCondition($condition): string
{
if (is_array($condition)) { // Проверка, является ли $condition массивом
if ($condition === []) { // Если массив пустой
return ''; // Возвращаем пустую строку, так как условие пустое
}
// Если массив не пустой, соединяем его элементы через 'AND' и оборачиваем в скобки
return '(' . implode(') AND (', $condition) . ')';
}
// Если $condition не массив, то возвращаем его как есть (предполагаем, что это строка)
return $condition;
}
В этом коде функция prepareCondition() обрабатывает входное условие. Если условие представлено в виде массива, каждый его элемент соединяется с другими через AND и заключен в круглые скобки. Если условие уже строковое, оно возвращается без изменений.
/**
* Подготавливает параметр для использования в SQL-запросе, не оборачивая его в кавычки.
*
* @param string $str Строковый параметр, который нужно подготовить.
* @return string Экранированный параметр для безопасного использования в запросе.
*/
public function prep($str)
{
if (empty($str)) { // Проверка, является ли строка пустой
return ''; // Если строка пустая, возвращаем пустую строку
}
// Применяем preg_replace для удаления внешних одинарных кавычек из строки, если они есть,
// затем экранируем строку с помощью метода quote для безопасного использования в SQL-запросах
return preg_replace("#^'(.*)'\$#", '$1', $this->quote((string) $str));
}
В этом коде функция prep() выполняет подготовку строки для использования в SQL-запросах. Она сначала проверяет, не является ли строка пустой, и если это так, возвращает пустую строку. Затем, если строка содержит внешние одинарные кавычки, они удаляются с помощью регулярного выражения, и оставшаяся строка экранируется через метод quote, который делает строку безопасной для использования в SQL-запросах.
/**
* Регистрирует имя таблицы без префикса в реестре имен таблиц.
*
* @param string $table_name Имя таблицы без префикса, например 'pages'.
*/
public function registerTable($table_name)
{
// Проверяем, зарегистрировано ли уже это имя таблицы в глобальном массиве $GLOBALS
if (!isset($GLOBALS['db_' . $table_name])) {
// Если не зарегистрировано, добавляем его, добавляя префикс таблицы из свойства tablePrefix
$GLOBALS['db_' . $table_name] = $this->tablePrefix . $table_name;
}
// Регистрируем таблицу в локальном массиве $this->_tables
$this->_tables[$table_name] = $GLOBALS['db_' . $table_name];
}
В этом коде метод registerTable() регистрирует имя таблицы без префикса в глобальном массиве $GLOBALS и локальном массиве $this->_tables. Если имя таблицы ещё не зарегистрировано в глобальном массиве, добавляется таблица с префиксом, который берётся из свойства tablePrefix. Это необходимо для работы с таблицами базы данных, когда нужно учитывать префикс таблицы для корректной работы с запросами.
/**
* Выполняет SQL-скрипт, содержащий несколько запросов.
*
* Скрипты должны быть созданы в формате MySQL с префиксом таблицы 'cot_'.
* Необходимый префикс таблицы будет автоматически подставлен, как и необходимые символы кавычек.
*
* @param string $script Тело SQL-скрипта, содержащее форматированные запросы, разделенные точками с запятой и новыми строками.
* @param bool $inTransaction Выполнять ли операции в рамках транзакции.
* @return string Сообщение об ошибке, если ошибка произошла, или пустая строка при успешном выполнении.
* @todo обработать $this->tableQuoteCharacter
*/
public function runScript($script, $inTransaction = false)
{
// Проверяем, не пуст ли скрипт
if (empty($script)) {
return '';
}
// Убираем комментарии из скрипта
$script = preg_replace('#^/\*.*?\*/#ms', "", $script); // Удаление многострочных комментариев
$script = preg_replace('#^--.*?$#m', '', $script); // Удаление однострочных комментариев
// Разбиваем скрипт на отдельные запросы, разделенные точками с запятой и новой строкой
$queries = preg_split('#;\r?\n#', $script);
// Если требуется выполнение в рамках транзакции, начинаем транзакцию
if ($inTransaction) {
$this->beginTransaction();
}
try {
// Проходим по всем запросам
foreach ($queries as $query) {
$query = trim($query); // Убираем лишние пробелы по краям запроса
if (empty($query)) {
continue; // Если запрос пустой, пропускаем его
}
// Если префикс таблицы отличается от 'cot_' или символ кавычек не '`', то заменяем префиксы таблиц в запросе
if (
($this->tablePrefix !== 'cot_' || $this->tableQuoteCharacter !== '`')
&& preg_match_all('#`cot_(\w+)`#', $query, $matches)
) {
// Заменяем каждый найденный префикс на актуальное имя таблицы с префиксом
foreach ($matches[0] as $key => $match) {
$tableName = isset($GLOBALS['db_' . $matches[1][$key]]) ?
$GLOBALS['db_' . $matches[1][$key]] : $this->tablePrefix . $matches[1][$key];
// Заменяем все вхождения префикса на нужное имя таблицы
$query = str_replace($match, $this->quoteTableName($tableName), $query);
}
}
// Если символы кавычек для колонок не равны '`', заменяем их на нужные
if ($this->columnQuoteCharacter != '`') {
$query = str_replace('`', $this->columnQuoteCharacter, $query); // Заменяем символы кавычек на нужные
}
// Выполняем запрос
$result = $this->query($query);
// Если запрос не выполнен успешно, генерируем ошибку
if (!$result) {
$message = !empty($this->error) ? $this->error : 'Ошибка при выполнении:';
$message .= ' "' . $query . '"';
throw new Exception ($message);
}
// Если результат является объектом PDOStatement, закрываем курсор
if ($result instanceof PDOStatement) {
$result->closeCursor();
}
}
// Если запросы выполнялись в рамках транзакции, подтверждаем транзакцию
if ($inTransaction) {
$this->commit();
}
} catch (Exception $e) {
// В случае ошибки откатываем транзакцию (если она была)
if ($inTransaction) {
$this->rollBack();
}
// Возвращаем сообщение об ошибке
return $e->getMessage();
}
// Если все прошло успешно, возвращаем пустую строку
return '';
}
Этот метод runScript выполняет SQL-скрипт, который может содержать несколько запросов. Он обрабатывает возможные ошибки, работает с транзакциями, заменяет префиксы таблиц и кавычки в запросах и возвращает сообщение об ошибке в случае неудачи.
/**
* Экранирует строковое значение для использования в запросе.
*
* @param string|string[] $data строка или массив строк для экранирования
* @return string|string[] правильно экранированная строка или массив строк
* @see http://php.net/manual/en/pdo.quote.php
*/
public function quote($data)
{
// Проверяем, является ли входной параметр строкой
if (is_string($data)) {
// Попытка экранировать строку с помощью метода quote из PDO-адаптера
if (($value = $this->adapter->quote($data)) !== false) {
return $value; // Если экранирование прошло успешно, возвращаем экранированную строку
}
// Если драйвер не поддерживает метод quote (например, для oci), экранируем строку вручную
return "'" . addcslashes(str_replace("'", "''", $data), "\000\n\r\\\032") . "'"; // Строка экранируется с заменой одинарных кавычек на две и добавлением спецсимволов
}
// Если $data не является строкой, но является массивом, обрабатываем каждую строку в массиве
if (!is_array($data)) {
return $data; // Если $data не строка и не массив, возвращаем ее без изменений
}
// Обрабатываем каждый элемент массива
foreach ($data as $key => $str) {
// Не экранируем целые числа
if (((string) ((int) $str)) != $str) {
// Если строка не является числом, экранируем ее
$data[$key] = $this->quote($str);
}
}
// Возвращаем массив экранированных строк
return $data;
}
Этот метод quote обрабатывает строковые данные, экранируя их для безопасного использования в SQL-запросах. Он проверяет, является ли входной параметр строкой или массивом, и экранирует строки соответствующим образом, используя либо встроенный метод quote адаптера, либо выполняя экранирование вручную, если драйвер не поддерживает этот метод. Если параметр является массивом, метод обрабатывает каждый элемент массива, не экранируя целые числа.
/**
* Экранирует имя таблицы для использования в запросе.
* Если имя таблицы содержит префикс схемы, префикс также будет корректно экранирован.
*
* Если имя таблицы уже экранировано или содержит специальные символы, включая '(',
* метод ничего не будет делать.
*
* @param string $name имя таблицы
* @return string правильно экранированное имя таблицы
*/
public function quoteTableName($name)
{
// Если имя таблицы пустое, возвращаем пустую строку
if (empty($name)) {
return '';
}
// Если имя таблицы начинается с '(' и заканчивается на ')', не экранируем его
if (strncmp($name, '(', 1) === 0 && strpos($name, ')') === strlen($name) - 1) {
return $name; // Возвращаем имя без изменений
}
// Если имя таблицы не содержит '.', то экранируем простое имя таблицы
if (strpos($name, '.') === false) {
return $this->quoteSimpleTableName($name); // Экранируем только имя таблицы
}
// Если имя таблицы содержит '.', разделяем его на части
$parts = explode('.', $name);
// Обрабатываем каждую часть имени таблицы
foreach ($parts as $i => $part) {
// Экранируем каждую часть имени
$parts[$i] = $this->quoteSimpleTableName($part);
}
// Возвращаем имя таблицы, соединенное обратно с точкой
return implode('.', $parts);
}
Метод quoteTableName экранирует имя таблицы для использования в SQL-запросах. Если имя таблицы уже экранировано или содержит специальные символы, такие как круглые скобки, оно остается неизменным. Если имя таблицы состоит из нескольких частей, разделенных точкой (например, схемы и имени таблицы), каждая часть экранируется отдельно.
/**
* Псевдоним для self::quoteTableName()
* Краткое название для удобства использования
* @param string $name имя таблицы
* @return string правильно экранированное имя таблицы
*/
public function quoteT($name)
{
return $this->quoteTableName($name); // Вызов метода quoteTableName для экранирования имени таблицы
}
Метод quoteT является псевдонимом для метода quoteTableName. Он упрощает использование, позволяя использовать более короткое название при необходимости экранировать имя таблицы в SQL-запросе.
/**
* Экранирует простое имя таблицы для использования в SQL-запросе.
* Простое имя таблицы должно содержать только имя таблицы без префикса схемы.
* Если имя таблицы уже экранировано, этот метод не изменит его.
*
* @param string $name имя таблицы
* @return string правильно экранированное имя таблицы
*/
protected function quoteSimpleTableName($name)
{
if (is_string($this->tableQuoteCharacter)) {
$startChar = $endChar = $this->tableQuoteCharacter; // Если символ для экранирования таблицы является строкой, то он будет использован как начальный и конечный символ для экранирования имени таблицы
} else {
[$startChar, $endChar] = $this->tableQuoteCharacter; // Если символ для экранирования таблицы является массивом, то первый элемент массива — это начальный символ, а второй — конечный символ для экранирования имени таблицы
}
if (strpos($name, $startChar) !== false) { // Если в имени таблицы уже присутствует символ экранирования (начальный символ), то возвращаем имя таблицы без изменений
return $name;
}
return $startChar . $name . $endChar; // Если имя таблицы не экранировано, то добавляем символы экранирования и возвращаем его
}
Метод quoteSimpleTableName экранирует имя таблицы для использования в SQL-запросах. Он добавляет необходимые символы для начала и конца имени таблицы, если они еще не добавлены, и проверяет, не содержится ли уже символ экранирования в имени таблицы.
/**
* Экранирует имя столбца для использования в SQL-запросе.
* Если имя столбца содержит префикс, то префикс также будет правильно экранирован.
* Если имя столбца уже экранировано или содержит специальные символы, такие как '(', '[['
* то этот метод не изменит имя.
*
* @param string $name имя столбца
* @return string правильно экранированное имя столбца
*/
public function quoteColumnName($name)
{
if (strpos($name, '(') !== false || strpos($name, '[[') !== false) {
return $name; // Если в имени столбца есть скобки или специальные символы '[[' то возвращаем имя без изменений
}
if (($pos = strrpos($name, '.')) !== false) { // Проверяем, есть ли в имени столбца точка, разделяющая префикс и сам столбец
$prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.'; // Если точка есть, то экранируем префикс (имя таблицы) и добавляем точку
$name = substr($name, $pos + 1); // Оставляем только имя столбца после точки
} else {
$prefix = ''; // Если точки нет, то префикс отсутствует
}
return $prefix . $this->quoteSingleColumnName($name); // Возвращаем экранированное имя столбца с префиксом (если он был)
}
Метод quoteColumnName экранирует имя столбца, при этом учитывает возможное наличие префикса (например, имя таблицы). Если в имени столбца присутствуют специальные символы, такие как скобки или символы [[], то оно не изменяется. Метод сначала отделяет префикс и затем экранирует имя столбца, если необходимо.
/**
* Псевдоним для self::quoteColumnName()
* Короткое имя для удобства использования
* @param string $name имя столбца
* @return string правильно экранированное имя столбца
*/
public function quoteC($name)
{
return $this->quoteColumnName($name); // Возвращаем результат работы метода quoteColumnName для экранирования имени столбца
}
Метод quoteC является псевдонимом для метода quoteColumnName. Он используется для удобства и сокращает запись при вызове метода для экранирования имен столбцов.
/**
* Quotes a simple column name for use in a query.
* A simple column name should contain the column name only without any prefix.
* If the column name is already quoted or is the asterisk character '*', this method will do nothing.
*
* @param string $name column name
* @return string the properly quoted column name
*/
protected function quoteSingleColumnName($name)
{
if (is_string($this->tableQuoteCharacter)) {
$startChar = $endChar = $this->columnQuoteCharacter;
} else {
[$startChar, $endChar] = $this->columnQuoteCharacter;
}
if ($name === '*' || strpos($name, $startChar) !== false) {
return $name;
}
return $startChar . $name . $endChar;
}
Метод quoteSingleColumnName экранирует имя столбца для безопасного использования в SQL-запросах. Он добавляет символы экранирования в начало и конец имени столбца, если имя не является символом * и если оно еще не экранировано. Если имя уже экранировано или это символ *, метод не вносит изменений.
Comments (0)
Content author
Offline
Administrator
Last logged: 2026-07-05 18:32
- Page published: 2025-01-26 13:37
- Last update: 2025-01-26 14:26