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\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);
// Добавляю оператор

View File

@@ -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 Возвращает правильно отформатированный оператор или <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'];
@@ -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);
}
}

View File

@@ -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, то это ошибка. Выбрасываем
// исключение.

View File

@@ -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);

View File

@@ -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('"', '"'),
};
}
}

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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]"
};
}
}