This commit is contained in:
icefox 2026-02-23 21:35:08 -03:00
parent 367858c97c
commit 6b1a385292
No known key found for this signature in database
13 changed files with 191 additions and 80 deletions

153
src/ValueFactory.php Normal file
View file

@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace Icefox\DTO;
use Icefox\DTO\Attributes\CastWith;
use Icefox\DTO\Attributes\Flat;
use Icefox\DTO\Config;
use Icefox\DTO\ReflectionHelper;
use Illuminate\Support\Facades\App;
use ReflectionNamedType;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\PseudoTypes\Generic;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\AbstractList;
use phpDocumentor\Reflection\Types\Boolean;
use phpDocumentor\Reflection\Types\Float_;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\Object_;
class ValueFactory
{
public static function constructObject(string $className, mixed $rawValue): object
{
if ($mapper = Config::getCaster($className)) {
return $mapper($rawValue);
}
// Plain values or numeric arrays are passed as a single parameter to the constructor
if (!is_array($rawValue) || array_key_exists(0, $rawValue)) {
return new $className($className);
}
// Associative arrays leverage Laravel service container
return App::makeWith($className, $rawValue);
}
public static function resolveAnnotatedValue(Type $type, mixed $rawValue): mixed
{
if ($type instanceof Nullable) {
$type = $type->getActualType();
}
if ($type instanceof AbstractList) {
$innerType = $type->getValueType();
$result = [];
foreach ($rawValue as $key => $value) {
$result[$key] = self::resolveAnnotatedValue($innerType, $value);
}
return $result;
}
if ($type instanceof Generic) {
$types = $type->getTypes();
$innerType = count($types) === 2 ? $types[1] : $types[0];
if (is_array($rawValue)) {
$innerValues = [];
foreach ($rawValue as $key => $value) {
$innerValues[$key] = self::resolveAnnotatedValue($innerType, $value);
}
return array_key_exists(0, $rawValue)
? new ($type->getFqsen()->__toString())($innerValues)
: App::makeWith($type->getFqsen()->__toString(), $innerValues);
}
$value = self::resolveAnnotatedValue($innerType, $rawValue);
return new ($type->getFqsen()->__toString())($value);
}
if ($type instanceof Boolean) {
return boolval($rawValue);
}
if ($type instanceof Float_) {
return floatval($rawValue);
}
if ($type instanceof Integer) {
return intval($rawValue);
}
if ($type instanceof Object_) {
return self::make($type->getFqsen()->__toString(), $rawValue);
}
return $rawValue;
}
public static function resolveDeclaredTypeValue(ReflectionNamedType $parameter, mixed $rawValue): mixed
{
return match ($parameter->getName()) {
'string' => $rawValue,
'bool' => boolval($rawValue),
'int' => intval($rawValue),
'float' => floatval($rawValue),
'array' => $rawValue,
default => self::make($parameter->getName(), $rawValue),
};
}
/**
* @param array<string,mixed> $input
*/
public static function make(string $class, array $input): object
{
$parameters = ReflectionHelper::getParametersMeta($class);
$arguments = [];
foreach ($parameters as $parameter) {
$name = $parameter->reflection->getName();
$parameterArgs = empty($parameter->reflection->getAttributes(Flat::class)) ? ($input[$name] ?? null) : $input;
if (is_null($parameterArgs)) {
if ($parameter->reflection->allowsNull()) {
$arguments[$name] = null;
}
continue;
}
if ($caster = $parameter->reflection->getAttributes(CastWith::class)[0] ?? null) {
$caster = $caster->newInstance()->class;
$arguments[$name] = App::call("$caster@cast", ['data' => $parameterArgs]);
continue;
}
$parameterClass = $parameter->reflection->getClass()?->getName();
$type = $parameter->tag?->getType();
if (empty($parameterClass) && $type instanceof Object_) {
$parameterClass = $type->getFqsen();
}
if (!empty($parameterClass) && $caster = config('dto.cast.' . $parameterClass, null)) {
$arguments[$name] = App::call($caster, ['data' => $parameterArgs]);
continue;
}
if ($parameter->tag instanceof Param) {
$arguments[$name] = self::resolveAnnotatedValue($type, $parameterArgs);
continue;
}
if ($parameter->reflection->getType() instanceof ReflectionNamedType) {
$arguments[$name] = self::resolveDeclaredTypeValue($parameter->reflection->getType(), $parameterArgs);
continue;
}
$arguments[$name] = $parameterArgs;
}
return App::makeWith($class, $arguments);
}
}