This commit is contained in:
2025-08-01 18:29:11 +03:00
parent 2f3dd81d0a
commit ccdcc3e047
10 changed files with 1226 additions and 51 deletions

View File

@@ -0,0 +1,158 @@
<?php
namespace goodboyalex\php_db_components_pack\classes;
use goodboyalex\php_components_pack\classes\Tuple;
use goodboyalex\php_components_pack\interfaces\IArrayable;
use PHPUnit\Event\InvalidArgumentException;
/**
* Условие запроса выборки по условию.
*
* @author Александр Бабаев
* @package php_db_components_pack
* @version 1.0
* @since 1.0
*/
final class Condition implements IArrayable
{
/**
* Оператор логического И.
*/
public const string LOGIC_AND = 'AND';
/**
* Оператор логического ИЛИ.
*/
public const string LOGIC_OR = 'OR';
/**
* Оператор логического НЕ.
*/
public const string LOGIC_NOT = 'NOT';
/**
* Оператор логического ИСКЛЮЧАЮЩЕГО ИЛИ.
*/
public const string LOGIC_XOR = 'XOR';
/**
* Оператор логического НЕ И.
*/
public const string LOGIC_NAND = 'NAND';
/**
* Оператор логического НЕ ИЛИ.
*/
public const string LOGIC_NOR = 'NOR';
/**
* @var string $ColumnName Имя колонки.
*/
private string $ColumnName;
/**
* @var string $Operator Оператор.
*/
private string $Operator;
/**
* @var mixed $Value Значение.
*/
private mixed $Value;
/**
* Конструктор.
*
* @param string $columnName Имя колонки.
* @param string $operator Оператор.
* @param mixed $value Значение.
*/
public function __construct (string $columnName = "", string $operator = "=", mixed $value = null)
{
$this->ColumnName = $columnName;
$this->Operator = $operator;
$this->Value = $value;
}
/**
* Разбирает условие, заданное массивом и возвращает объект <code>Condition</code>.
*
* @param array $condition Условие, заданное в массиве.
*
* @return Condition Условие.
*/
public static function Parse (array $condition): Condition
{
// Проверка условия
if (count($condition) < 3)
// - если условие не содержит 3 элемента, то выбрасывается исключение
throw new InvalidArgumentException('Неверный формат условия / Unexpected condition format.');
// Разбор условия
[$column, $operator, $value] = $condition;
// Создание условия
return new Condition($column, $operator, $value);
}
/**
* Формирует условие.
*
* @param int $index Индекс замены параметров для защиты от SQL-инъекций.
*
* @return Tuple (string, array) Сформированное условие: SQL-запрос и параметры запроса.
*/
public function Get (int $index = 0): Tuple
{
// Формирование условия SQL
$sql = "`$this->ColumnName` $this->Operator :prop_$index";
// Формирование параметров запроса
$params = [":prop_$index" => $this->Value];
// Возвращение результата
return new Tuple($sql, $params);
}
/**
* @inheritDoc
*/
public function ToArray (): array
{
// Создаю массив
$result = [];
// Заполняю его
$result["type_class"] = Condition::class;
$result["ColumnName"] = $this->ColumnName;
$result["Operator"] = $this->Operator;
$result["Value"] = $this->Value;
// Возвращаю массив
return $result;
}
/**
* @inheritDoc
*/
public function FromArray (array $array): void
{
// Проверка массива
// - если массив содержит элемент "ColumnName"
if (isset($array["ColumnName"]))
// -- то присваиваю его значение
$this->ColumnName = $array["ColumnName"];
// - если массив содержит элемент "Operator"
if (isset($array["Operator"]))
// -- то присваиваю его значение
$this->Operator = $array["Operator"];
// - если массив содержит элемент "Value"
if (isset($array["Value"]))
// -- то присваиваю его значение
$this->Value = $array["Value"];
}
}

View File

