3 declare(strict_types=1);
5 namespace CuyZ\Valinor\Normalizer\Transformer;
7 use CuyZ\Valinor\Definition\FunctionDefinition;
8 use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
9 use CuyZ\Valinor\Normalizer\Exception\TransformerHasInvalidCallableParameter;
10 use CuyZ\Valinor\Normalizer\Exception\TransformerHasNoParameter;
11 use CuyZ\Valinor\Normalizer\Exception\TransformerHasTooManyParameters;
12 use CuyZ\Valinor\Type\Types\CallableType;
14 use function array_shift;
15 use function call_user_func;
16 use function method_exists;
19 final class ValueTransformersHandler
21 /** @var array<string, true> */
22 private array $transformerCheck = [];
24 public function __construct(
25 private FunctionDefinitionRepository $functionDefinitionRepository,
29 * @param array<object> $attributes
30 * @param list<callable> $transformers
31 * @return array<mixed>|scalar|null
33 public function transform(mixed $value, array $attributes, array $transformers, callable $defaultTransformer): mixed
35 return call_user_func(
36 $this->next($transformers, $value, $attributes, $defaultTransformer),
41 * @param list<callable> $transformers
42 * @param list<object> $attributes
44 private function next(array $transformers, mixed $value, array $attributes, callable $defaultTransformer): callable
46 if ($attributes !== []) {
47 return $this->nextAttribute(
50 fn () => call_user_func($this->next($transformers, $value, [], $defaultTransformer)),
54 $transformer = array_shift($transformers);
56 if ($transformer === null) {
57 return fn () => $defaultTransformer($value);
60 $function = $this->functionDefinitionRepository->for($transformer);
62 $this->checkTransformer($function);
64 if (! $function->parameters()->at(0)->type()->accepts($value)) {
65 return $this->next($transformers, $value, [], $defaultTransformer);
68 return fn () => $transformer($value, fn () => call_user_func($this->next($transformers, $value, [], $defaultTransformer)));
72 * @param array<object> $attributes
74 private function nextAttribute(mixed $value, array $attributes, callable $next): callable
76 $attribute = array_shift($attributes);
78 if ($attribute === null) {
82 if (! method_exists($attribute, 'normalize')) {
83 return $this->nextAttribute($value, $attributes, $next);
86 // PHP8.1 First-class callable syntax
87 $function = $this->functionDefinitionRepository->for([$attribute, 'normalize']);
89 $this->checkTransformer($function);
91 if (! $function->parameters()->at(0)->type()->accepts($value)) {
92 return $this->nextAttribute($value, $attributes, $next);
95 return fn () => $attribute->normalize($value, fn () => call_user_func($this->nextAttribute($value, $attributes, $next)));
98 private function checkTransformer(FunctionDefinition $function): void
100 if (isset($this->transformerCheck[$function->signature()])) {
104 // @infection-ignore-all
105 $this->transformerCheck[$function->signature()] = true;
107 $parameters = $function->parameters();
109 if ($parameters->count() === 0) {
110 throw new TransformerHasNoParameter($function);
113 if ($parameters->count() > 2) {
114 throw new TransformerHasTooManyParameters($function);
117 if ($parameters->count() > 1 && ! $parameters->at(1)->type() instanceof CallableType) {
118 throw new TransformerHasInvalidCallableParameter($function, $parameters->at(1)->type());