2025-08-05 18:33:19 +03:00

414 lines
18 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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]);
}
// Возвращаем группу условий
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));
}
/**
* Собирает условие в виде пригодном для SQL. Также возвращает массив параметров защиты от SQL-инъекций.
*
* @return Tuple (string, array) Возвращает условие в виде, пригодном для SQL и массив параметров для защиты от
* 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);
}
}