20250203
This commit is contained in:
		
							
								
								
									
										335
									
								
								sources/classes/ClassMapper.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										335
									
								
								sources/classes/ClassMapper.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,335 @@ | ||||
| <?php | ||||
|  | ||||
| namespace goodboyalex\php_components_pack\classes; | ||||
|  | ||||
| use DateTimeImmutable; | ||||
| use DateTimeInterface; | ||||
| use Exception; | ||||
| use ReflectionClass; | ||||
| use ReflectionException; | ||||
| use stdClass; | ||||
| use UnitEnum; | ||||
|  | ||||
| /** | ||||
|  * Класс сопоставлений классов и объектов. | ||||
|  * | ||||
|  * @author Александр Бабаев | ||||
|  * @package php_components_pack | ||||
|  * @version 1.0 | ||||
|  * @since 1.0 | ||||
|  */ | ||||
| final class ClassMapper | ||||
| { | ||||
|     /** | ||||
|      * @var array $DefaultOptions Настройки | ||||
|      */ | ||||
|     public const array DefaultOptions = [ | ||||
|         'ignored' => [], | ||||
|         '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(); | ||||
|  | ||||
|                 // - если значение является классом | ||||
|                 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 ($value == null || $value == "null") | ||||
|                 // - то присваиваю значение по умолчанию | ||||
|                 $value = self::GetDefaults($property->getType()->getName()); | ||||
|  | ||||
|             // Присваиваю значение | ||||
|             $property->setValue($classObj, $value); | ||||
|         } | ||||
|         catch (ReflectionException $exception) { | ||||
|             // Выбрасываю исключение | ||||
|             throw new Exception($exception->getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Получает значение по умолчанию для разных типов данных. | ||||
|      * | ||||
|      * @param string $typeName Имя типа данных. | ||||
|      * | ||||
|      * @return mixed|null Результат. | ||||
|      */ | ||||
|     public static function GetDefaults (string $typeName): mixed | ||||
|     { | ||||
|         return match ($typeName) { | ||||
|             'int' => 0, | ||||
|             'float' => 0.0, | ||||
|             'bool' => false, | ||||
|             'string' => '', | ||||
|             'array' => [], | ||||
|             'object' => new stdClass(), | ||||
|             default => null, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user