This commit is contained in:
2025-08-19 23:38:52 +03:00
parent 5074629e40
commit cc3b1ef41b
8 changed files with 189 additions and 157 deletions

View File

@@ -5,6 +5,7 @@
use goodboyalex\php_components_pack\classes\Tuple; use goodboyalex\php_components_pack\classes\Tuple;
use goodboyalex\php_components_pack\extensions\StringExtension; use goodboyalex\php_components_pack\extensions\StringExtension;
use goodboyalex\php_components_pack\interfaces\IArrayable; use goodboyalex\php_components_pack\interfaces\IArrayable;
use goodboyalex\php_db_components_pack\enums\DBDriver;
use PHPUnit\Event\InvalidArgumentException; use PHPUnit\Event\InvalidArgumentException;
/** /**
@@ -101,15 +102,19 @@
/** /**
* Формирует условие. * Формирует условие.
* *
* @param DBDriver $driver Тип драйвера СУБД.
* @param int $index Индекс замены параметров для защиты от SQL-инъекций. * @param int $index Индекс замены параметров для защиты от SQL-инъекций.
* *
* @return Tuple (string, array) Сформированное условие: 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
$sql = (!str_starts_with($this->ColumnName, "FUNC:")) $sql = (!str_starts_with($this->ColumnName, "FUNC:"))
? "`$this->ColumnName`" ? "$signOpen$this->ColumnName$signClose"
: StringExtension::Replace('FUNC:', '', $this->ColumnName); : StringExtension::Replace('FUNC:', '', $this->ColumnName);
// Добавляю оператор // Добавляю оператор

View File

@@ -5,6 +5,7 @@
use goodboyalex\php_components_pack\classes\Tuple; use goodboyalex\php_components_pack\classes\Tuple;
use goodboyalex\php_components_pack\extensions\ArrayExtension; use goodboyalex\php_components_pack\extensions\ArrayExtension;
use goodboyalex\php_components_pack\interfaces\IArrayable; 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\ConditionBuilderConditionsSet;
use goodboyalex\php_db_components_pack\traits\ConditionBuilder\ConditionBuilderLogicalOperationSet; use goodboyalex\php_db_components_pack\traits\ConditionBuilder\ConditionBuilderLogicalOperationSet;
use InvalidArgumentException; use InvalidArgumentException;
@@ -64,7 +65,9 @@
// --- если ошибка // --- если ошибка
if ($builder === false) if ($builder === false)
// ---- то выбрасываем исключение // ---- то выбрасываем исключение
throw new InvalidArgumentException("Неверный логический оператор: $condition / The logical operator is invalid: $condition."); throw new InvalidArgumentException(
"Неверный логический оператор: $condition / The logical operator is invalid: $condition."
);
// --- идём к следующему элементу // --- идём к следующему элементу
continue; continue;
@@ -93,7 +96,7 @@
* @return false|string Возвращает правильно отформатированный оператор или <code>false</code>, если оператор * @return false|string Возвращает правильно отформатированный оператор или <code>false</code>, если оператор
* не является логическим. * не является логическим.
*/ */
private static function PrepareLogicalOperator (string $operator): false|string private static function PrepareLogicalOperator (string $operator): false | string
{ {
// Задаем массив логических операторов // Задаем массив логических операторов
$logicalOperators = ['AND', 'OR', 'NOT', 'XOR', 'NAND', 'NOR']; $logicalOperators = ['AND', 'OR', 'NOT', 'XOR', 'NAND', 'NOR'];
@@ -178,6 +181,81 @@
return new ConditionGroup($operator, $conditionItems); 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-инъекций. * Собирает условие в виде пригодном для SQL. Также возвращает массив параметров защиты от SQL-инъекций.
* *
* @param DBDriver $driver Тип драйвера СУБД.
*
* @return Tuple (string, array) Возвращает условие в виде, пригодном для SQL и массив параметров для защиты от * @return Tuple (string, array) Возвращает условие в виде, пригодном для SQL и массив параметров для защиты от
* SQL-инъекций. * SQL-инъекций.
*/ */
public function Build (): Tuple public function Build (DBDriver $driver): Tuple
{ {
// Очищаем цепочку от пустых элементов // Очищаем цепочку от пустых элементов
ArrayExtension::RemoveEmpties($this->Conditions); ArrayExtension::RemoveEmpties($this->Conditions);
// Парсим цепочку и возвращаем результат // Парсим цепочку и возвращаем результат
return $this->ProcessConditions($this->Conditions); return self::ProcessConditions($driver, $this->Conditions);
} }
/** /**
@@ -337,78 +417,4 @@
// Возвращаем объект // Возвращаем объект
return $this; 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);
}
} }

View File

@@ -8,6 +8,7 @@
use goodboyalex\php_components_pack\classes\Tuple; use goodboyalex\php_components_pack\classes\Tuple;
use goodboyalex\php_components_pack\interfaces\IArrayable; use goodboyalex\php_components_pack\interfaces\IArrayable;
use goodboyalex\php_db_components_pack\enums\DBDriver;
use InvalidArgumentException; use InvalidArgumentException;
/** /**
@@ -45,11 +46,12 @@
/** /**
* Формирует массив условий. * Формирует массив условий.
* *
* @param DBDriver $driver Тип драйвера СУБД.
* @param int $index Индекс замены параметров для защиты от SQL-инъекций. * @param int $index Индекс замены параметров для защиты от SQL-инъекций.
* *
* @return Tuple (string, array) Массив условий (строка SQL, параметры SQL). * @return Tuple (string, array) Массив условий (строка SQL, параметры SQL).
*/ */
public function GetConditions (int $index = 0): Tuple public function GetConditions (DBDriver $driver, int $index = 0): Tuple
{ {
// Создаём результирующую строку // Создаём результирующую строку
$resultString = ""; $resultString = "";
@@ -66,7 +68,7 @@
$count++; $count++;
// - получаем условие // - получаем условие
$result = $this->WriteCondition($this->Conditions[$i], $count); $result = $this->WriteCondition($driver, $this->Conditions[$i], $count);
// - записываем условие в строку // - записываем условие в строку
$resultString .= $result->Get(0); $resultString .= $result->Get(0);
@@ -182,22 +184,23 @@
/** /**
* Формирует условие. * Формирует условие.
* *
* @param DBDriver $driver Тип драйвера СУБД.
* @param mixed $condition Условие. * @param mixed $condition Условие.
* @param int $index Индекс замены параметров для защиты от SQL-инъекций. * @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 // Проверяем, является ли условие объектом класса Condition
if ($condition instanceof Condition) if ($condition instanceof Condition)
// - если да, то возвращаем его значение // - если да, то возвращаем его значение
return $condition->Get($index); return $condition->Get($driver, $index);
// Проверяем, является ли условие объектом класса ConditionGroup // Проверяем, является ли условие объектом класса ConditionGroup
if ($condition instanceof ConditionGroup) if ($condition instanceof ConditionGroup)
// - если да, то возвращаем его значения // - если да, то возвращаем его значения
return $condition->GetConditions($index); return $condition->GetConditions($driver, $index);
// Если условие не является ни классом Condition, ни классом ConditionGroup, то это ошибка. Выбрасываем // Если условие не является ни классом Condition, ни классом ConditionGroup, то это ошибка. Выбрасываем
// исключение. // исключение.

View File

@@ -36,16 +36,6 @@
*/ */
private ?PDO $DataBaseHandle; private ?PDO $DataBaseHandle;
/**
* @var string $DBSignOpen Символ открытия для подстановки в запросы к базе.
*/
private string $DBSignOpen;
/**
* @var string $DBSignСlose Символ закрытия для подстановки в запросы к базе.
*/
private string $DBSignClose;
/** /**
* @var Closure $OnException Обработчик исключений. Анонимная функция формата: * @var Closure $OnException Обработчик исключений. Анонимная функция формата:
* *
@@ -96,21 +86,6 @@
DBDriver::SQLite => "sqlite:$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 = new PDO($dsn, username: $user, password: $password);

View File

@@ -2,6 +2,7 @@
namespace goodboyalex\php_db_components_pack\enums; namespace goodboyalex\php_db_components_pack\enums;
use goodboyalex\php_components_pack\classes\Tuple;
use goodboyalex\php_components_pack\traits\EnumExtensionsTrait; use goodboyalex\php_components_pack\traits\EnumExtensionsTrait;
/** /**
@@ -42,4 +43,20 @@
* SQLite * SQLite
*/ */
case SQLite = 4; 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('"', '"'),
};
}
} }

