babaev-an 07a994df83 20250223-1
[О] [ClassMapper::MapToClassProperty] и [ClassMapper::SetParameterToClass]: Исправлена ошибка, при которой некорректно переводился тип bool.
2025-02-23 14:05:30 +03:00

344 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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();
// - если значение является типом 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,
};
}
}