null */ private static function FindAttribute (array $attrs, string $className): ?object { return array_find($attrs, fn ($attr) => $attr->getName() === $className); } /** * Разбирает объект на свойства. * * @param IDBItem $source Объект со свойствами. * @param DBOperation $operation Текущая операция. * * @return ObjectArray Массив свойств и их значений класса, который реализует интерфейс IDBItem. */ private static function GetProperties (IDBItem $source, DBOperation $operation): ObjectArray { // Создаю массив свойств $result = new ObjectArray(); // Получаю массив свойств $properties = get_class_vars(get_class($source)); // Для каждого свойства foreach ($properties as $key => $value) { // - пропускаю не свойства if (!property_exists($source, $key)) // -- пропускаю continue; // - получаю рефлексию класса $reflectedClass = new ReflectionClass(get_class($source)); try { // - получаю рефлексию свойства $reflectionProperty = $reflectedClass->getProperty($key); } catch (ReflectionException) { // - если ошибка, то вывожу пустой массив return $result; } // - пропускаю не публичные свойства if (!$reflectionProperty->isPublic()) // -- пропускаю continue; // - получаю атрибуты $attributes = $reflectionProperty->getAttributes(); /** * Фильтруем поля, игнорируемые для данной операции * * @var PrimaryKey|null $pkAttr Атрибут первичного ключа. */ $pkAttr = self::FindAttribute($attributes, PrimaryKey::class); // - это первичный ключ? $isPrimary = $pkAttr !== null; /** * Фильтруем поля, игнорируемые для данной операции * * @var IgnoredInDB|null $ignoreAttr Атрибут игнорирования. */ $ignoreAttr = self::FindAttribute($attributes, IgnoredInDB::class); // - если поле игнорируется $isIgnore = $ignoreAttr !== null && $ignoreAttr->IgnoredOperations->IsExist(fn (DBOperation $oper) => $oper == $operation); /** * Получаю значение имени поля * * @var FieldName|null $fieldNameAttr Атрибут имени поля. */ $fieldNameAttr = self::FindAttribute($attributes, FieldName::class); // Если есть атрибут имени поля, то беру его имя, иначе беру имя свойства $fieldName = $fieldNameAttr !== null ? $fieldNameAttr->FieldName : ""; /** * Преобразование типа. * * @var ConvertToDB|null $convertAttr Атрибут конвертации. */ $convertAttr = self::FindAttribute($attributes, ConvertToDB::class); // - получаю функцию конвертации и сравнения $converterToDB = $convertAttr?->ConvertToDB; $converterFromDB = $convertAttr?->ConvertFromDB; $compareFunc = $convertAttr?->Compare; // - создаю объект свойства $item = new DBItemProperty($key, $value, $fieldName, $isPrimary, $isIgnore, $converterToDB, $converterFromDB, $compareFunc); // - добавляю в массив $result[] = $item; } // Возвращаю результат return $result; } /** * Подготавливает массив параметров * * @param IDBItem $source Объект со свойствами. * @param DBOperation $operation Текущая операция. * * @return array|false Подготовленный массив параметров или false в случае ошибки */ private function PrepareParamsArray (IDBItem $source, DBOperation $operation): array|false { $result = []; // Получаю массив свойств $properties = self::GetProperties($source, $operation); /** * Для каждого свойства... * * @var DBItemProperty $property Свойство. */ foreach ($properties as $property) { // - пропускаю игнорируемые поля if ($property->IsIgnored) continue; // - получаю значение имени поля $fieldName = $property->FieldName; // - преобразую тип $value = call_user_func($property->ConvertToDB, $property->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(); // Получаю свойства класса $properties = self::GetProperties($class, $operation); // Массив отношений, содержащий имя поля, имя свойства, метод преобразования $propertiesRelationship = []; /** * Для каждого свойства... * * @var DBItemProperty $property Свойство. */ foreach ($properties as $property) { // - пропускаю игнорируемые поля if ($property->IsIgnored) continue; // - получаю имя поля $fieldName = $property->FieldName; // - добавляю в массив отношений $propertiesRelationship[$fieldName] = [$property->Name, $property->ConverterFromDB]; } // Проходим элементы массива из БД 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; } }