300 lines
12 KiB
PHP
300 lines
12 KiB
PHP
<?php
|
||
|
||
/**
|
||
* @noinspection SqlNoDataSourceInspection
|
||
*/
|
||
|
||
namespace goodboyalex\php_db_components_pack\traits\Database;
|
||
|
||
use Exception;
|
||
use goodboyalex\php_db_components_pack\attributes\ConvertToDB;
|
||
use goodboyalex\php_db_components_pack\attributes\FieldName;
|
||
use goodboyalex\php_db_components_pack\attributes\IgnoredInDB;
|
||
use goodboyalex\php_db_components_pack\enums\DBOperation;
|
||
use goodboyalex\php_db_components_pack\interfaces\IDBItem;
|
||
use PDO;
|
||
use ReflectionClass;
|
||
use ReflectionException;
|
||
use ReflectionProperty;
|
||
|
||
/**
|
||
* Трейт для функций поддержки для работы с базой данных.
|
||
*
|
||
* @author Александр Бабаев
|
||
* @package php_components_pack
|
||
* @version 1.0
|
||
* @since 1.0
|
||
* @see PDO
|
||
*/
|
||
trait DatabaseSpecial
|
||
{
|
||
/**
|
||
* Подготавливает массив параметров для запроса.
|
||
*
|
||
* @param array $array Массив.
|
||
*
|
||
* @return array Массив с подготовленными параметрами.
|
||
*/
|
||
private static function PrepareArray (array $array): array
|
||
{
|
||
// Результирующий массив
|
||
$result = [];
|
||
|
||
// Для каждого элемента массива
|
||
foreach ($array as $key => $value)
|
||
// - если ключ начинается с ":"
|
||
if ($key[0] == ":")
|
||
// - то сразу добавляем его в результирующий массив
|
||
$result[$key] = $value;
|
||
else
|
||
// - в противном случае, предварительно добавим в имя ключа ":"
|
||
$result[':' . $key] = $value;
|
||
|
||
// Возвращаем результат
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Находит атрибут в массиве.
|
||
*
|
||
* @param array $attrs Массив атрибутов.
|
||
* @param string $className Имя класса.
|
||
*
|
||
* @return object|null Объект атрибута или <code>null</code>
|
||
*/
|
||
private static function FindAttribute (array $attrs, string $className): ?object
|
||
{
|
||
return array_find($attrs, fn ($attr) => $attr->getName() === $className);
|
||
}
|
||
|
||
/**
|
||
* Проверяет, является ли свойство публичным.
|
||
*
|
||
* @param object $obj Объект.
|
||
* @param string $propertyName Имя свойства.
|
||
*
|
||
* @return bool Возвращает <code>true</code>, если свойство публичное, иначе <code>false</code>.
|
||
*/
|
||
private static function IsPublicProperty (object $obj, string $propertyName): bool
|
||
{
|
||
return property_exists($obj, $propertyName) && new ReflectionProperty($obj, $propertyName)->isPublic();
|
||
}
|
||
|
||
/**
|
||
* Подготавливает массив параметров
|
||
*
|
||
* @param IDBItem $source Объект со свойствами.
|
||
* @param DBOperation $operation Текущая операция.
|
||
*
|
||
* @return array|false Подготовленный массив параметров или false в случае ошибки
|
||
*/
|
||
private function PrepareParamsArray (IDBItem $source, DBOperation $operation): array|false
|
||
{
|
||
$result = [];
|
||
|
||
// Получаю массив свойств
|
||
$properties = get_class_vars(get_class($source));
|
||
|
||
// Для каждого свойства
|
||
foreach ($properties as $key => $value) {
|
||
// - пропускаю не публичные свойства
|
||
if (!self::IsPublicProperty($source, $key))
|
||
continue;
|
||
|
||
// Получаю рефлексию
|
||
// - для класса
|
||
$reflectedClass = new ReflectionClass(get_class($source));
|
||
try {
|
||
// - для свойства
|
||
$reflectionProperty = $reflectedClass->getProperty($key);
|
||
}
|
||
catch (ReflectionException $e) {
|
||
// - если ошибка, то вывожу и выходим
|
||
$this->HandleException($e);
|
||
}
|
||
|
||
// - получаю атрибуты
|
||
$attributes = $reflectionProperty->getAttributes();
|
||
|
||
/**
|
||
* Фильтруем поля, игнорируемые для данной операции
|
||
*
|
||
* @var IgnoredInDB|null $ignoreAttr Атрибут игнорирования.
|
||
*/
|
||
$ignoreAttr = self::FindAttribute($attributes, IgnoredInDB::class);
|
||
|
||
// - если поле игнорируется
|
||
if ($ignoreAttr !== null) {
|
||
// -- то проверяю, игнорируется ли данное поле
|
||
$isIgnore = $ignoreAttr->IgnoredOperations->IsExist(fn (DBOperation $oper) => $oper == $operation);
|
||
|
||
// -- если игнорируется
|
||
if ($isIgnore)
|
||
// --- то пропускаю
|
||
continue;
|
||
}
|
||
|
||
/**
|
||
* Получаю значение имени поля
|
||
*
|
||
* @var FieldName|null $fieldNameAttr Атрибут имени поля.
|
||
*/
|
||
$fieldNameAttr = self::FindAttribute($attributes, FieldName::class);
|
||
|
||
// Если есть атрибут имени поля, то беру его имя, иначе беру имя свойства
|
||
$fieldName = $fieldNameAttr !== null ? $fieldNameAttr->FieldName : $key;
|
||
|
||
/**
|
||
* Преобразование типа.
|
||
*
|
||
* @var ConvertToDB|null $convertAttr Атрибут конвертации.
|
||
*/
|
||
$convertAttr = self::FindAttribute($attributes, ConvertToDB::class);
|
||
|
||
// Если есть атрибут конвертации
|
||
if ($convertAttr) {
|
||
// - то получаю значение функции конвертации
|
||
$converter = $convertAttr->ConvertToDB;
|
||
|
||
// - выполняю функцию конвертации
|
||
$value = call_user_func($converter, $value);
|
||
}
|
||
|
||
// Добавляю в массив
|
||
$result[$fieldName] = $value;
|
||
}
|
||
|
||
// Возвращаю результат
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Обрабатывает исключение.
|
||
*
|
||
* @param Exception $exception Исключение.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function HandleException (Exception $exception): void
|
||
{
|
||
// Выбираю обработчик исключений
|
||
$onException = $this->OnException ?? fn (Exception $e) => die($e->getMessage());
|
||
|
||
// Выполняю обработчик исключений
|
||
$onException($exception);
|
||
}
|
||
|
||
/**
|
||
* Подготавливает массив столбцов для использования в базе данных
|
||
*
|
||
* @param array $columns Массив колонок.
|
||
*
|
||
* @return array Массив преобразованных колонок.
|
||
*/
|
||
private function PrepareColumn (array $columns): array
|
||
{
|
||
return array_map(function ($item)
|
||
{
|
||
// Результирующая строка
|
||
$result = "";
|
||
|
||
// Если длинна строки > 0
|
||
if (strlen($item) > 0) {
|
||
// - первый символ
|
||
$firstLetter = substr($item, 0, 1);
|
||
|
||
// - последний символ
|
||
$lastLetter = substr($item, -1);
|
||
|
||
// - если первый символ не $this->DBSignOpen
|
||
if ($firstLetter !== $this->DBSignOpen)
|
||
// -- то добавляем
|
||
$result .= $this->DBSignOpen;
|
||
|
||
// - добавляем строку
|
||
$result .= $item;
|
||
|
||
// - если последний символ не $this->DBSignClose
|
||
if ($lastLetter !== $this->DBSignClose)
|
||
// -- то добавляем
|
||
$result .= $this->DBSignClose;
|
||
}
|
||
|
||
// Возвращаем результат
|
||
return $result;
|
||
}, $columns);
|
||
}
|
||
|
||
/**
|
||
* Генерирует SQL запрос выборки строк.
|
||
*
|
||
* @param string $table Имя таблицы
|
||
* @param array $columns Колонки, которые нужно включить в запрос
|
||
* @param array $where Параметры выборки
|
||
* @param array $params Параметры и их значения
|
||
*
|
||
* @return string SQL-запрос
|
||
*/
|
||
private function PrepareSQLForRowsQuery (string $table, array $columns = [], array $where = [],
|
||
array &$params = []): string
|
||
{
|
||
// Очищаю параметры
|
||
$params = [];
|
||
|
||
// Строковая интерпретация массива условий
|
||
$sql_where = $this->PrepareQueryWhere($where, $params);
|
||
|
||
// Колонки
|
||
$sql_columns = count($columns) > 0 ? implode(', ', $this->PrepareColumn($columns)) : "*";
|
||
|
||
// Создаю запрос
|
||
$sql = "SELECT $sql_columns FROM $this->DBSignOpen$table$this->DBSignClose";
|
||
|
||
// Если заданы where-параметры
|
||
if (count($where) > 0) {
|
||
// - то добавляю их
|
||
$sql .= " WHERE $sql_where";
|
||
}
|
||
|
||
// Возвращаю запрос
|
||
return $sql;
|
||
}
|
||
|
||
/**
|
||
* Готовит выражение для WHERE-запроса
|
||
*
|
||
* @param array $where Массив условий
|
||
* @param array $params Очищенные параметры
|
||
*
|
||
* @return string Строка WHERE-запроса
|
||
*/
|
||
private function PrepareQueryWhere (array $where, array &$params): string
|
||
{
|
||
// Очищаю параметры
|
||
$params = [];
|
||
|
||
// Задаю результат
|
||
$result = "";
|
||
|
||
// Если массив условий не пуст
|
||
if (count($where) > 0) {
|
||
// - то для каждого условия
|
||
foreach ($where as $key => $value) {
|
||
// -- получаю ключ 100%-но без ":" в начале
|
||
$where_key = $key[0] == ":" ? substr($key, 1) : $key;
|
||
|
||
// -- добавляю префикс для 2 или более итерации
|
||
$prefix = $result == "" ? "" : " AND ";
|
||
|
||
// -- добавляю данные в $sql_where
|
||
$result .= $prefix . $this->DBSignOpen . $where_key . $this->DBSignClose . " = :" . $where_key;
|
||
|
||
// -- добавляю данные в параметры
|
||
$params[$where_key] = "$value";
|
||
}
|
||
}
|
||
|
||
// Вывожу результат
|
||
return $result;
|
||
}
|
||
} |