[], 'allowed' => [] ]; /** * Передаёт одинаковые параметры из класса $from в класс $to, учитывая игнорируемые ($ignoredProperties) и * разрешенные ($allowedProperties) свойства. * * @param object $from Класс-донор * @param object $to Класс-приемник * @param array $options Параметры привязки свойств (см атрибут Bind). * * @return void */ public static function MapClass (object $from, object $to, array $options = self::DefaultOptions): void { // Если есть игнорируемые или разрешенные свойства if (!(count($options['ignored']) == 0 && count($options['allowed']) == 0)) // -- то для каждого игнорируемого свойства foreach ($options['ignored'] as $ignoredProperty) // --- и если оно есть в массиве разрешенных if (in_array($ignoredProperty, $options['allowed'])) // ---- то исключаю его из массива разрешенных unset($options['allowed'][array_search($ignoredProperty, $options['allowed'])]); // Получаю массив свойств $properties = get_class_vars(get_class($from)); // Для каждого элемента массива foreach ($properties as $name => $value) { // - если свойство игнорируется if (in_array($name, $options['ignored'])) // -- пропускаю continue; // - если свойство не разрешено if (count($options['allowed']) > 0 && !in_array($name, $options['allowed'])) // -- пропускаю continue; // - если свойство есть в объекте if (property_exists($to, $name)) // -- то присваиваю значение $to->$name = $from->$name; } } /** * Подготавливает значения свойств класса. * * @param array $params Данные запроса. * @param ReflectionClass $classReflector Анализатор класса. * @param array $options Массив свойств привязки. * * @return array Массив данных класса. */ public static function PrepareClassProperties (array $params, ReflectionClass $classReflector, array $options = self::DefaultOptions): array { // Создаю массив данных класса $classData = []; // Для каждого свойства класса foreach ($classReflector->getProperties() as $property) { // - получаю имя свойства $propertyName = $property->getName(); // - если это свойство задано в массиве параметров if (array_key_exists($propertyName, $params)) { // -- если задан массив разрешённых свойств if (!empty($options["allowed"])) // --- если свойство не разрешено if (!in_array($propertyName, $options["allowed"])) // ---- то пропускаю continue; // -- если задан массив запрещённых свойств if (!empty($options["ignored"])) // --- если свойство должно игнорироваться if (in_array($propertyName, $options["ignored"])) // ---- то пропускаю continue; // -- добавляю значение свойства в результат $classData[$propertyName] = $params[$propertyName]; } else { // - в противном случае, пробегаю массив параметров foreach ($params as $key => $value) { // -- если в имени параметра есть разделитель "_" if (str_starts_with($key, $propertyName . "_")) { // -- разбиваю имя параметра на части $keyParts = explode("_", $key); // -- добавляю значение свойства в результат self::GetClassParametersArrayParser($classData, $keyParts, $value); } } } } // Возвращаю массив данных класса return $classData; } /** * Парсит массив свойств класса. * * @param array $source Исходный массив (он же и результат парсинга). * @param array $parametersKeys Массив имен свойств. Например, Page_Meta_Id должен быть разбит на * ["Page", "Meta", "Id"]. * @param mixed $value Значение свойства. * @param array $options Массив параметров привязки свойств. * * @return void */ public static function GetClassParametersArrayParser (array &$source, array $parametersKeys, mixed $value, array $options = self::DefaultOptions): void { // Если массив имен свойств пустой if (empty($parametersKeys)) // - то прерываю парсинг return; // Получаю имя текущего свойства $currentName = array_shift($parametersKeys); // Если текущего свойства нет в массиве if (!isset($source[$currentName])) // - то создаю его $source[$currentName] = []; // Если массив имен свойств содержит только одно свойство if (count($parametersKeys) === 0) { // - если задан массив разрешённых свойств if (!empty($options["allowed"])) // --- если свойство не разрешено if (!in_array($currentName, $options["allowed"])) // ---- то пропускаю return; // -- если задан массив запрещённых свойств if (!empty($options["ignored"])) // --- если свойство должно игнорироваться if (in_array($currentName, $options["ignored"])) // ---- то пропускаю return; // - добавляю значение свойства в результат $source[$currentName] = $value; } else // - иначе продолжаю парсинг self::GetClassParametersArrayParser($source[$currentName], $parametersKeys, $value, $options); } /** * Переводит данные из массива в объект класса. * * @param string $className Имя класса * @param array $properties Массив данных * * @return mixed Объект класса * @throws Exception */ public static function MapToClassProperty (string $className, array $properties): mixed { // Создаю try { $classReflector = new ReflectionClass($className); // Создаю объект класса $classObj = new $className(); // Для каждого свойства класса foreach ($properties as $key => $value) { // - проверяю наличие свойства if (!$classReflector->hasProperty($key)) // -- иду к следующему continue; // - получаю данные про свойство // -- само свойство $property = $classReflector->getProperty($key); // -- тип свойства $propertyType = $property->getType(); // - если значение является типом bool if ($propertyType->getName() === 'bool') { // -- присваиваю дату self::SetParameterToClass($classReflector, $key, $classObj, $value == "1"); // -- следующий элемент continue; } // - если значение является классом if (!$propertyType->isBuiltin() && is_array($value)) { // -- присваиваю объект self::SetParameterToClass($classReflector, $key, $classObj, self::MapToClassProperty($propertyType->getName(), $value)); // -- следующий элемент continue; } // - если значение является датой if ($classObj->$key instanceof DateTimeInterface) { // -- получаю дату $dateValue = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $value . " 00:00:00"); // -- если не получилось if ($dateValue === false) // --- то добавляю дату по умолчанию (1970-01-01 00:00:00) $dateValue = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', "1970-01-01 00:00:00"); // -- присваиваю дату self::SetParameterToClass($classReflector, $key, $classObj, $dateValue); // -- следующий элемент continue; } // - если значение является перечислением if ($classObj->$key instanceof UnitEnum) { // -- если значение уже является перечислением if ($value instanceof UnitEnum) // --- присваиваю перечисление self::SetParameterToClass($classReflector, $key, $classObj, $value); else // -- иначе self::SetParameterToClass($classReflector, $key, $classObj, is_numeric($value) ? $classObj->$key::FromInt($value) : $classObj->$key::FromName($value)); // -- следующий элемент continue; } // - если значение является NULL if ($value == "null") { // -- присваиваю NULL self::SetParameterToClass($classReflector, $key, $classObj, is_array($key) ? [] : null); // -- следующий элемент continue; } // - присваиваю значение self::SetParameterToClass($classReflector, $key, $classObj, $value); } // Возвращаю объект класса return $classObj; } catch (Exception $exception) { throw new Exception($exception->getMessage()); } } /** * Присваивает значение параметра объекту класса. * * @param ReflectionClass $classReflector Рефлектор класса. * @param string $propertyName Имя свойства. * @param mixed $classObj Объект класса. * @param mixed $value Значение. * * @throws Exception */ public static function SetParameterToClass (ReflectionClass $classReflector, string $propertyName, mixed $classObj, mixed $value): void { try { // Получаю свойство $property = $classReflector->getProperty($propertyName); /** * Устанавливаю доступ значения свойства. * * @noinspection PhpExpressionResultUnusedInspection */ $property->setAccessible(true); // Если значение null if (!is_bool($value) && ($value == null || $value == "null")) // - то присваиваю значение по умолчанию $value = self::GetDefaults($property->getType()->getName()); // Присваиваю значение $property->setValue($classObj, $value); } catch (ReflectionException $exception) { // Выбрасываю исключение throw new Exception($exception->getMessage()); } } /** * Возвращает значение по умолчанию для типа $typeName. * * @param string $typeName Тип * * @return mixed Значение по умолчанию */ public static function GetDefaults (string $typeName): mixed { return match (strtolower($typeName)) { 'int', 'integer' => 0, 'float', 'double' => 0.0, 'bool', 'boolean' => false, 'string' => '', 'array' => [], 'object' => new stdClass(), default => null, }; } }