From cc3b1ef41bb071fbc80f0901aaff1c2630c58553 Mon Sep 17 00:00:00 2001 From: babaev-an Date: Tue, 19 Aug 2025 23:38:52 +0300 Subject: [PATCH] 20250819 --- sources/classes/Condition.php | 9 +- sources/classes/ConditionBuilder.php | 162 +++++++++--------- sources/classes/ConditionGroup.php | 15 +- sources/classes/Database.php | 25 --- sources/enums/DBDriver.php | 17 ++ .../traits/Database/DatabaseCountExist.php | 5 +- sources/traits/Database/DatabaseInsert.php | 57 +++++- sources/traits/Database/DatabaseSpecial.php | 56 ++---- 8 files changed, 189 insertions(+), 157 deletions(-) diff --git a/sources/classes/Condition.php b/sources/classes/Condition.php index d6cbb3b..e22ac1b 100644 --- a/sources/classes/Condition.php +++ b/sources/classes/Condition.php @@ -5,6 +5,7 @@ use goodboyalex\php_components_pack\classes\Tuple; use goodboyalex\php_components_pack\extensions\StringExtension; use goodboyalex\php_components_pack\interfaces\IArrayable; + use goodboyalex\php_db_components_pack\enums\DBDriver; use PHPUnit\Event\InvalidArgumentException; /** @@ -101,15 +102,19 @@ /** * Формирует условие. * + * @param DBDriver $driver Тип драйвера СУБД. * @param int $index Индекс замены параметров для защиты от SQL-инъекций. * * @return Tuple (string, array) Сформированное условие: SQL-запрос и параметры запроса. */ - public function Get (int $index = 0): Tuple + public function Get (DBDriver $driver, int $index = 0): Tuple { + // Получаю знаки открытия и закрытия + [$signOpen, $signClose] = $driver->GetSigns($driver); + // Начинаю формировать SQL $sql = (!str_starts_with($this->ColumnName, "FUNC:")) - ? "`$this->ColumnName`" + ? "$signOpen$this->ColumnName$signClose" : StringExtension::Replace('FUNC:', '', $this->ColumnName); // Добавляю оператор diff --git a/sources/classes/ConditionBuilder.php b/sources/classes/ConditionBuilder.php index faba0a9..30b6748 100644 --- a/sources/classes/ConditionBuilder.php +++ b/sources/classes/ConditionBuilder.php @@ -5,6 +5,7 @@ use goodboyalex\php_components_pack\classes\Tuple; use goodboyalex\php_components_pack\extensions\ArrayExtension; use goodboyalex\php_components_pack\interfaces\IArrayable; + use goodboyalex\php_db_components_pack\enums\DBDriver; use goodboyalex\php_db_components_pack\traits\ConditionBuilder\ConditionBuilderConditionsSet; use goodboyalex\php_db_components_pack\traits\ConditionBuilder\ConditionBuilderLogicalOperationSet; use InvalidArgumentException; @@ -64,7 +65,9 @@ // --- если ошибка if ($builder === false) // ---- то выбрасываем исключение - throw new InvalidArgumentException("Неверный логический оператор: $condition / The logical operator is invalid: $condition."); + throw new InvalidArgumentException( + "Неверный логический оператор: $condition / The logical operator is invalid: $condition." + ); // --- идём к следующему элементу continue; @@ -93,7 +96,7 @@ * @return false|string Возвращает правильно отформатированный оператор или false, если оператор * не является логическим. */ - private static function PrepareLogicalOperator (string $operator): false|string + private static function PrepareLogicalOperator (string $operator): false | string { // Задаем массив логических операторов $logicalOperators = ['AND', 'OR', 'NOT', 'XOR', 'NAND', 'NOR']; @@ -178,6 +181,81 @@ return new ConditionGroup($operator, $conditionItems); } + /** + * Собирает условие в виде, пригодном для SQL. + * + * @param DBDriver $driver Тип драйвера СУБД. + * @param array $conditions Условия. + * + * @return Tuple (string, array) Возвращает условие в виде, пригодном для SQL и массив параметров для защиты + * от SQL-инъекций. + */ + private static function ProcessConditions (DBDriver $driver, array $conditions): Tuple + { + // Задаём массив частей + $parts = []; + + // Задаём массив параметров для защиты от SQL-инъекций + $params = []; + + // Задаём счётчик + $count = 0; + + // Перебираем условия + foreach ($conditions as $condition) { + // - если это группа условий + if ($condition instanceof ConditionGroup) { + // -- парсим её и добавляем результат + $result = $condition->GetConditions($count); + + // -- добавляем условие в массив частей + $parts[] = $result->Get(0); + + // -- добавляем параметры для защиты от SQL-инъекций в массив + $params = array_merge($params, $result->Get(1)); + + // -- увеличиваем счётчик + $count = $count + count($result->Get(1)); + + // -- идём к следующему элементу + continue; + } + + // - если это условие + if ($condition instanceof Condition) { + // -- парсим его + $result = $condition->Get($driver, $count); + + // -- добавляем условие в массив частей + $parts[] = $result->Get(0); + + // -- добавляем параметры для защиты от SQL-инъекций в массив + $params = array_merge($params, $result->Get(1)); + + // -- увеличиваем счётчик на 1 + $count++; + + // -- идём к следующему элементу + continue; + } + + + // - иначе считаем, что это логический оператор, проверим это + $condition = self::PrepareLogicalOperator($condition); + + // - если это не логический оператор + if ($condition === false) + // -- то пропускаем его + continue; + + // - добавляем его в массив частей + $parts[] = $condition; + } + + // Возвращаем результат + return new Tuple(implode(' ', $parts), $params); + } + /** * Добавляет группу условий. * @@ -194,16 +272,18 @@ /** * Собирает условие в виде пригодном для SQL. Также возвращает массив параметров защиты от SQL-инъекций. * + * @param DBDriver $driver Тип драйвера СУБД. + * * @return Tuple (string, array) Возвращает условие в виде, пригодном для SQL и массив параметров для защиты от * SQL-инъекций. */ - public function Build (): Tuple + public function Build (DBDriver $driver): Tuple { // Очищаем цепочку от пустых элементов ArrayExtension::RemoveEmpties($this->Conditions); // Парсим цепочку и возвращаем результат - return $this->ProcessConditions($this->Conditions); + return self::ProcessConditions($driver, $this->Conditions); } /** @@ -337,78 +417,4 @@ // Возвращаем объект return $this; } - - /** - * Собирает условие в виде, пригодном для SQL. - * - * @param array $conditions Условия. - * - * @return Tuple (string, array) Возвращает условие в виде, пригодном для SQL и массив параметров для защиты - * от SQL-инъекций. - */ - private function ProcessConditions (array $conditions): Tuple - { - // Задаём массив частей - $parts = []; - - // Задаём массив параметров для защиты от SQL-инъекций - $params = []; - - // Задаём счётчик - $count = 0; - - // Перебираем условия - foreach ($conditions as $condition) { - // - если это группа условий - if ($condition instanceof ConditionGroup) { - // -- парсим её и добавляем результат - $result = $condition->GetConditions($count); - - // -- добавляем условие в массив частей - $parts[] = $result->Get(0); - - // -- добавляем параметры для защиты от SQL-инъекций в массив - $params = array_merge($params, $result->Get(1)); - - // -- увеличиваем счётчик - $count = $count + count($result->Get(1)); - - // -- идём к следующему элементу - continue; - } - - // - если это условие - if ($condition instanceof Condition) { - // -- парсим его - $result = $condition->Get($count); - - // -- добавляем условие в массив частей - $parts[] = $result->Get(0); - - // -- добавляем параметры для защиты от SQL-инъекций в массив - $params = array_merge($params, $result->Get(1)); - - // -- увеличиваем счётчик на 1 - $count++; - - // -- идём к следующему элементу - continue; - } - - - // - иначе считаем, что это логический оператор, проверим это - $condition = self::PrepareLogicalOperator($condition); - - // - если это не логический оператор - if ($condition === false) - // -- то пропускаем его - continue; - - // - добавляем его в массив частей - $parts[] = $condition; - } - - // Возвращаем результат - return new Tuple(implode(' ', $parts), $params); - } } \ No newline at end of file diff --git a/sources/classes/ConditionGroup.php b/sources/classes/ConditionGroup.php index 5d93f8f..8d393d8 100644 --- a/sources/classes/ConditionGroup.php +++ b/sources/classes/ConditionGroup.php @@ -8,6 +8,7 @@ use goodboyalex\php_components_pack\classes\Tuple; use goodboyalex\php_components_pack\interfaces\IArrayable; + use goodboyalex\php_db_components_pack\enums\DBDriver; use InvalidArgumentException; /** @@ -45,11 +46,12 @@ /** * Формирует массив условий. * + * @param DBDriver $driver Тип драйвера СУБД. * @param int $index Индекс замены параметров для защиты от SQL-инъекций. * * @return Tuple (string, array) Массив условий (строка SQL, параметры SQL). */ - public function GetConditions (int $index = 0): Tuple + public function GetConditions (DBDriver $driver, int $index = 0): Tuple { // Создаём результирующую строку $resultString = ""; @@ -66,7 +68,7 @@ $count++; // - получаем условие - $result = $this->WriteCondition($this->Conditions[$i], $count); + $result = $this->WriteCondition($driver, $this->Conditions[$i], $count); // - записываем условие в строку $resultString .= $result->Get(0); @@ -182,22 +184,23 @@ /** * Формирует условие. * + * @param DBDriver $driver Тип драйвера СУБД. * @param mixed $condition Условие. * @param int $index Индекс замены параметров для защиты от SQL-инъекций. * - * @return string Возвращает условие в виде строки SQL. + * @return Tuple Возвращает условие в виде строки SQL. */ - private function WriteCondition (mixed $condition, int $index = 0): Tuple + private function WriteCondition (DBDriver $driver, mixed $condition, int $index = 0): Tuple { // Проверяем, является ли условие объектом класса Condition if ($condition instanceof Condition) // - если да, то возвращаем его значение - return $condition->Get($index); + return $condition->Get($driver, $index); // Проверяем, является ли условие объектом класса ConditionGroup if ($condition instanceof ConditionGroup) // - если да, то возвращаем его значения - return $condition->GetConditions($index); + return $condition->GetConditions($driver, $index); // Если условие не является ни классом Condition, ни классом ConditionGroup, то это ошибка. Выбрасываем // исключение. diff --git a/sources/classes/Database.php b/sources/classes/Database.php index de042c2..911a973 100644 --- a/sources/classes/Database.php +++ b/sources/classes/Database.php @@ -36,16 +36,6 @@ */ private ?PDO $DataBaseHandle; - /** - * @var string $DBSignOpen Символ открытия для подстановки в запросы к базе. - */ - private string $DBSignOpen; - - /** - * @var string $DBSignСlose Символ закрытия для подстановки в запросы к базе. - */ - private string $DBSignClose; - /** * @var Closure $OnException Обработчик исключений. Анонимная функция формата: * @@ -96,21 +86,6 @@ 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); diff --git a/sources/enums/DBDriver.php b/sources/enums/DBDriver.php index 8a0cb58..243805e 100644 --- a/sources/enums/DBDriver.php +++ b/sources/enums/DBDriver.php @@ -2,6 +2,7 @@ namespace goodboyalex\php_db_components_pack\enums; + use goodboyalex\php_components_pack\classes\Tuple; use goodboyalex\php_components_pack\traits\EnumExtensionsTrait; /** @@ -42,4 +43,20 @@ * SQLite */ case SQLite = 4; + + /** + * Получить знаки открытия/закрытия полей для СУБД. + * + * @param DBDriver $driver Драйвер СУБД. + * + * @return Tuple Возвращает кортеж [знак открытия, знак закрытия]. + */ + public static function GetSigns (DBDriver $driver): Tuple + { + return match ($driver) { + DBDriver::MySQL, DBDriver::SQLite => new Tuple('`', '`'), + DBDriver::MSSQL => new Tuple('[', ']'), + DBDriver::PostgreSQL, DBDriver::OracleDB => new Tuple('"', '"'), + }; + } } \ No newline at end of file diff --git a/sources/traits/Database/DatabaseCountExist.php b/sources/traits/Database/DatabaseCountExist.php index 7ec057f..3f98fdd 100644 --- a/sources/traits/Database/DatabaseCountExist.php +++ b/sources/traits/Database/DatabaseCountExist.php @@ -38,8 +38,11 @@ */ [$sql_where, $params] = $where->Build(); + // Подготавливаю имя таблицы + $table = $this->PrepareTableName($table); + // Создаю запрос - $sql = "SELECT COUNT(*) FROM $this->DBSignOpen$table$this->DBSignClose"; + $sql = "SELECT COUNT(*) FROM $table"; // Если заданы where-параметры if ($where->Count() > 0) diff --git a/sources/traits/Database/DatabaseInsert.php b/sources/traits/Database/DatabaseInsert.php index 5d2cf02..92ae7d9 100644 --- a/sources/traits/Database/DatabaseInsert.php +++ b/sources/traits/Database/DatabaseInsert.php @@ -11,6 +11,7 @@ use goodboyalex\php_db_components_pack\enums\DBDriver; use goodboyalex\php_db_components_pack\enums\DBOperation; use goodboyalex\php_db_components_pack\interfaces\IDBItem; + use goodboyalex\php_db_components_pack\models\DBItemProperty; use PDO; /** @@ -36,7 +37,7 @@ public function Insert (string $table, IDBItem $row): mixed { // Подготавливаю запрос - [$sql, $params] = $this->PrepareInsertSQL($table, $row); + [$sql, $params, $primaryKeyValue] = $this->PrepareInsertSQL($table, $row); // Выполняю запрос $count = $this->Execute($sql, $params); @@ -48,7 +49,7 @@ // Задаю переменную для последнего id $lastId = match ($this->Config->Driver) { - DBDriver::MSSQL => $this->DataBaseHandle->query('SELECT SCOPE_IDENTITY()')->fetchColumn(), + DBDriver::MSSQL => $this->Query('SELECT SCOPE_IDENTITY() AS last_inserted_id;')['last_inserted_id'], DBDriver::MySQL, DBDriver::SQLite => $this->DataBaseHandle->lastInsertId(), DBDriver::PostgreSQL, DBDriver::OracleDB => $this->DataBaseHandle->lastInsertId('sequence_name') }; @@ -58,6 +59,11 @@ // - то вывожу просто true $lastId = true; + // Если id не генерировался + if ($lastId === null) + // - то вывожу -1 + $lastId = $primaryKeyValue !== "NULL" ? $primaryKeyValue : true; + // Вывожу последний id return $lastId; } @@ -106,12 +112,35 @@ * @param string $table Имя таблицы. * @param IDBItem $row Элемент. * - * @return Tuple Возвращает [запрос, параметры запроса]. + * @return Tuple Возвращает [запрос, параметры запроса, значение первичного ключа]. */ private function PrepareInsertSQL (string $table, IDBItem $row): Tuple { // Подготавливаю массив параметров - $params = $this->PrepareParamsArray(source: $row, operation: DBOperation::Insert); + $params = []; + + // Получаю массив свойств + $properties = self::GetProperties($row, DBOperation::Insert); + + /** + * Для каждого свойства... + * + * @var DBItemProperty $property Свойство. + */ + foreach ($properties as $property) { + // - пропускаю игнорируемые поля + if ($property->IsIgnored) + continue; + + // - получаю значение имени поля + $fieldName = $property->Column->Name; + + // - преобразую тип + $value = call_user_func($property->ConvertToDB, $property->Value); + + // - добавляю в массив + $params[$fieldName] = $value; + } // Получаю ключи параметров $keys = array_keys($params); @@ -140,10 +169,26 @@ // Значения sql запроса $sql_values = implode(', ', $keys_values); + // Подготавливаю имя таблицы + $table = $this->PrepareTableName($table); + // Создаю запрос - $sql = "INSERT INTO $this->DBSignOpen$table$this->DBSignClose ($sql_keys) VALUES ($sql_values);"; + $sql = "INSERT INTO $table ($sql_keys) VALUES ($sql_values);"; + + /** + * Получаю первичный ключ таблицы. + * + * @var false|DBItemProperty $key Первичный ключ таблицы. + */ + $key = $properties->GetRow( + selectCondition: fn (DBItemProperty $property): bool => $property->Column->IsPrimaryKey + ); + + // Передаю первичный ключ в переменную + $pKey = $key === false ? 'NULL' : $key->Value ?? "NULL"; + // Возвращаю результат - return new Tuple($sql, $params); + return new Tuple($sql, $params, $pKey); } } \ No newline at end of file diff --git a/sources/traits/Database/DatabaseSpecial.php b/sources/traits/Database/DatabaseSpecial.php index e4fcc93..6659386 100644 --- a/sources/traits/Database/DatabaseSpecial.php +++ b/sources/traits/Database/DatabaseSpecial.php @@ -23,6 +23,7 @@ use goodboyalex\php_db_components_pack\attributes\PrimaryKey; use goodboyalex\php_db_components_pack\attributes\Unique; use goodboyalex\php_db_components_pack\classes\ConditionBuilder; + use goodboyalex\php_db_components_pack\enums\DBDriver; use goodboyalex\php_db_components_pack\enums\DBOperation; use goodboyalex\php_db_components_pack\enums\DBType; use goodboyalex\php_db_components_pack\interfaces\IDBItem; @@ -283,45 +284,6 @@ }; } - /** - * Подготавливает массив параметров - * - * @param IDBItem $source Объект со свойствами. - * @param DBOperation $operation Текущая операция. - * - * @return array|false Подготовленный массив параметров или false в случае ошибки - */ - private function PrepareParamsArray (IDBItem $source, DBOperation $operation): array | false - { - $result = []; - - // Получаю массив свойств - $properties = self::GetProperties($source, $operation); - - /** - * Для каждого свойства... - * - * @var DBItemProperty $property Свойство. - */ - foreach ($properties as $property) { - // - пропускаю игнорируемые поля - if ($property->IsIgnored) - continue; - - // - получаю значение имени поля - $fieldName = $property->Column->Name; - - // - преобразую тип - $value = call_user_func($property->ConvertToDB, $property->Value); - - // - добавляю в массив - $result[$fieldName] = $value; - } - - // Возвращаю результат - return $result; - } - /** * Восстанавливает объект из БД. * @@ -492,4 +454,20 @@ // Возвращаю запрос return $sql; } + + /** + * Генерирует имя таблицы для использования в запросах. + * + * @param string $table Имя таблицы. + * + * @return string Готовое имя таблицы для использования в запросах. + */ + private function PrepareTableName (string $table): string + { + return match ($this->Config->Driver) { + DBDriver::MySQL, DBDriver::SQLite, DBDriver::OracleDB, DBDriver::PostgreSQL => $this->DBSignOpen + . $table . $this->DBSignClose, + DBDriver::MSSQL => "[dbo].[$table]" + }; + } } \ No newline at end of file