2025-07-28 18:08:03 +03:00

823 lines
29 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @noinspection SqlNoDataSourceInspection
*/
namespace goodboyalex\php_db_components_pack\classes;
use Closure;
use Exception;
use goodboyalex\php_db_components_pack\enums\DBDriver;
use goodboyalex\php_db_components_pack\models\DBConfig;
use PDO;
use PDOException;
/**
* Класс для работы с базой данных.
*
* Используется класс PDO для подключения к базе данных.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
* @see PDO
*/
final class Database
{
/**
* @var PDO|null $DataBaseHandle Переменная, хранящая класс PDO.
*/
private ?PDO $DataBaseHandle;
/**
* @var string $DBSignOpen Символ открытия для подстановки в запросы к базе.
*/
private string $DBSignOpen;
/**
* @var string $DBSignСlose Символ закрытия для подстановки в запросы к базе.
*/
private string $DBSignClose;
/**
* @var Closure $OnException Обработчик исключений.
*/
private Closure $OnException;
/**
* @var DBConfig $Config Конфигурация подключения к базе данных.
*/
private DBConfig $Config;
/**
* Конструктор. Подключает базу данных
*
* @param DBConfig $config Конфигурация подключения к базе данных.
* @param callable $onException Обработчик исключений.
*/
public function __construct (DBConfig $config, callable $onException)
{
// Задаю конфигурацию
$this->Config = $config;
// Устанавливаю обработчик исключений
$this->OnException = $onException;
try {
// Загружаю параметры подключения
// - хост
$host = $this->Config->Host;
// - порт
$port = $this->Config->Port;
// - имя базы данных
$dbname = $this->Config->Name;
// - пользователь
$user = $this->Config->UserName;
// - пароль
$password = $this->Config->Password;
// Создаю dsn
$dsn = match ($this->Config->Driver) {
DBDriver::MySQL => "mysql:host=$host;port=$port;dbname=$dbname",
DBDriver::MSSQL => "sqlsrv:Server=$host,$port;Database=$dbname;Encrypt=false;",
DBDriver::PostgreSQL => "pgsql:host=$host;port=$port;dbname=$dbname;",
DBDriver::OracleDB => "oci:dbname=$host:$port/$dbname",
DBDriver::SQLite => "sqlite:$dbname"
};
// Задаю DBSign
// - Open
$this->DBSignOpen = match ($this->Config->Driver) {
DBDriver::MySQL, DBDriver::SQLite => '`',
DBDriver::MSSQL => '[',
DBDriver::PostgreSQL, DBDriver::OracleDB => '"'
};
// - Close
$this->DBSignClose = match ($this->Config->Driver) {
DBDriver::MySQL, DBDriver::SQLite => '`',
DBDriver::MSSQL => ']',
DBDriver::PostgreSQL, DBDriver::OracleDB => '"'
};
// Создаю объект для связи с базой данных
$this->DataBaseHandle = new PDO($dsn, username: $user, password: $password);
// Устанавливаю уровень ошибок
$this->DataBaseHandle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch (PDOException $e) {
$onException($e);
}
}
/**
* Деструктор. Закрывает соединение с базой данных
*/
public function __destruct ()
{
$this->DataBaseHandle = null;
}
/**
* Выполняем запрос на получение последней строки
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return false|array Строка в формате массива или false в случае ошибки
*
* @see Query
* @see QueryFirst
* @see QueryScalar
*/
public function QueryLast (string $query, array $params = []): false|array
{
// Выполняю запрос
$result = $this->Query($query, $params);
// Если в результате запроса получили ошибку или количество строк = 0
if ($result === false || count($result) == 0)
// - то возвращаем ошибку
return false;
// Получаю последний ключ массива
$lastKey = array_key_last($result);
// Возвращаем первую строку
return $result[$lastKey];
}
/**
* Запрос строк из базы данных.
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return false|array Ассоциированный массив с результатом запроса или false в случае ошибки
*/
public function Query (string $query, array $params = []): false|array
{
// По умолчанию, результат пуст
$result = false;
try {
// Подготавливаю запрос
$STH = $this->DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
// Выполняю запрос
$STH->execute($params);
// Указываю, что данные, которые я хочу получить, должны быть в ассоциативном массиве
$STH->setFetchMode(PDO::FETCH_ASSOC);
// Получаю все данные
$result = $STH->fetchAll();
}
catch (PDOException $e) {
$this->OnException($e);
}
// Вывожу результат
return $result;
}
/**
* Выполнение запроса. Обычно используется для операций,
* которые не возвращают никаких данных, кроме количества
* затронутых ими записей. Например,
*
* <code>$FcmsDB->Execute('DELETE FROM table WHERE id=1');</code>
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return int|false Количество затронутых строк или false в случае ошибки
*/
public function Execute (string $query, array $params = []): int|false
{
// По умолчанию результат false
$result = false;
try {
// Если параметры не заданы
if (count($params) == 0) {
// - то выполняю запрос
$result = $this->DataBaseHandle->exec($query);
}
else {
// - в противном случае
// -- подготавливаю запрос
$STH = $this->DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]);
// -- выполняю запрос
$opResult = $STH->execute($params);
// -- и если выполнение успешное,
if ($opResult)
// --- то в результат пойдёт количество строк
$result = $STH->rowCount();
}
}
catch (PDOException $e) {
$this->HandleException($e);
}
// Если в результате false
if ($result === false)
// - то возвращаю его
return false;
// Возвращаю результат
return $result;
}
private function HandleException (Exception $exception): void
{
$this->OnException($exception);
}
/**
* Получает набор строк в массиве данных, удовлетворяющий выборке
*
* @param string $table Имя таблицы
* @param array $columns Колонки, которые нужно включить в запрос
* @param array $where Параметры выборки
*
* @return false|array Строка в формате массива или false в случае ошибки
*
* @see Query
* @see QueryFirst
* @see QueryLast
* @see GetRow
*/
public function GetRows (string $table, array $columns = [], array $where = []): false|array
{
// Задаю массив параметров
$params = [];
// Получаю SQL запрос
$sql = $this->PrepareSQLForRowsQuery($table, $columns, $where, $params);
// Получаю строки на основании запроса
$queryResult = $this->Query($sql, $params);
// Если строки не получены
if ($queryResult === false)
// - то выдаю ошибку
return false;
// Получаю значение строк
return $queryResult;
}
/**
* Генерирует SQL запрос выборки строк.
*
* @param string $table Имя таблицы
* @param array $columns Колонки, которые нужно включить в запрос
* @param array $where Параметры выборки
* @param array $params Параметры и их значения
*
* @return string SQL-запрос
*/
private function PrepareSQLForRowsQuery (string $table, array $columns = [], array $where = [],
array &$params = []): string
{
// Очищаю параметры
$params = [];
// Строковая интерпретация массива условий
$sql_where = $this->PrepareQueryWhere($where, $params);
// Колонки
$sql_columns = count($columns) > 0 ? implode(', ', $this->PrepareColumn($columns)) : "*";
// Создаю запрос
$sql = "SELECT $sql_columns FROM $this->DBSignOpen$table$this->DBSignClose";
// Если заданы where-параметры
if (count($where) > 0) {
// - то добавляю их
$sql .= " WHERE $sql_where";
}
// Возвращаю запрос
return $sql;
}
/**
* Готовит выражение для WHERE-запроса
*
* @param array $where Массив условий
* @param array $params Очищенные параметры
*
* @return string Строка WHERE-запроса
*/
private function PrepareQueryWhere (array $where, array &$params): string
{
// Очищаю параметры
$params = [];
// Задаю результат
$result = "";
// Если массив условий не пуст
if (count($where) > 0) {
// - то для каждого условия
foreach ($where as $key => $value) {
// -- получаю ключ 100%-но без ":" в начале
$where_key = $key[0] == ":" ? substr($key, 1) : $key;
// -- добавляю префикс для 2 или более итерации
$prefix = $result == "" ? "" : " AND ";
// -- добавляю данные в $sql_where
$result .= $prefix . $this->DBSignOpen . $where_key . $this->DBSignClose . " = :" . $where_key;
// -- добавляю данные в параметры
$params[$where_key] = "$value";
}
}
// Вывожу результат
return $result;
}
/**
* Подготавливает массив столбцов для использования в базе данных
*
* @param array $columns Массив колонок.
*
* @return array Массив преобразованных колонок.
*/
private function PrepareColumn (array $columns): array
{
return array_map(function ($item)
{
// Результирующая строка
$result = "";
// Если длинна строки > 0
if (strlen($item) > 0) {
// - первый символ
$firstLetter = substr($item, 0, 1);
// - последний символ
$lastLetter = substr($item, -1);
// - если первый символ не $this->DBSignOpen
if ($firstLetter !== $this->DBSignOpen)
// -- то добавляем
$result .= $this->DBSignOpen;
// - добавляем строку
$result .= $item;
// - если последний символ не $this->DBSignClose
if ($lastLetter !== $this->DBSignClose)
// -- то добавляем
$result .= $this->DBSignClose;
}
// Возвращаем результат
return $result;
}, $columns);
}
/**
* Получает первую строку в массиве данных, удовлетворяющую выборке
*
* @param string $table Имя таблицы
* @param array $columns Колонки, которые нужно включить в запрос
* @param array $where Параметры выборки
*
* @return false|array Строка в формате массива или false в случае ошибки
*
* @see Query
* @see QueryFirst
* @see QueryLast
* @see QueryScalar
* @see GetRows
*/
public function GetRow (string $table, array $columns = [], array $where = []): false|array
{
// Задаю массив параметров
$params = [];
// Получаю SQL запрос
$sql = $this->PrepareSQLForRowsQuery($table, $columns, $where, $params);
// Получаю строку на основании запроса
return $this->QueryScalar($sql, $params);
}
/**
* Выполняем запрос на получение одной строки (аналог QueryFirst)
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return false|array Строка в формате массива или false в случае ошибки
*
* @see Query
* @see QueryFirst
* @see QueryLast
* @see GetRow
*/
public function QueryScalar (string $query, array $params = []): false|array
{
return $this->QueryFirst($query, $params);
}
/**
* Выполняем запрос на получение первой строки
*
* @param string $query Запрос
* @param array $params Параметры запроса
*
* @return false|array Строка в формате массива или false в случае ошибки
*
* @see Query
* @see QueryLast
* @see QueryScalar
*/
public function QueryFirst (string $query, array $params = []): false|array
{
// Выполняю запрос
$result = $this->Query($query, $params);
// Если в результате запроса получили ошибку или количество строк = 0
if ($result === false || count($result) == 0)
// - то возвращаем ошибку
return false;
// Получаю первый ключ массива
$firstKey = array_key_first($result);
// Возвращаем первую строку
return $result[$firstKey];
}
/**
* Получает колонку в массиве данных
*
* @param string $table Имя таблицы
* @param string $column Имя колонки
* @param array $where Параметры запроса
*
* @return false|array Ассоциированный массив с результатом запроса или false в случае ошибки
*
* @see Query
*/
public function GetCol (string $table, string $column, array $where = []): false|array
{
// Задаю параметры
$params = [];
// Строковая интерпретация массива условий
$sql_where = $this->PrepareQueryWhere($where, $params);
// Создаю запрос
$sql = "SELECT $this->DBSignOpen$column$this->DBSignClose FROM $this->DBSignOpen$table$this->DBSignClose";
// Если заданы where-параметры
if (count($where) > 0) {
// - то добавляю их
$sql .= " WHERE $sql_where";
}
// Получаю столбец на основании запроса
$queryResult = $this->Query($sql, $params);
// Если строка не получена или пуста
if ($queryResult === false)
// - то выдаю ошибку
return false;
// Создаю результат
$result = [];
// Для каждого результата запроса
foreach ($queryResult as $row)
// - передаю его в результат
$result[] = $row[$column];
// Вывожу результат
return $result;
}
/**
* Получение значение единичного поля
*
* @param string $table Имя таблицы
* @param string $column Требуемый столбец
* @param array $where Параметры запроса
*
* @return mixed|null Результат запроса или null в случае ошибки
*/
public function GetValue (string $table, string $column, array $where = []): mixed
{
// Задаю параметры
$params = [];
// Строковая интерпретация массива условий
$sql_where = $this->PrepareQueryWhere($where, $params);
// Создаю запрос
$sql = "SELECT $this->DBSignOpen$column$this->DBSignClose FROM $this->DBSignOpen$table$this->DBSignClose";
// Если заданы where-параметры
if (count($where) > 0) {
// - то добавляю их
$sql .= " WHERE $sql_where";
}
// Получаю строку на основании запроса
$queryResult = $this->QueryScalar($sql, $params);
// Если строка не получена или пуста
if ($queryResult === false || count($queryResult) == 0)
// - то выдаю результат null
return null;
// Получаю значение колонки
return $queryResult[$column];
}
/**
* Вставляет строку в базу данных.
*
* @param string $table Имя таблицы
* @param array $params Параметры
*
* @return string|false В случае успеха выведет: id созданной записи, -1, если запись создана, но id не получен
* (глюк?) и false, если ошибка
*/
public function Insert (string $table, array $params = []): string|false
{
// Если массив параметров пуст
if (count($params) == 0)
// - прерываем с ошибкой
return false;
// Подготавливаю массив параметров
$params = $this->PrepareParamsArray($params);
// Получаю ключи параметров
$keys = array_keys($params);
// Создаю результирующий массив имён ключей параметров
$keysReal = [];
// Для каждого ключа параметра
foreach ($keys as $key) {
// - получаю его имя
$keyResult = $key[0] == ":" ? substr($key, 1) : $key;
// - заключаю в кавычки
$keyResult = "$this->DBSignOpen$keyResult$this->DBSignClose";
// - добавляю в результирующий массив ключей
$keysReal[] = $keyResult;
}
// Ключи sql запроса
$sql_keys = implode(', ', $keysReal);
// Значения sql запроса
$sql_values = implode(', ', $keys);
// Создаю запрос
$sql = "INSERT INTO $this->DBSignOpen$table$this->DBSignClose ($sql_keys) VALUES ($sql_values);";
// Выполняю запрос
$count = $this->Execute($sql, $params);
// Если результат - false или добавлена не одна запись
if (($count === false) || ($count < 1) || ($count > 1))
// - то и общий результат - false
return false;
$lastId = -1;
try {
// Получаю последний id этой записи
$lastIdResult = $this->DataBaseHandle->lastInsertId();
// Если получение неудачное
if ($lastIdResult === false)
// - то вывожу -1
return $lastId;
// Устанавливаю последний id
$lastId = $lastIdResult;
}
catch (PDOException $e) {
$this->HandleException($e);
}
// Вывожу последний id
return $lastId;
}
/**
* Подготавливает массив параметров
*
* @param array $params Массив параметров
*
* @return array|false Подготовленный массив параметров или false в случае ошибки
*/
private function PrepareParamsArray (array $params = []): array|false
{
// Если массив параметров пуст
if (count($params) == 0)
// - прерываем с ошибкой
return false;
// Создаём результирующий массив
$result = [];
// Для каждого параметра
foreach ($params as $key => $value) {
// - если ключ параметра начинается с ":"
if ($key[0] == ":")
// -- то сразу добавляем его в результирующий массив
$result[$key] = $value;
else
// -- в противном случае, предварительно добавим в имя ключа ":"
$result[':' . $key] = $value;
}
// Вывожу результирующий массив
return $result;
}
/**
* Заменяет данные в строке базы данных
*
* @param string $table Имя таблицы
* @param array $set Массив данных для замены
* @param array $where Массив условий
*
* @return bool Результат выполнения
*/
public function Update (string $table, array $set, array $where = []): bool
{
// Создаю массив параметров
$params_set = [];
// Строковая интерпретация массива для изменения
$sql_set = "";
// Для каждых данных для изменения
foreach ($set as $key => $value) {
// - получаю ключ 100%-но без ":" в начале
$set_key = $key[0] == ":" ? substr($key, 1) : $key;
// - добавляю префикс для 2 или более итерации
$prefix = $sql_set == "" ? "" : ", ";
// - добавляю данные в sql_set
$sql_set .= "$prefix$this->DBSignOpen$set_key$this->DBSignClose=:$set_key";
// - добавляю данные в параметры
$params_set[":" . $set_key] = $value;
}
// Обработанные параметры
$params_where = [];
// Строковая интерпретация массива условий
$sql_where = $this->PrepareQueryWhere(where: $where, params: $params_where);
// Создаю параметры
$params = array_merge($params_set, $params_where);
// Создаю запрос
$sql = "UPDATE $this->DBSignOpen$table$this->DBSignClose SET $sql_set";
// Если заданы where-параметры
if (count($where) > 0)
// - то добавляю их
$sql .= " WHERE $sql_where";
// Выполняю запрос
$count = $this->Execute($sql, $params);
// Если результат - false
if ($count === false)
// - то и общий результат - false
return false;
// Если изменено 0 строк
if ($count === 0)
// - то и общий результат - false
return false;
// Вывожу результат -- успех
return true;
}
/**
* Проверяет, существует ли запись в таблице.
*
* @param string $table Имя таблицы
* @param array $where Массив условий
*
* @return bool Результат проверки
*/
public function IsExist (string $table, array $where = []): bool
{
// Вывожу результат
return $this->Count($table, $where) > 0;
}
/**
* Подсчитывает количество строк, удовлетворяющих условию.
*
* @param string $table Имя таблицы
* @param array $where Массив условий выборки
*
* @return int Количество строк или -1, в случае ошибки
*/
public function Count (string $table, array $where = []): int
{
// Параметры
$params = [];
// Строковая интерпретация массива условий
$sql_where = $this->PrepareQueryWhere($where, $params);
// Создаю запрос
$sql = "SELECT COUNT(*) FROM $this->DBSignOpen$table$this->DBSignClose";
// Если заданы where-параметры
if (count($where) > 0) {
// - то добавляю их
$sql .= ' WHERE ' . $sql_where;
}
// Выполняю запрос
$countResult = $this->Query($sql, $params);
// Если запрос выполнен с ошибкой
if ($countResult === false)
// - то в результат идёт -1
return -1;
// Получаю секцию
$section = match ($config->Driver) {
DBDriver::MySQL, DBDriver::SQLite => "COUNT(*)",
DBDriver::MSSQL, DBDriver::OracleDB, DBDriver::PostgreSQL => ""
};
// Вывожу количество
return isset($countResult[0][$section]) ? (int)$countResult[0][$section] : -1;
}
/**
* Удаляет строки по условию.
*
* @param string $table Имя таблицы
* @param array $where Массив условий
*
* @return bool Результат выполнения
*/
public function Delete (string $table, array $where = []): bool
{
// Обработанные параметры
$params = [];
// Строковая интерпретация массива условий
$sql_where = $this->PrepareQueryWhere(where: $where, params: $params);
// Создаю запрос
$sql = "DELETE FROM $this->DBSignOpen$table$this->DBSignClose";
// Если заданы where-параметры
if (count($where) > 0) {
// - то добавляю их
$sql .= " WHERE $sql_where";
}
// Выполняю запрос
$count = $this->Execute($sql, $params);
// Если результат - false
if ($count === false)
// - то и общий результат - false
return false;
// Если изменено 0 строк
if ($count === 0)
// - то и общий результат - false
return false;
// Вывожу результат -- успех
return true;
}
}