OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
204
app/Core/Support/Cast.php
Normal file
204
app/Core/Support/Cast.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Core\Support;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @todo the cast to method needs to be refactored to have better support of constructor params
|
||||
**/
|
||||
class Cast
|
||||
{
|
||||
protected array $mappings;
|
||||
|
||||
public function __construct(private array|object $object)
|
||||
{
|
||||
if (is_array($this->object)) {
|
||||
$this->object = (object) $this->object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
* @throws \ReflectionException
|
||||
**/
|
||||
public function castTo(string $classDest, array $constructParams = [], array $mappings = []): object
|
||||
{
|
||||
$this->mappings ??= $mappings;
|
||||
|
||||
if (! class_exists($classDest)) {
|
||||
throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $classDest));
|
||||
}
|
||||
|
||||
$sourceObj = $this->object;
|
||||
$classRef = new \ReflectionClass($classDest);
|
||||
$properties = $classRef->getProperties();
|
||||
|
||||
if (
|
||||
! empty($reflectedConstructParams = $classRef->getConstructor()?->getParameters() ?? [])
|
||||
&& empty($constructParams)
|
||||
) {
|
||||
foreach ($reflectedConstructParams as $param) {
|
||||
if (isset($sourceObj->{$param->getName()})) {
|
||||
$constructParams[] = $sourceObj->{$param->getName()};
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($param->isOptional()) {
|
||||
$constructParams[] = $param->getDefaultValue();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Missing construct parameter %s.', $param->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
$returnObj = build(new $classDest(...$constructParams));
|
||||
|
||||
foreach ($properties as $property) {
|
||||
$name = $property->getName();
|
||||
|
||||
if (! isset($sourceObj->$name)) {
|
||||
if ($property->hasDefaultValue()) {
|
||||
$returnObj->set($name, $property->getDefaultValue());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new \RuntimeException(sprintf('Property %s does not exist in source object.', $name));
|
||||
}
|
||||
|
||||
try {
|
||||
$type = collect($mappings)->firstOrFail(fn ($mapping, $key) => in_array($key, [$name, '*']));
|
||||
} catch (\Illuminate\Support\ItemNotFoundException) {
|
||||
$type = ($reflectionType = $property->getType()) instanceof \ReflectionNamedType ? $reflectionType->getName() : null;
|
||||
}
|
||||
|
||||
$returnObj->set($name, match (true) {
|
||||
enum_exists($type) => self::castEnum($sourceObj->$name, $type),
|
||||
$type !== 'stdClass' && class_exists($type) => (new self($sourceObj->$name))->castTo(
|
||||
classDest: $type,
|
||||
mappings: $this->getMatchingMappings($mappings, $name),
|
||||
),
|
||||
in_array($type, ['array', 'object', 'stdClass']) => $this->handleIterator(
|
||||
$sourceObj->$name,
|
||||
$this->getMatchingMappings($mappings, $name)
|
||||
),
|
||||
is_null($type) || $type == 'mixed' => $sourceObj->$name,
|
||||
default => self::castSimple($sourceObj->$name, $type),
|
||||
});
|
||||
}
|
||||
|
||||
return $returnObj->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
* @throws \InvalidArgumentException
|
||||
**/
|
||||
public static function castSimple(mixed $value, string $simpleType): mixed
|
||||
{
|
||||
if (
|
||||
is_null($castedValue = match ($simpleType) {
|
||||
'int', 'integer' => filter_var($value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE),
|
||||
'float' => filter_var($value, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE),
|
||||
'string', 'str' => is_array($value) || (is_object($value) && ! method_exists($value, '__toString')) ? null : (string) $value,
|
||||
'bool', 'boolean' => filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE),
|
||||
'object', 'stdClass' => is_array($value) ? (object) $value : null,
|
||||
'array' => is_object($value) || is_array($value) ? (array) $value : null,
|
||||
default => throw new \InvalidArgumentException(sprintf('%s is not a simple type.', $simpleType)),
|
||||
})
|
||||
) {
|
||||
throw new \RuntimeException(sprintf('Could not cast value to type %s.', $simpleType));
|
||||
}
|
||||
|
||||
return $castedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast to backed enum
|
||||
*
|
||||
**/
|
||||
public static function castEnum(mixed $value, string $enumClass): mixed
|
||||
{
|
||||
// For non-backed enums, iterate and match by name.
|
||||
if (is_string($value)) {
|
||||
foreach ($enumClass::cases() as $case) {
|
||||
if ($case->name === $value) {
|
||||
return $case;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For backed enums, try to get the case by value
|
||||
if (
|
||||
is_subclass_of($enumClass, \BackedEnum::class)
|
||||
&& ($enum = $enumClass::tryFrom($value) ?? false)
|
||||
) {
|
||||
return $enum;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Value cannot be casted to %s.', $enumClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts a string value into a datetime object.
|
||||
*
|
||||
* @param string $value The value to be casted into a datetime object.
|
||||
* @return \Carbon\CarbonImmutable The datetime object.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the value is not a valid datetime string.
|
||||
**/
|
||||
public static function castDateTime(string $value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
return dtHelper()->parseDbDateTime($value);
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleIterator(iterable $iterator, array $mappings = []): array|object
|
||||
{
|
||||
$result = is_object($iterator) ? new \stdClass : [];
|
||||
|
||||
foreach ($iterator as $key => $value) {
|
||||
if (is_numeric($key)) {
|
||||
$type = $mappings['*'] ?? false;
|
||||
} else {
|
||||
if ($type = preg_match('/\<[a-zA-Z0-9\\\\]+\>/', $key)) {
|
||||
$key = preg_replace('/\<[a-zA-Z0-9\\\\]+\>/', '', $key);
|
||||
} else {
|
||||
$type = $mappings[$key] ?? $mappings['*'] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
$value = match (true) {
|
||||
$type && enum_exists($type) => self::castEnum($value, $type),
|
||||
$type && class_exists($type) => (new self($value))->castTo($type),
|
||||
$type && in_array($type, ['string', 'str', 'int', 'integer', 'float', 'bool', 'boolean']) => self::castSimple($value, $type),
|
||||
$type && in_array($type, ['array', 'object', 'stdClass']),
|
||||
is_array($value),
|
||||
is_object($value) => $this->{__FUNCTION__}($value, $this->getMatchingMappings($mappings, (string) $key)),
|
||||
default => $value,
|
||||
};
|
||||
|
||||
if (is_object($result)) {
|
||||
$result->$key = $value;
|
||||
} else {
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getMatchingMappings(array $mappings, string $propName): array
|
||||
{
|
||||
return collect($mappings)
|
||||
->filter(fn ($mapping, $key) => Str::startsWith($key, "$propName.") || Str::startsWith($key, '*.'))
|
||||
->mapWithKeys(fn ($mapping, $key) => [Str::after($key, '.') => $mapping])
|
||||
->all();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user