This commit is contained in:
2025-08-02 15:45:51 +03:00
parent 906ed15c58
commit 4ec8ccc089
8 changed files with 731 additions and 536 deletions

View File

@@ -10,6 +10,7 @@
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\attributes\PrimaryKey;
use goodboyalex\php_db_components_pack\classes\ConditionBuilder;
use goodboyalex\php_db_components_pack\enums\DBOperation;
use goodboyalex\php_db_components_pack\interfaces\IDBItem;
@@ -29,32 +30,6 @@
*/
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;
}
/**
* Находит атрибут в массиве.
*
@@ -69,16 +44,112 @@
}
/**
* Проверяет, является ли свойство публичным.
* Получает публичные свойства класса и их значения.
*
* @param object $obj Объект.
* @param string $propertyName Имя свойства.
* @param object $obj Класс.
*
* @return bool Возвращает <code>true</code>, если свойство публичное, иначе <code>false</code>.
* @return array Массив публичных свойств и их значений.
*/
private static function IsPublicProperty (object $obj, string $propertyName): bool
private static function GetPublicProperties (object $obj): array
{
return property_exists($obj, $propertyName) && new ReflectionProperty($obj, $propertyName)->isPublic();
// Создаю массив свойств
$result = [];
// Получаю массив свойств
$properties = get_class_vars(get_class($obj));
// Для каждого свойства
foreach ($properties as $key => $value) {
// - пропускаю не свойства
if (!property_exists($obj, $key))
// -- пропускаю
continue;
// - получаю рефлексию
$rProperty = new ReflectionProperty($obj, $key);
// - пропускаю не публичные свойства
if (!$rProperty->isPublic())
// -- пропускаю
continue;
// - добавляю в массив свойство и его значение
$result[$key] = $value;
}
// Возвращаю результат
return $result;
}
/**
* Получает первичные ключи класса.
*
* @param IDBItem $source Класс.
* @param DBOperation $operation Текущая операция.
*
* @return array Массив первичных ключей и их значений.
*/
private function FindPrimaryKeys (IDBItem $source, DBOperation $operation): array
{
// Создаю массив первичных ключей
$pKeys = [];
// Получаю массив свойств
$properties = self::GetPublicProperties($source);
// Для каждого свойства
foreach ($properties as $key => $value) {
// - получаю рефлексию
// - для класса
$reflectedClass = new ReflectionClass(get_class($source));
try {
// - для свойства
$reflectionProperty = $reflectedClass->getProperty($key);
}
catch (ReflectionException) {
// - если ошибка, то вывожу пустой массив
return [];
}
// - получаю атрибуты
$attributes = $reflectionProperty->getAttributes();
/**
* Фильтруем поля, игнорируемые для данной операции
*
* @var PrimaryKey|null $pkAttr Атрибут первичного ключа.
*/
$pkAttr = self::FindAttribute($attributes, PrimaryKey::class);
// - если на свойстве нет атрибута первичного ключа
if ($pkAttr === null)
// -- то пропускаю
continue;
/**
* Фильтруем поля, игнорируемые для данной операции
*
* @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;
}
// - добавляю первичный ключ и его значение
$pKeys[$key] = $value;
}
// Возвращаю найденные ключи
return $pKeys;
}
/**
@@ -94,14 +165,10 @@
$result = [];
// Получаю массив свойств
$properties = get_class_vars(get_class($source));
$properties = self::GetPublicProperties($source);
// Для каждого свойства
foreach ($properties as $key => $value) {
// - пропускаю не публичные свойства
if (!self::IsPublicProperty($source, $key))
continue;
// Получаю рефлексию
// - для класса
$reflectedClass = new ReflectionClass(get_class($source));
@@ -169,20 +236,135 @@
return $result;
}
/**
* Восстанавливает объект из БД.
*
* @param array $row Строка БД.
* @param string $className Имя класса объекта.
* @param DBOperation $operation Операция.
*
* @return object|null Экземпляр класса объекта или <code>null</code>, если ошибка.
*/
private function RestoreItem (array $row, string $className, DBOperation $operation): ?object
{
// Если целевой класс не реализует интерфейс IDBItem
if (in_array(IDBItem::class, class_implements($className)))
// - то прерываем и возвращаем null
return null;
// Создаём объект желаемого класса
$class = new $className();
// Получаем публичные свойства класса
try {
$properties = new ReflectionClass($class)->getProperties(ReflectionProperty::IS_PUBLIC);
}
catch (ReflectionException $exception) {
// - в случае ошибки, обрабатываем её
$this->HandleException($exception, false);
// - и возвращаем null
return null;
}
// Массив отношений, содержащий имя поля, имя свойства, метод преобразования
$propertiesRelationship = [];
// Для каждого свойства
foreach ($properties as $property) {
// - получаю атрибуты
$attributes = $property->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 : $property->getName();
/**
* Преобразование типа.
*
* @var ConvertToDB|null $convertAttr Атрибут конвертации.
*/
$convertAttr = self::FindAttribute($attributes, ConvertToDB::class);
// - если есть атрибут конвертации, то получаю значение функции конвертации, иначе null
$converterFunc = ($convertAttr) ? $convertAttr->ConvertFromDB : null;
// - добавляю в массив отношений
$propertiesRelationship[$fieldName] = [$property->getName(), $converterFunc];
}
// Проходим элементы массива из БД
foreach ($row as $column => $value) {
// - если свойство не найдено
if (!array_key_exists($column, $propertiesRelationship))
// -- то пропускаем
continue;
// - получаю имя свойства и процедуру конвертации
[$property, $converter] = $propertiesRelationship[$column];
// - применяем процедуру конвертации (если указана)
if ($converter !== null)
// - выполняю функцию конвертации
$value = call_user_func($converter, $value);
// - устанавливаем значение свойства
$class->$property = $value;
}
// Возвращаю класс
return $class;
}
/**
* Обрабатывает исключение.
*
* @param Exception $exception Исключение.
* @param bool $terminate Прерывать выполнение (<code>true</code>) или просто зафиксировать
* (<code>false</code>).
*
* @return void
*/
private function HandleException (Exception $exception): void
private function HandleException (Exception $exception, bool $terminate = true): void
{
// Выбираю обработчик исключений
$onException = $this->OnException ?? fn (Exception $e) => die($e->getMessage());
$onException = $this->OnException ?? function (Exception $e, bool $isTerminate): void
{
// Если требуется прерывать выполнение
if ($isTerminate)
// - то прерываем
die($e->getMessage());
else
// - в противном случае, выводим сообщение
echo $e->getMessage();
};
// Выполняю обработчик исключений
$onException($exception);
$onException($exception, $terminate);
}
/**
@@ -231,19 +413,21 @@
*
* @param string $table Имя таблицы
* @param array $columns Колонки, которые нужно включить в запрос
* @param array $where Параметры выборки
* @param ConditionBuilder $whereConditions Параметры выборки
* @param array $params Параметры и их значения
*
* @return string SQL-запрос
*/
private function PrepareSQLForRowsQuery (string $table, array $columns, ConditionBuilder $where,
private function PrepareSQLForRowsQuery (string $table, array $columns, ConditionBuilder $whereConditions,
array &$params = []): string
{
// Очищаю параметры
$params = [];
// Строковая интерпретация массива условий
$sql_where = $this->PrepareQueryWhere($where, $params);
/**
* Собираю условие в виде пригодном для SQL
*
* @var string $sql_where where-запрос SQL
* @var array $params Параметры и их значения (для защиты от SQL-инъекции)
*/
[$sql_where, $params] = $whereConditions->Build();
// Колонки
$sql_columns = count($columns) > 0 ? implode(', ', $this->PrepareColumn($columns)) : "*";
@@ -252,50 +436,11 @@
$sql = "SELECT $sql_columns FROM $this->DBSignOpen$table$this->DBSignClose";
// Если заданы where-параметры
if (count($where) > 0) {
if ($whereConditions->Count() > 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;
}
}