View File

@@ -38,8 +38,11 @@
*/ */
[$sql_where, $params] = $where->Build(); [$sql_where, $params] = $where->Build();
// Подготавливаю имя таблицы
$table = $this->PrepareTableName($table);
// Создаю запрос // Создаю запрос
$sql = "SELECT COUNT(*) FROM $this->DBSignOpen$table$this->DBSignClose"; $sql = "SELECT COUNT(*) FROM $table";
// Если заданы where-параметры // Если заданы where-параметры
if ($where->Count() > 0) if ($where->Count() > 0)

View File

@@ -11,6 +11,7 @@
use goodboyalex\php_db_components_pack\enums\DBDriver; use goodboyalex\php_db_components_pack\enums\DBDriver;
use goodboyalex\php_db_components_pack\enums\DBOperation; use goodboyalex\php_db_components_pack\enums\DBOperation;
use goodboyalex\php_db_components_pack\interfaces\IDBItem; use goodboyalex\php_db_components_pack\interfaces\IDBItem;
use goodboyalex\php_db_components_pack\models\DBItemProperty;
use PDO; use PDO;
/** /**
@@ -36,7 +37,7 @@
public function Insert (string $table, IDBItem $row): mixed 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); $count = $this->Execute($sql, $params);
@@ -48,7 +49,7 @@
// Задаю переменную для последнего id // Задаю переменную для последнего id
$lastId = match ($this->Config->Driver) { $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::MySQL, DBDriver::SQLite => $this->DataBaseHandle->lastInsertId(),
DBDriver::PostgreSQL, DBDriver::OracleDB => $this->DataBaseHandle->lastInsertId('sequence_name') DBDriver::PostgreSQL, DBDriver::OracleDB => $this->DataBaseHandle->lastInsertId('sequence_name')
}; };
@@ -58,6 +59,11 @@
// - то вывожу просто true // - то вывожу просто true
$lastId = true; $lastId = true;
// Если id не генерировался
if ($lastId === null)
// - то вывожу -1
$lastId = $primaryKeyValue !== "NULL" ? $primaryKeyValue : true;
// Вывожу последний id // Вывожу последний id
return $lastId; return $lastId;
} }
@@ -106,12 +112,35 @@
* @param string $table Имя таблицы. * @param string $table Имя таблицы.
* @param IDBItem $row Элемент. * @param IDBItem $row Элемент.
* *
* @return Tuple Возвращает [запрос, параметры запроса]. * @return Tuple Возвращает [запрос, параметры запроса, значение первичного ключа].
*/ */
private function PrepareInsertSQL (string $table, IDBItem $row): 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); $keys = array_keys($params);
@@ -140,10 +169,26 @@
// Значения sql запроса // Значения sql запроса
$sql_values = implode(', ', $keys_values); $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);
} }
} }

View File

@@ -23,6 +23,7 @@
use goodboyalex\php_db_components_pack\attributes\PrimaryKey; use goodboyalex\php_db_components_pack\attributes\PrimaryKey;
use goodboyalex\php_db_components_pack\attributes\Unique; use goodboyalex\php_db_components_pack\attributes\Unique;
use goodboyalex\php_db_components_pack\classes\ConditionBuilder; 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\DBOperation;
use goodboyalex\php_db_components_pack\enums\DBType; use goodboyalex\php_db_components_pack\enums\DBType;
use goodboyalex\php_db_components_pack\interfaces\IDBItem; 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; 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]"
};
}
} }