416 lines
18 KiB
PHP
416 lines
18 KiB
PHP
<?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));
|
||
}
|
||
|
||
/**
|
||
* Собирает условие в виде пригодном для 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);
|
||
}
|
||
} |