377 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			377 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| 
 | ||
| namespace goodboyalex\php_components_pack\classes;
 | ||
| 
 | ||
| use DateTimeImmutable;
 | ||
| use DateTimeInterface;
 | ||
| use Exception;
 | ||
| use goodboyalex\php_components_pack\attributes\GetOnly;
 | ||
| use ReflectionClass;
 | ||
| use ReflectionException;
 | ||
| use ReflectionProperty;
 | ||
| use stdClass;
 | ||
| use UnitEnum;
 | ||
| 
 | ||
| /**
 | ||
|  * Класс сопоставлений классов и объектов.
 | ||
|  *
 | ||
|  * @author Александр Бабаев
 | ||
|  * @package php_components_pack
 | ||
|  * @version 1.0.1
 | ||
|  * @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;
 | ||
| 
 | ||
|             // - если свойство маркируется как GetOnly
 | ||
|             if (self::HasGetOnlyAttributes($from, $name))
 | ||
|                 // -- пропускаю
 | ||
|                 continue;
 | ||
| 
 | ||
|             // - если свойство не разрешено
 | ||
|             if (count($options['allowed']) > 0 && !in_array($name, $options['allowed']))
 | ||
|                 // -- пропускаю
 | ||
|                 continue;
 | ||
| 
 | ||
|             // - если свойство есть в объекте
 | ||
|             if (property_exists($to, $name))
 | ||
|                 // -- то присваиваю значение
 | ||
|                 $to->$name = $from->$name;
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Проверяет, есть ли у свойства класса $class атрибуты GetOnly.
 | ||
|      *
 | ||
|      * @param object $class Объект класса.
 | ||
|      * @param string $propertyName Имя свойства.
 | ||
|      *
 | ||
|      * @return bool true если у свойства есть атрибут GetOnly, иначе false.
 | ||
|      */
 | ||
|     private static function HasGetOnlyAttributes (object $class, string $propertyName): bool
 | ||
|     {
 | ||
|         // Создаем отражение свойства класса
 | ||
|         try {
 | ||
|             $reflectionProperty = new ReflectionProperty(get_class($class), $propertyName);
 | ||
|         }
 | ||
|         catch (ReflectionException) {
 | ||
|             // - возвращаю false, если произошла ошибка создания отражения свойства класса
 | ||
|             return false;
 | ||
|         }
 | ||
| 
 | ||
|         // Получаем список атрибутов у данного свойства
 | ||
|         $attributes = $reflectionProperty->getAttributes(GetOnly::class);
 | ||
| 
 | ||
|         // Возвращаем true, если атрибут найден, иначе false
 | ||
|         return !empty($attributes);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * Подготавливает значения свойств класса.
 | ||
|      *
 | ||
|      * @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,
 | ||
|         };
 | ||
|     }
 | ||
| } |