[О] [ClassMapper::GetDefaults]: Улучшено определение типа. Теперь проверка integer не вызовет ошибку, что ожидается int. [О] [ClassMapper::MapClass]: Теперь идёт проверка свойства на доступность get и set. Свойства с только get и только set пропускаются.
399 lines
17 KiB
PHP
399 lines
17 KiB
PHP
<?php
|
||
|
||
namespace goodboyalex\php_components_pack\classes;
|
||
|
||
use DateTimeImmutable;
|
||
use DateTimeInterface;
|
||
use Error;
|
||
use Exception;
|
||
use ReflectionClass;
|
||
use ReflectionException;
|
||
use stdClass;
|
||
use Throwable;
|
||
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 = [];
|
||
|
||
// Получаю имя исходного класса
|
||
$className = get_class($from);
|
||
|
||
// Получение всех свойств класса
|
||
try {
|
||
$reflection = new ReflectionClass($className);
|
||
}
|
||
catch (ReflectionException) {
|
||
return;
|
||
}
|
||
|
||
// Получение всех свойств класса
|
||
$props = $reflection->getProperties();
|
||
|
||
// Создаю экземпляр класса
|
||
$instance = new $className();
|
||
|
||
// Для каждого свойства
|
||
foreach ($props as $prop) {
|
||
// - получаю имя свойства
|
||
$propName = $prop->getName();
|
||
// - получаю значение свойства
|
||
$value = $from->$propName;
|
||
|
||
try {
|
||
// - получаю тип свойства
|
||
$typeOf = gettype($from->$propName);
|
||
|
||
// - получаю значение свойства по типу и по умолчанию
|
||
$writeValue = self::GetDefaults($typeOf);
|
||
|
||
try {
|
||
// - проверяем, можно ли записать и прочитать значение
|
||
// -- пытаюсь установить значение
|
||
$instance->$propName = $writeValue;
|
||
// -- пытаюсь прочитать установленное значение
|
||
$readValue = $instance->$propName;
|
||
|
||
// -- и проверяю, что значение совпадают
|
||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||
if ($readValue !== $writeValue)
|
||
continue;
|
||
}
|
||
catch (Throwable) {
|
||
// - в случае ошибки, понимаем, что свойство доступно или только для чтения, или
|
||
// только для записи и оно нам не подходит. Поэтому пропускаем его.
|
||
continue;
|
||
}
|
||
|
||
// - если свойство игнорируется
|
||
if (in_array($propName, $options['ignored']))
|
||
// -- пропускаю
|
||
continue;
|
||
|
||
// - если свойство не разрешено
|
||
if (count($options['allowed']) > 0 && !in_array($propName, $options['allowed']))
|
||
// -- пропускаю
|
||
continue;
|
||
|
||
// Если не было ошибки, значит свойство имеет и геттер, и сеттер
|
||
$properties[$propName] = $value;
|
||
}
|
||
catch (Error) {
|
||
// - в случае ошибки, понимаем, что свойство нам не подходит. Поэтому пропускаю его.
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// Для каждого элемента массива
|
||
foreach ($properties as $name => $value) {
|
||
// - если свойство есть в объекте
|
||
if (property_exists($to, $name))
|
||
// -- то присваиваю значение
|
||
$to->$name = $from->$name;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Возвращает значение по умолчанию для типа $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,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Подготавливает значения свойств класса.
|
||
*
|
||
* @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());
|
||
}
|
||
}
|
||
} |