null */ private static function FindAttribute (array $attrs, string $className): ?object { return array_find($attrs, fn ($attr) => $attr->getName() === $className); } /** * Получает публичные свойства класса и их значения. * * @param object $obj Класс. * * @return array Массив публичных свойств и их значений. */ private static function GetPublicProperties (object $obj): array { // Создаю массив свойств $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; } /** * Подготавливает массив параметров * * @param IDBItem $source Объект со свойствами. * @param DBOperation $operation Текущая операция. * * @return array|false Подготовленный массив параметров или false в случае ошибки */ private function PrepareParamsArray (IDBItem $source, DBOperation $operation): array|false { $result = []; // Получаю массив свойств $properties = self::GetPublicProperties($source); // Для каждого свойства foreach ($properties as $key => $value) { // Получаю рефлексию // - для класса $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 array $row Строка БД. * @param string $className Имя класса объекта. * @param DBOperation $operation Операция. * * @return object|null Экземпляр класса объекта или null, если ошибка. */ 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 Прерывать выполнение (true) или просто зафиксировать * (false). * * @return void */ private function HandleException (Exception $exception, bool $terminate = true): void { // Выбираю обработчик исключений $onException = $this->OnException ?? function (Exception $e, bool $isTerminate): void { // Если требуется прерывать выполнение if ($isTerminate) // - то прерываем die($e->getMessage()); else // - в противном случае, выводим сообщение echo $e->getMessage(); }; // Выполняю обработчик исключений $onException($exception, $terminate); } /** * Подготавливает массив столбцов для использования в базе данных * * @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 ConditionBuilder $whereConditions Параметры выборки * @param array $params Параметры и их значения * * @return string SQL-запрос */ private function PrepareSQLForRowsQuery (string $table, array $columns, ConditionBuilder $whereConditions, array &$params = []): string { /** * Собираю условие в виде пригодном для 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)) : "*"; // Создаю запрос $sql = "SELECT $sql_columns FROM $this->DBSignOpen$table$this->DBSignClose"; // Если заданы where-параметры if ($whereConditions->Count() > 0) // - то добавляю их $sql .= " WHERE $sql_where"; // Возвращаю запрос return $sql; } }