From 8436569ce5a6b3adf727092290df8ba4e9f885ac Mon Sep 17 00:00:00 2001 From: babaev-an Date: Tue, 29 Jul 2025 17:50:57 +0300 Subject: [PATCH] 20250729 --- .idea/codeStyles/Project.xml | 70 + .idea/codeStyles/codeStyleConfig.xml | 5 + sources/classes/Database.php | 1242 ++++++----------- sources/interfaces/IDBItem.php | 32 + sources/interfaces/IStoredAtSQL.php | 32 - sources/traits/Database/DatabaseInsert.php | 153 ++ .../traits/Database/DatabaseQueryExecute.php | 182 +++ sources/traits/Database/DatabaseSpecial.php | 248 ++++ .../traits/Database/DatabaseTransactions.php | 98 ++ 9 files changed, 1223 insertions(+), 839 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 sources/interfaces/IDBItem.php delete mode 100644 sources/interfaces/IStoredAtSQL.php create mode 100644 sources/traits/Database/DatabaseInsert.php create mode 100644 sources/traits/Database/DatabaseQueryExecute.php create mode 100644 sources/traits/Database/DatabaseSpecial.php create mode 100644 sources/traits/Database/DatabaseTransactions.php diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..b7a5a01 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,70 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/sources/classes/Database.php b/sources/classes/Database.php index 2448734..b8507f3 100644 --- a/sources/classes/Database.php +++ b/sources/classes/Database.php @@ -1,823 +1,451 @@ 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; - } - - /** - * Выполнение запроса. Обычно используется для операций, - * которые не возвращают никаких данных, кроме количества - * затронутых ими записей. Например, - * - * $FcmsDB->Execute('DELETE FROM table WHERE id=1'); - * - * @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) + + /** + * @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) { - // Результирующая строка - $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; + // Задаю конфигурацию + $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) { + $this->HandleException($e); + } + } + + /** + * Деструктор. Закрывает соединение с базой данных + */ + public function __destruct () + { + $this->DataBaseHandle = null; + } + + /** + * Получает набор строк в массиве данных, удовлетворяющий выборке + * + * @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; + } + + // Транзакции + use DatabaseTransactions; + + // Запросы Query и Execute + use DatabaseQueryExecute; + + // Вставка данных + use DatabaseInsert; + + // Приватные методы + use DatabaseSpecial; + + /** + * Получает первую строку в массиве данных, удовлетворяющую выборке + * + * @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); + } + + /** + * Получает колонку в массиве данных + * + * @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; - }, $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"; + + /** + * Получение значение единичного поля + * + * @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]; } - - // Получаю строку на основании запроса - $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; + + /** + * Заменяет данные в строке базы данных + * + * @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; } - - // Ключи 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; + + /** + * Проверяет, существует ли запись в таблице. + * + * @param string $table Имя таблицы + * @param array $where Массив условий + * + * @return bool Результат проверки + */ + public function IsExist (string $table, array $where = []): bool + { + // Вывожу результат + return $this->Count($table, $where) > 0; } - catch (PDOException $e) { - $this->HandleException($e); + + /** + * Подсчитывает количество строк, удовлетворяющих условию. + * + * @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 ($this->Config->Driver) { + DBDriver::MySQL, DBDriver::SQLite => "COUNT(*)", + DBDriver::MSSQL, DBDriver::OracleDB, DBDriver::PostgreSQL => "" + }; + + // Вывожу количество + return isset($countResult[0][$section]) ? (int)$countResult[0][$section] : -1; } - - // Вывожу последний 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; + + /** + * Удаляет строки по условию. + * + * @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; } - - // Вывожу результирующий массив - 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; - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/sources/interfaces/IDBItem.php b/sources/interfaces/IDBItem.php new file mode 100644 index 0000000..7f5106e --- /dev/null +++ b/sources/interfaces/IDBItem.php @@ -0,0 +1,32 @@ +ignore: array - игнорировать перечисленные поля. Когда массив пуст, то ничего + * игнорироваться не будет. По умолчанию - пустой массив. + * - allow: array - включать только перечисленные поля. Когда массив пуст, то все поля будут + * включены. По умолчанию - пустой массив. + * + * @return string|false В случае успеха выведет: id созданной записи, -1, если запись создана, но id не получен + * (глюк?) и false, если ошибка + */ + public function Insert (string $table, IDBItem $row, array $options = []): string|false + { + // Подготавливаю запрос + [$sql, $params] = $this->PrepareInsertSQL($table, $row, $options); + + // Выполняю запрос + $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 string $table Имя таблицы. + * @param array $options Параметры. + * @param IDBItem ...$sources Модели или классы, реализующие интерфейс IDBItem, для вставки. + * + * @return void + */ + public function InsertMany (string $table, array $options, IDBItem ...$sources): void + { + // Инициализирую транзакцию + $this->InitTransaction(); + + try { + + // Для каждого источника + foreach ($sources as $source) + // - вставляю строку + $this->Insert($table, $source, $options); + + // Если вставка успешна, то подтверждаю транзакцию + $this->Commit(); + } + catch (Exception $exception) { + // - если ошибка, то откатываю транзакцию + $this->RollBack(); + + // - и вывожу ошибку + $this->HandleException($exception); + } + } + + /** + * Подготавливает запрос для вставки строки в базу данных. + * + * @param string $table Имя таблицы. + * @param IDBItem $row Элемент. + * @param array $options Параметры. + * + * @return Tuple Возвращает [запрос, параметры запроса]. + */ + private function PrepareInsertSQL (string $table, IDBItem $row, array $options = []): Tuple + { + // Подготавливаю массив параметров + $params = $this->PrepareParamsArray(source: $row, options: $options); + + // Получаю ключи параметров + $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);"; + + // Возвращаю результат + return new Tuple($sql, $params); + } + } \ No newline at end of file diff --git a/sources/traits/Database/DatabaseQueryExecute.php b/sources/traits/Database/DatabaseQueryExecute.php new file mode 100644 index 0000000..22570f2 --- /dev/null +++ b/sources/traits/Database/DatabaseQueryExecute.php @@ -0,0 +1,182 @@ +DataBaseHandle->prepare($query, [PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY]); + + // Выполняю запрос + $STH->execute($params); + + // Указываю, что данные, которые я хочу получить, должны быть в ассоциативном массиве + $STH->setFetchMode(PDO::FETCH_ASSOC); + + // Получаю все данные + $result = $STH->fetchAll(); + } + catch (PDOException $e) { + $this->HandleException($e); + } + + // Вывожу результат + return $result; + } + + /** + * Выполняем запрос на получение одной строки (аналог 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 $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]; + } + + + /** + * Выполнение запроса. Обычно используется для операций, + * которые не возвращают никаких данных, кроме количества + * затронутых ими записей. Например, + * + * $db->Execute('DELETE FROM table WHERE id=1'); + * + * @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; + } + } \ No newline at end of file diff --git a/sources/traits/Database/DatabaseSpecial.php b/sources/traits/Database/DatabaseSpecial.php new file mode 100644 index 0000000..928a0c9 --- /dev/null +++ b/sources/traits/Database/DatabaseSpecial.php @@ -0,0 +1,248 @@ +OnException ?? fn (Exception $e) => die($e->getMessage()); + + // Выполняю обработчик исключений + $onException($exception); + } + + /** + * Подготавливает массив параметров + * + * @param IDBItem $source Объект со свойствами. + * @param bool $withId Добавлять ли id в массив. + * @param array $options Опции. + * + * @return array|false Подготовленный массив параметров или false в случае ошибки + */ + private function PrepareParamsArray (IDBItem $source, bool $withId = true, array $options = []): array|false + { + // Создаём результирующий массив + $result = []; + + // Если есть игнорируемые или разрешенные свойства + if (!(count($options['ignore']) == 0 && count($options['allow']) == 0)) + // -- то для каждого игнорируемого свойства + foreach ($options['ignored'] as $ignoredProperty) + // --- и если оно есть в массиве разрешенных + if (in_array($ignoredProperty, $options['allowed'])) + // ---- то исключаю его из массива разрешенных + unset($options['allowed'][array_search($ignoredProperty, $options['allowed'])]); + + // Получаю массив свойств + $properties = $source->ToSQL($withId); + + // Для каждого элемента массива + foreach ($properties as $name => $value) { + // - если свойство игнорируется + if (in_array($name, $options['ignored'])) + // -- пропускаю + continue; + + // - если свойство не разрешено + if (count($options['allowed']) > 0 && !in_array($name, $options['allowed'])) + // -- пропускаю + continue; + + // - если свойство является объектом + if (is_object($source->$name)) { + try { + // -- пытаюсь преобразовать его в массив + self::AddArrayItem($result, $name, json_encode(TypeExtension::ToArray($source->$name), + JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); + } + catch (TypeException) { + // -- если не получилось, то сериализую его + self::AddArrayItem($result, $name, + json_encode($source->$name, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); + } + + // -- пропускаю + continue; + } + + // - если свойство является массивом + if (is_array($source->$name)) { + // -- сериализую его + self::AddArrayItem($result, $name, json_encode($source->$name, JSON_UNESCAPED_UNICODE | + JSON_PRETTY_PRINT)); + + // -- пропускаю + continue; + } + + // - иначе просто добавляю свойство в массив + self::AddArrayItem($result, $name, $source->$name); + } + + // Вывожу результирующий массив + 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); + } + + /** + * Генерирует 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; + } + } \ No newline at end of file diff --git a/sources/traits/Database/DatabaseTransactions.php b/sources/traits/Database/DatabaseTransactions.php new file mode 100644 index 0000000..bba7c4e --- /dev/null +++ b/sources/traits/Database/DatabaseTransactions.php @@ -0,0 +1,98 @@ +InTransaction()) + // - если находится, то выбрасываем исключение. + throw new PDOException("Транзакция уже начата! / Transaction already started!"); + + // Начинаем транзакцию + $isSuccess = $this->DataBaseHandle->beginTransaction(); + + // Если транзакция не началась + if (!$isSuccess) + // - то выбрасываем исключение. + throw new PDOException("Ошибка при начале транзакции! / Transaction start error!"); + } + + /** + * Проверяет, находится ли текущая транзакция в процессе. + * + * @return bool Результат проверки: true - в процессе, false - нет. + */ + public function InTransaction (): bool + { + return $this->DataBaseHandle->inTransaction(); + } + + /** + * Отправляет транзакцию в базу данных. + * + * @return void + * @throws PDOException Если транзакция не начата или не отправлена. + */ + public function Commit (): void + { + // Проверка, не находится ли текущая транзакция в процессе + if (!$this->InTransaction()) + // - если нет, то выбрасываем исключение. + throw new PDOException("Транзакция не начата! / Transaction not started!"); + + // Отправляем транзакцию в базу данных + $isSuccess = $this->DataBaseHandle->commit(); + + // Если транзакция не отправлена + if (!$isSuccess) + // - то выбрасываем исключение. + throw new PDOException("Ошибка при отправке транзакции в базу данных! / Transaction send error!"); + } + + /** + * Откатывает транзакцию. + * + * @return void + * @throws PDOException Если транзакция не начата или не откатана. + */ + public function RollBack (): void + { + // Проверка, не находится ли текущая транзакция в процессе + if (!$this->InTransaction()) + // - если нет, то выбрасываем исключение. + throw new PDOException("Транзакция не начата! / Transaction not started!"); + + // Откатываем транзакцию + $isSuccess = $this->DataBaseHandle->rollBack(); + + // Если транзакция не откатана + if (!$isSuccess) + // - то выбрасываем исключение. + throw new PDOException("Ошибка при откате транзакции! / Transaction rollback error!"); + } + } \ No newline at end of file