This commit is contained in:
2025-02-03 18:49:47 +03:00
parent f1a79d66ec
commit dd62ad0ca4
1739 changed files with 154102 additions and 0 deletions

View 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,
};
}
}