@@ -0,0 +1,415 @@
<?php
namespace goodboyalex\php_db_components_pack\classes;
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\traits\ConditionBuilder\ConditionBuilderConditionsSet;
use goodboyalex\php_db_components_pack\traits\ConditionBuilder\ConditionBuilderLogicalOperationSet;
use InvalidArgumentException;
/**
* Построитель условий запроса выборки.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class ConditionBuilder implements IArrayable
{
/**
* @var array $Conditions Массив условий.
*/
private array $Conditions = [];
// Задание цепочки условий
use ConditionBuilderConditionsSet;
// Задание цепочки логических операций
use ConditionBuilderLogicalOperationSet;
/**
* Создает объект для построения условий запроса выборки по массиву условий.
*
* Пример:
*
* ["id" => 1, 'AND', "age" => ['>=', 18], 'OR', "profile" => ['<=', 12], 'AND', ['AND', "Name" => "Alex",
* ['OR', "Age" => ['>', 18], "FirstName" => "Titanic"]]]
*
* задаёт следующий массив условий:
*
* `id` = 1 AND `age` >= 18 OR `profile` <= 12 AND (`Name` = 'Alex' AND (`Age` > 18 OR `FirstName` =
* 'Titanic'
*
* @param array $conditions Массив условий.
*
* @return ConditionBuilder Объект построителя условий запроса выборки.
*/
public static function Parse (array $conditions): ConditionBuilder
{
// Создаём объект для цепочки
$builder = new ConditionBuilder();
// Перебираем условия
foreach ($conditions as $key => $condition) {
// - если это индексированная часть массива
if (is_numeric($key)) {
// -- это может быть логический оператор? Проверим, является ли $condition строкой
if (is_string($condition)) {
// --- если да, то добавляем его в цепочку
$builder = $builder->AddLogicalOperator($condition);
// --- если ошибка
if ($builder === false)
// ---- то выбрасываем исключение
throw new InvalidArgumentException("Неверный логический оператор: $condition / The logical operator is invalid: $condition.");
// --- идём к следующему элементу
continue;
}
// -- это группа условий, парсим её и добавляем результат
$builder = $builder->AddGroupA(self::ParseConditionGroup($condition));
// -- идём к следующему элементу
continue;
}
// - это условие, парсим его и добавляем результат
$builder = $builder->AddConditionA(self::ParseCondition($key, $condition));
}
// Возвращаем объект
return $builder;
}
/**
* Проверяет, является ли оператор логическим, и возвращает правильно отформатированный оператор.
*
* @param string $operator Оператор.
*
* @return false|string Возвращает правильно отформатированный оператор или <code>false</code>, если оператор
* не является логическим.
*/
private static function PrepareLogicalOperator (string $operator): false|string
{
// Задаем массив логических операторов
$logicalOperators = ['AND', 'OR', 'NOT', 'XOR', 'NAND', 'NOR'];
// Переводим оператор в верхний регистр
$operator = strtoupper($operator);
// Если оператор не входит в массив логических операторов
if (!in_array($operator, $logicalOperators, true))
// - возвращаем false
return false;
// Возвращаем правильный оператор
return $operator;
}
/**
* Обрабатывает условие, заданное в виде массива.
*
* @param string $column Имя столбца.
* @param mixed $conditions Условия.
*
* @return Condition Возвращает условие.
*/
private static function ParseCondition (string $column, mixed $conditions): Condition
{
// Проверяем условия
// - если это не массив
if (!is_array($conditions))
// -- то добавляем оператор равенства и обрабатываем как массив
$conditions = ['=', $conditions];
// - если это массив в массиве
if (is_array($conditions[0]))
// -- то извлекаем первый элемент, как весь массив
$conditions = $conditions[0];
// - если это массив с длинной меньше 2
if (count($conditions) < 2)
// -- то исправляем это
$conditions = array_merge(['='], $conditions);
// Добавляем условие
return new Condition($column, $conditions[0], $conditions[1]);
}
/**
* Обрабатывает группу условий.
*
* @param array $conditions Условия.
*
* @return ConditionGroup Возвращает группу условий.
*/
private static function ParseConditionGroup (array $conditions): ConditionGroup
{
// Получаем оператор
$operator = $conditions[0];
// Удаляем его из массива условий
$conditions = array_slice($conditions, 1);
// Задаем массив условий группы
$conditionItems = [];
// Перебираем условия
foreach ($conditions as $key => $condition) {
// - если это индексированная часть массива
if (is_numeric($key)) {
// -- то это группа условий, парсим её и добавляем результат
$conditionItems[] = self::ParseConditionGroup($condition);
// -- идём к следующему элементу
continue;
}
// - если это не индексированная часть массива, то это условие, парсим его и добавляем результат
$conditionItems[] = self::ParseCondition($key, [$condition]);
}
//var_dump($conditionItems);
// Возвращаем группу условий
return new ConditionGroup($operator, $conditionItems);
}
/**
* Добавляет группу условий.
*
* @param string $logicalOperator Оператор связок внутри группы.
* @param array $conditions Условия.
*
* @return ConditionBuilder Возвращает объект для цепочек.
*/
public function AddGroup (string $logicalOperator, array $conditions): ConditionBuilder
{
return $this->AddGroupA(new ConditionGroup($logicalOperator, $conditions));
}
/**
* Собирает условие в виде строки.
*
* @return string Возвращает условие в виде, пригодном для SQL.
*/
public function Build (): Tuple
{
// Очищаем цепочку от пустых элементов
ArrayExtension::RemoveEmpties($this->Conditions);
// Парсим цепочку и возвращаем результат
return $this->ProcessConditions($this->Conditions);
}
/**
* @inheritDoc
*/
public function ToArray (): array
{
// Задаем массив результата
$result = [
'type_class' => ConditionBuilder::class
];
// Перебираем условия
foreach ($this->Conditions as $condition) {
// - если это группа условий
if ($condition instanceof ConditionGroup) {
// -- парсим её и добавляем результат
$result['conditions'][] = $condition->ToArray();
// -- идём к следующему элементу
continue;
}
// - если это условие
if ($condition instanceof Condition) {
// -- парсим его и добавляем результат
$result['conditions'][] = $condition->ToArray();
// -- идём к следующему элементу
continue;
}
// - иначе считаем, что это логический оператор
$result['conditions'][] = $condition;
}
// Возвращаем результат
return $result;
}
/**
* @inheritDoc
*/
public function FromArray (array $array): void
{
// Удаляем тип класса
$preparedConditions = array_diff(['type_class' => ConditionBuilder::class], $array);
// Очищаем массив
$this->Conditions = [];
// Перебираем условия
foreach ($preparedConditions as $condition) {
// - получаем тип
$type = (isset($condition['type_class'])) ? $condition['type_class'] : "";
// - если это группа условий
if ($type == ConditionGroup::class) {
// -- создаём объект
$conditionGroup = new ConditionGroup();
// -- восстанавливаем его из массива
$conditionGroup->FromArray($condition);
// -- добавляем его в цепочку
$this->Conditions[] = $conditionGroup;
// -- идём к следующему элементу
continue;
}
// - если это условие
if ($type == Condition::class) {
// -- создаём объект
$conditionGroup = new Condition();
// -- восстанавливаем его из массива
$conditionGroup->FromArray($condition);
// -- добавляем его в цепочку
$this->Conditions[] = $conditionGroup;
// -- идём к следующему элементу
continue;
}
// - иначе считаем, что это логический оператор
$this->Conditions[] = $condition;
}
}
/**
* Получает количество условий в цепочке.
*
* @return int Возвращает количество условий.
*/
public function Count (): int
{
// Задаем счетчик
$count = 0;
// Перебираем условия
foreach ($this->Conditions as $condition) {
// - если это условие
if ($condition instanceof Condition)
// -- то сразу увеличиваем счетчик
$count++;
// - если это группа условий
if ($condition instanceof ConditionGroup)
// -- то считаем их количество и добавляем к счетчику
$count += $condition->Count();
}
// Возвращаем результат
return $count;
}
/**
* Добавляет группу условий.
*
* @param ConditionGroup $group Группа условий.
*
* @return ConditionBuilder Возвращает объект для цепочек.
*/
private function AddGroupA (ConditionGroup $group): ConditionBuilder
{
// Добавляем условие
$this->Conditions[] = $group;
// Возвращаем объект
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

@@ -0,0 +1,205 @@
<?php
/**
* @noinspection SqlNoDataSourceInspection
*/
namespace goodboyalex\php_db_components_pack\classes;
use goodboyalex\php_components_pack\classes\Tuple;
use goodboyalex\php_components_pack\interfaces\IArrayable;
/**
* Группа условий запроса выборки по условию.
*
* @author Александр Бабаев
* @package php_components_pack
* @version 1.0
* @since 1.0
*/
final class ConditionGroup implements IArrayable
{
/**
* @var string $LogicOperator Логический оператор.
*/
private string $LogicOperator;
/**
* @var array $Conditions Условия.
*/
private array $Conditions;
/**
* Конструктор.
*
* @param string $logicOperator Логический оператор.
* @param array $conditions Условия.
*/
public function __construct (string $logicOperator = Condition::LOGIC_AND, array $conditions = [])
{
$this->LogicOperator = $logicOperator;
$this->Conditions = $conditions;
}
/**
* Формирует массив условий.
*
* @param int $index Индекс замены параметров для защиты от SQL-инъекций.
*
* @return Tuple (string, array) Массив условий (строка SQL, параметры SQL).
*/
public function GetConditions (int $index = 0): Tuple
{
// Создаём результирующую строку
$resultString = "";
// Создаём результирующий массив
$resultArray = [];
// Создаём счётчик
$count = $index;
// Проходим по всем элементам, кроме последнего
for ($i = 0; $i < count($this->Conditions) - 1; $i++) {
// - увеличиваем счётчик
$count++;
// - получаем условие
$result = $this->WriteCondition($this->Conditions[$i], $count);
// - записываем условие в строку
$resultString .= $result->Get(0);
// - записываем условие в массив
$resultArray[] = array_merge($resultArray, $result->Get(1));
// - записываем логический оператор
$resultString .= " $this->LogicOperator ";
}
// Увеличиваем счётчик
$count++;
// Получаем последнее условие
$result = $this->WriteCondition($this->Conditions[count($this->Conditions) - 1], $count);
// Записываем условие в строку
$resultString .= $result->Get(0);
// Записываем условие в массив
$resultArray[] = array_merge($resultArray, $result->Get(1));
// Очищаем результирующую строку от лишних пробелов
$resultString = '(' . trim($resultString) . ')';
// Возвращаем результат
return new Tuple($resultString, $resultArray);
}
/**
* Возвращает количество условий в группе.
*
* @return int Количество условий в группе.
*/
public function Count (): int
{
// Создаём счетчик
$count = 0;
// Проходим по всем элементам
foreach ($this->Conditions as $condition) {
// - если это условие
if ($condition instanceof Condition)
// -- то увеличиваем счетчик
$count++;
// - если это группа условий
if ($condition instanceof ConditionGroup)
// -- то считаем количество условий в группе и увеличиваем счетчик на это число
$count += $condition->Count();
}
// Возвращаем результат
return $count;
}
/**
* @inheritDoc
*/
public function ToArray (): array
{
// Создаём результирующий массив
$result = [];
// Записываем тип класса
$result["type_class"] = ConditionGroup::class;
// Записываем логический оператор
$result["LogicOperator"] = $this->LogicOperator;
// Перебираем все условия
foreach ($this->Conditions as $condition)
// - записываем условие в массив
$result["Conditions"][] = $condition->ToArray();
// Возвращаем результат
return $result;
}
/**
* @inheritDoc
*/
public function FromArray (array $array): void
{
// Проверяем, существует ли поле LogicOperator
if (isset($array["LogicOperator"]))
// - если да, то записываем его значение
$this->LogicOperator = $array["LogicOperator"];
// Проверяем, существует ли поле Conditions
if (isset($array["Conditions"]))
/**
* -- если да, то проходим по всем элементам массива
*
* @var IArrayable $condition
*/
foreach ($array["Conditions"] as $condition) {
// -- получаем класс по имени
$className = $condition["type_class"];
// -- создаём объект класса
$class = new $className();
// -- заполняем объект
$class->FromArray($condition);
// -- добавляем объект в массив
$this->Conditions[] = $class;
}
}
/**
* Формирует условие.
*
* @param mixed $condition Условие.
* @param int $index Индекс замены параметров для защиты от SQL-инъекций.
*
* @return string Возвращает условие в виде строки SQL.
*/
private function WriteCondition (mixed $condition, int $index = 0): Tuple
{
// Проверяем, является ли условие объектом класса Condition
if ($condition instanceof Condition)
// - если да, то возвращаем его значение
return $condition->Get($index);
// Проверяем, является ли условие объектом класса ConditionGroup
if ($condition instanceof ConditionGroup)
// - если да, то возвращаем его значения
return $condition->GetConditions($index);
// Если условие не является ни классом Condition, ни классом ConditionGroup, то это ошибка. Выбрасываем
// исключение.
throw new InvalidArgumentException("Неверное условие / Invalid condition");
}
}

View File

@@ -9,6 +9,7 @@
use Closure;
use goodboyalex\php_db_components_pack\enums\DBDriver;
use goodboyalex\php_db_components_pack\models\DBConfig;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseGet;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseInsert;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseQueryExecute;
use goodboyalex\php_db_components_pack\traits\Database\DatabaseSpecial;
@@ -168,35 +169,13 @@
// Вставка данных
use DatabaseInsert;
// Получение данных
use DatabaseGet;
// Приватные методы
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);
}
/**
* Получает колонку в массиве данных